add basic ui for private inference resource

This commit is contained in:
miloschwartz
2026-08-04 16:54:23 -04:00
parent c085de1e9e
commit 7759d87835
19 changed files with 828 additions and 208 deletions
+72 -20
View File
@@ -42,6 +42,7 @@ export type PrivateResourceFormValues = {
roles?: PrivateResourceAccessTag[];
users?: PrivateResourceAccessTag[];
clients?: PrivateResourceClient[];
providerIds?: number[];
};
export type SiteResourceAccess = {
@@ -220,7 +221,11 @@ export function buildCreateSiteResourcePayload(
typeof data.alias === "string" &&
data.alias.trim()
? data.alias
: undefined
: undefined,
aiProviders: (data.providerIds ?? []).map((providerId) => ({
providerId,
modelAccessMode: "catalog" as const
}))
}),
...((data.mode === "host" || data.mode === "cidr") && {
tcpPortRangeString: data.tcpPortRangeString,
@@ -353,19 +358,33 @@ export function siteResourceToFormValues(
};
}
export function createGeneralFormSchema(t: TranslateFn) {
return z.object({
name: z
.string()
.min(1, t("editInternalResourceDialogNameRequired"))
.max(255, t("editInternalResourceDialogNameMaxLength")),
niceId: z
.string()
.min(1)
.max(255)
.regex(/^[a-zA-Z0-9-]+$/),
enabled: z.boolean()
});
export function createGeneralFormSchema(
t: TranslateFn,
options?: { requireAlias?: boolean }
) {
return z
.object({
name: z
.string()
.min(1, t("editInternalResourceDialogNameRequired"))
.max(255, t("editInternalResourceDialogNameMaxLength")),
niceId: z
.string()
.min(1)
.max(255)
.regex(/^[a-zA-Z0-9-]+$/),
enabled: z.boolean(),
alias: z.string().nullish()
})
.superRefine((data, ctx) => {
if (options?.requireAlias && !data.alias?.trim()) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("aiResourceAliasRequired"),
path: ["alias"]
});
}
});
}
export function createAccessFormSchema() {
@@ -422,7 +441,8 @@ export function createCreateFormSchema(t: TranslateFn) {
pamMode: z.enum(["passthrough", "push"]).optional().nullable(),
tcpPortRangeString: createPortRangeStringSchema(t),
udpPortRangeString: createPortRangeStringSchema(t),
disableIcmp: z.boolean().optional()
disableIcmp: z.boolean().optional(),
providerIds: z.array(z.number().int().positive()).optional()
})
.superRefine((data, ctx) => {
const isNativeSsh =
@@ -434,12 +454,27 @@ export function createCreateFormSchema(t: TranslateFn) {
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t(
"createInternalResourceDialogPleaseSelectSite"
),
message: t("createInternalResourceDialogPleaseSelectSite"),
path: ["siteIds"]
});
}
if (data.mode === "inference") {
const trimmedAlias = data.alias?.trim();
if (!trimmedAlias) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("aiResourceAliasRequired"),
path: ["alias"]
});
}
if (!data.providerIds || data.providerIds.length < 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("aiResourceProvidersRequired"),
path: ["providerIds"]
});
}
}
if (
data.mode !== "ssh" &&
data.mode !== "inference" &&
@@ -592,9 +627,26 @@ export function createInferenceFormSchema(t: TranslateFn) {
return z
.object({
mode: z.literal("inference"),
alias: z.string().nullish()
alias: z.string().nullish(),
providerIds: z.array(z.number().int().positive()).optional()
})
.superRefine((data, ctx) => destinationRefine(data, ctx, t));
.superRefine((data, ctx) => {
const trimmedAlias = data.alias?.trim();
if (!trimmedAlias) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("aiResourceAliasRequired"),
path: ["alias"]
});
}
if (!data.providerIds || data.providerIds.length < 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("aiResourceProvidersRequired"),
path: ["providerIds"]
});
}
});
}
export function createCidrFormSchema(t: TranslateFn) {
+58
View File
@@ -57,6 +57,10 @@ import type {
} from "@server/routers/siteResource";
import type { GetSiteResourceResponse } from "@server/routers/siteResource/getSiteResource";
import type { ListTargetsResponse } from "@server/routers/target";
import type {
ListAiModelsResponse,
ListAiProvidersResponse
} from "@server/routers/aiProvider/types";
import type { ListUsersResponse } from "@server/routers/user";
import type ResponseT from "@server/types/Response";
import {
@@ -1174,6 +1178,36 @@ export const aiProviderQueries = {
return res.data.data.targets;
}
}),
providerModels: ({ providerId }: { providerId: number }) =>
queryOptions({
queryKey: ["AI_PROVIDERS", providerId, "MODELS"] as const,
queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get<
AxiosResponse<ListAiModelsResponse>
>(`/ai-provider/${providerId}/models`, {
params: { page: 1, pageSize: 1000 },
signal
});
return res.data.data.models;
}
}),
orgProviders: ({ orgId, query }: { orgId: string; query?: string }) =>
queryOptions({
queryKey: ["AI_PROVIDERS", orgId, "LIST", query ?? ""] as const,
queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get<
AxiosResponse<ListAiProvidersResponse>
>(`/org/${orgId}/ai-providers`, {
params: {
page: 1,
pageSize: 100,
...(query ? { query } : {})
},
signal
});
return res.data.data.providers;
}
})
};
@@ -1242,6 +1276,30 @@ export const resourceQueries = {
return res.data.data.clients;
}
}),
siteResourceAiProviders: ({ siteResourceId }: { siteResourceId: number }) =>
queryOptions({
queryKey: [
"SITE_RESOURCES",
siteResourceId,
"AI_PROVIDERS"
] as const,
queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get<
AxiosResponse<{
providers: Array<{
providerId: number;
modelAccessMode: "catalog" | "allowlist";
name: string;
type: string;
enabled: boolean;
}>;
}>
>(`/site-resource/${siteResourceId}/ai-providers`, {
signal
});
return res.data.data.providers;
}
}),
resourceTargets: ({ resourceId }: { resourceId: number }) =>
queryOptions({
queryKey: ["RESOURCES", resourceId, "TARGETS"] as const,