diff --git a/autogpt_platform/backend/backend/api/features/admin/execution_analytics_routes.py b/autogpt_platform/backend/backend/api/features/admin/execution_analytics_routes.py index 13d5662d82..6fbc087826 100644 --- a/autogpt_platform/backend/backend/api/features/admin/execution_analytics_routes.py +++ b/autogpt_platform/backend/backend/api/features/admin/execution_analytics_routes.py @@ -201,6 +201,21 @@ async def get_execution_analytics_config( # Sort models by provider and name for better UX available_models.sort(key=lambda x: (x.provider, x.label)) + # Handle case where no models are available + if not available_models: + logger.warning( + "No enabled LLM models found in registry. " + "Ensure models are configured and enabled in the LLM Registry." + ) + # Provide a placeholder entry so admins see meaningful feedback + available_models.append( + ModelInfo( + value="", + label="No models available - configure in LLM Registry", + provider="none", + ) + ) + return ExecutionAnalyticsConfig( available_models=available_models, default_system_prompt=DEFAULT_SYSTEM_PROMPT, diff --git a/autogpt_platform/backend/backend/executor/llm_registry_init.py b/autogpt_platform/backend/backend/executor/llm_registry_init.py index 92b8dc01bf..0779e932da 100644 --- a/autogpt_platform/backend/backend/executor/llm_registry_init.py +++ b/autogpt_platform/backend/backend/executor/llm_registry_init.py @@ -27,12 +27,7 @@ async def initialize_registry_for_executor() -> None: await db.connect() logger.info("[GraphExecutor] Connected to database for registry refresh") - # Refresh LLM registry before initializing blocks - await llm_registry.refresh_llm_registry() - refresh_llm_costs() - logger.info("[GraphExecutor] LLM registry refreshed") - - # Initialize blocks (also refreshes registry, but we do it explicitly above) + # Initialize blocks (internally refreshes LLM registry and costs) await initialize_blocks() logger.info("[GraphExecutor] Blocks initialized") except Exception as exc: diff --git a/autogpt_platform/backend/backend/server/v2/llm/db.py b/autogpt_platform/backend/backend/server/v2/llm/db.py index ad3ee30082..894fcbf256 100644 --- a/autogpt_platform/backend/backend/server/v2/llm/db.py +++ b/autogpt_platform/backend/backend/server/v2/llm/db.py @@ -252,24 +252,22 @@ async def update_model( # If we have costs to update, we need to handle them separately # because nested writes have different constraints if request.costs is not None: - # First update scalar fields - if scalar_data: - await prisma.models.LlmModel.prisma().update( - where={"id": model_id}, - data=scalar_data, - ) - # Then handle costs: delete existing and create new - await prisma.models.LlmModelCost.prisma().delete_many( - where={"llmModelId": model_id} - ) - if request.costs: - cost_payload = _cost_create_payload(request.costs) - for cost_item in cost_payload["create"]: - cost_item["llmModelId"] = model_id - await prisma.models.LlmModelCost.prisma().create( - data=cast(Any, cost_item) + # Wrap cost replacement in a transaction for atomicity + async with transaction() as tx: + # First update scalar fields + if scalar_data: + await tx.llmmodel.update( + where={"id": model_id}, + data=scalar_data, ) - # Fetch the updated record + # Then handle costs: delete existing and create new + await tx.llmmodelcost.delete_many(where={"llmModelId": model_id}) + if request.costs: + cost_payload = _cost_create_payload(request.costs) + for cost_item in cost_payload["create"]: + cost_item["llmModelId"] = model_id + await tx.llmmodelcost.create(data=cast(Any, cost_item)) + # Fetch the updated record (outside transaction) record = await prisma.models.LlmModel.prisma().find_unique( where={"id": model_id}, include={"Costs": True, "Creator": True}, diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx index 52778393ca..c024b45540 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/layout.tsx @@ -1,5 +1,8 @@ +"use client"; + import { Sidebar } from "@/components/__legacy__/Sidebar"; -import { Users, DollarSign, UserSearch, FileText, Cpu } from "lucide-react"; +import { Users, DollarSign, UserSearch, FileText } from "lucide-react"; +import { Cpu } from "@phosphor-icons/react"; import { IconSliders } from "@/components/__legacy__/ui/icons"; @@ -29,7 +32,7 @@ const sidebarLinkGroups = [ { text: "LLM Registry", href: "/admin/llms", - icon: , + icon: , }, { text: "Admin User Management", diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddCreatorModal.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddCreatorModal.tsx index 81914b0196..ab84c07092 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddCreatorModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddCreatorModal.tsx @@ -8,8 +8,24 @@ import { useRouter } from "next/navigation"; export function AddCreatorModal() { const [open, setOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); const router = useRouter(); + async function handleSubmit(formData: FormData) { + setIsSubmitting(true); + setError(null); + try { + await createLlmCreatorAction(formData); + setOpen(false); + router.refresh(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to create creator"); + } finally { + setIsSubmitting(false); + } + } + return ( -
{ - await createLlmCreatorAction(formData); - setOpen(false); - router.refresh(); - }} - className="space-y-4" - > +
+ {error && ( +
+ {error} +
+ )} + - diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddModelModal.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddModelModal.tsx index cdecf3fd32..9d94e9850c 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddModelModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddModelModal.tsx @@ -16,8 +16,24 @@ interface Props { export function AddModelModal({ providers, creators }: Props) { const [open, setOpen] = useState(false); const [selectedCreatorId, setSelectedCreatorId] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); const router = useRouter(); + async function handleSubmit(formData: FormData) { + setIsSubmitting(true); + setError(null); + try { + await createLlmModelAction(formData); + setOpen(false); + router.refresh(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to create model"); + } finally { + setIsSubmitting(false); + } + } + // When provider changes, auto-select matching creator if one exists function handleProviderChange(providerId: string) { const provider = providers.find((p) => p.id === providerId); @@ -49,14 +65,7 @@ export function AddModelModal({ providers, creators }: Props) { Register a new model slug, metadata, and pricing.
-
{ - await createLlmModelAction(formData); - setOpen(false); - router.refresh(); - }} - className="space-y-6" - > + {/* Basic Information */}
@@ -239,6 +248,7 @@ export function AddModelModal({ providers, creators }: Props) { required type="number" name="credit_cost" + step="1" className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm transition-colors placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" placeholder="5" min={0} @@ -269,17 +279,32 @@ export function AddModelModal({ providers, creators }: Props) {
+ {error && ( +
+ {error} +
+ )} + - diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddProviderModal.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddProviderModal.tsx index cb664b298d..4e771b1d81 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddProviderModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/AddProviderModal.tsx @@ -8,8 +8,26 @@ import { useRouter } from "next/navigation"; export function AddProviderModal() { const [open, setOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); const router = useRouter(); + async function handleSubmit(formData: FormData) { + setIsSubmitting(true); + setError(null); + try { + await createLlmProviderAction(formData); + setOpen(false); + router.refresh(); + } catch (err) { + setError( + err instanceof Error ? err.message : "Failed to create provider", + ); + } finally { + setIsSubmitting(false); + } + } + return (
-
{ - await createLlmProviderAction(formData); - setOpen(false); - router.refresh(); - }} - className="space-y-6" - > + {/* Basic Information */}
@@ -222,17 +233,32 @@ export function AddProviderModal() {
+ {error && ( +
+ {error} +
+ )} + -
diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/CreatorsTable.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/CreatorsTable.tsx index de6ad5a116..e66992e8a7 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/CreatorsTable.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/CreatorsTable.tsx @@ -58,7 +58,13 @@ export function CreatorsTable({ creators }: { creators: LlmModelCreator[] }) { rel="noopener noreferrer" className="text-sm text-primary hover:underline" > - {new URL(creator.website_url).hostname} + {(() => { + try { + return new URL(creator.website_url).hostname; + } catch { + return creator.website_url; + } + })()} ) : ( @@ -80,8 +86,24 @@ export function CreatorsTable({ creators }: { creators: LlmModelCreator[] }) { function EditCreatorModal({ creator }: { creator: LlmModelCreator }) { const [open, setOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); const router = useRouter(); + async function handleSubmit(formData: FormData) { + setIsSubmitting(true); + setError(null); + try { + await updateLlmCreatorAction(formData); + setOpen(false); + router.refresh(); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to update creator"); + } finally { + setIsSubmitting(false); + } + } + return ( -
{ - await updateLlmCreatorAction(formData); - setOpen(false); - router.refresh(); - }} - className="space-y-4" - > +
@@ -145,17 +160,32 @@ function EditCreatorModal({ creator }: { creator: LlmModelCreator }) { />
+ {error && ( +
+ {error} +
+ )} + -
diff --git a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/DeleteModelModal.tsx b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/DeleteModelModal.tsx index 8b975d89b2..ef762f5251 100644 --- a/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/DeleteModelModal.tsx +++ b/autogpt_platform/frontend/src/app/(platform)/admin/llms/components/DeleteModelModal.tsx @@ -147,6 +147,7 @@ export function DeleteModelModal({