mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-25 12:01:04 +02:00
145 lines
5.7 KiB
TypeScript
145 lines
5.7 KiB
TypeScript
"use client";
|
|
|
|
import { ExternalLink } from "lucide-react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
import {
|
|
getModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2Get,
|
|
getModelConfigurationV2DefaultsApiV1OrganizationsModelConfigurationsV2DefaultsGet,
|
|
saveModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2Put,
|
|
} from "@/client/sdk.gen";
|
|
import type {
|
|
ModelConfigurationPricingResponse,
|
|
OrganizationAiModelConfigurationResponse,
|
|
OrganizationAiModelConfigurationV2,
|
|
} from "@/client/types.gen";
|
|
import { AIModelConfigurationV2Editor, type ModelConfigurationDefaultsV2 } from "@/components/AIModelConfigurationV2Editor";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { useUserConfig } from "@/context/UserConfigContext";
|
|
import { detailFromError } from "@/lib/apiError";
|
|
import { useAuth } from "@/lib/auth";
|
|
import { fetchModelConfigurationPricing } from "@/lib/modelConfigurationPricing";
|
|
|
|
export default function ModelConfigurationV2({ docsUrl }: { docsUrl?: string }) {
|
|
const auth = useAuth();
|
|
const { refreshConfig } = useUserConfig();
|
|
const hasFetched = useRef(false);
|
|
|
|
const [defaults, setDefaults] = useState<ModelConfigurationDefaultsV2 | null>(null);
|
|
const [response, setResponse] = useState<OrganizationAiModelConfigurationResponse | null>(null);
|
|
const [pricing, setPricing] = useState<ModelConfigurationPricingResponse | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [notice, setNotice] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (auth.loading || !auth.user || hasFetched.current) return;
|
|
hasFetched.current = true;
|
|
|
|
const load = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
const [defaultsResult, configResult, pricingResult] = await Promise.all([
|
|
getModelConfigurationV2DefaultsApiV1OrganizationsModelConfigurationsV2DefaultsGet(),
|
|
getModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2Get(),
|
|
fetchModelConfigurationPricing(),
|
|
]);
|
|
|
|
if (defaultsResult.error) {
|
|
setError(detailFromError(defaultsResult.error, "Failed to load model configuration defaults"));
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
if (configResult.error) {
|
|
setError(detailFromError(configResult.error, "Failed to load model configuration"));
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
const nextDefaults = defaultsResult.data as ModelConfigurationDefaultsV2;
|
|
if (!nextDefaults || !configResult.data) {
|
|
setError("Failed to load model configuration");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
setDefaults(nextDefaults);
|
|
setResponse(configResult.data);
|
|
setPricing(pricingResult);
|
|
setLoading(false);
|
|
};
|
|
|
|
load();
|
|
|
|
}, [auth.loading, auth.user]);
|
|
|
|
const saveConfiguration = async (configuration: OrganizationAiModelConfigurationV2) => {
|
|
if (!defaults) return;
|
|
setError(null);
|
|
setNotice(null);
|
|
|
|
const result = await saveModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2Put({
|
|
body: configuration,
|
|
});
|
|
|
|
if (result.error) {
|
|
throw new Error(detailFromError(result.error, "Failed to save model configuration"));
|
|
}
|
|
if (!result.data) {
|
|
throw new Error("Failed to save model configuration");
|
|
}
|
|
|
|
setResponse(result.data);
|
|
void fetchModelConfigurationPricing().then(setPricing);
|
|
await refreshConfig();
|
|
setNotice("Model configuration saved");
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="w-full max-w-4xl mx-auto space-y-6">
|
|
<Skeleton className="h-10 w-80" />
|
|
<Skeleton className="h-28 w-full" />
|
|
<Skeleton className="h-96 w-full" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="w-full max-w-4xl mx-auto space-y-6">
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">AI Models Configuration</h1>
|
|
<p className="mt-2 text-sm text-muted-foreground">
|
|
Organization-scoped model settings.{" "}
|
|
{docsUrl && (
|
|
<a href={docsUrl} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-0.5 underline">
|
|
Learn more <ExternalLink className="h-3 w-3" />
|
|
</a>
|
|
)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
|
{error}
|
|
</div>
|
|
)}
|
|
{notice && (
|
|
<div className="rounded-md border border-green-500/40 bg-green-500/10 px-4 py-3 text-sm text-green-700 dark:text-green-300">
|
|
{notice}
|
|
</div>
|
|
)}
|
|
|
|
{defaults && response && (
|
|
<AIModelConfigurationV2Editor
|
|
defaults={defaults}
|
|
configuration={response.configuration}
|
|
effectiveConfiguration={response.effective_configuration}
|
|
pricing={pricing}
|
|
onSave={saveConfiguration}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|