Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions app/(main)/guardrails/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import SavedConfigsList from "@/app/components/guardrails/SavedConfigsList";

export default function GuardrailsPage() {
const { sidebarCollapsed } = useApp();
const { isHydrated } = useAuth();
const { isHydrated, activeKey } = useAuth();
const apiKey = activeKey?.key ?? "";
const toast = useToast();
const [orgContext, setOrgContext] = useState<OrgContext | null>(null);
const [validators, setValidators] = useState<Validator[]>([]);
Expand All @@ -40,6 +41,7 @@ export default function GuardrailsPage() {
if (!isHydrated) return;
guardrailsFetch<{ data?: { organization_id: number; project_id: number } }>(
"/api/apikeys/verify", //need to change this in backend to /auth/verify
apiKey,
)
.then((data) => {
const org_id = data?.data?.organization_id;
Expand All @@ -53,11 +55,11 @@ export default function GuardrailsPage() {
.catch((e: Error) =>
toast.error(e.message || "Session verification failed"),
);
}, [isHydrated]);
}, [isHydrated, apiKey]);

useEffect(() => {
setValidatorsLoading(true);
guardrailsFetch<{ validators?: Validator[] }>("/api/guardrails")
guardrailsFetch<{ validators?: Validator[] }>("/api/guardrails", apiKey)
.then((data) => {
const list: Validator[] = Array.isArray(data?.validators)
? data.validators
Expand All @@ -66,7 +68,7 @@ export default function GuardrailsPage() {
})
.catch(() => toast.error("Failed to load validators"))
.finally(() => setValidatorsLoading(false));
}, []);
}, [apiKey]);

const configsQueryString = orgContext
? `?organization_id=${parseInt(String(orgContext.organization_id), 10)}&project_id=${parseInt(String(orgContext.project_id), 10)}`
Expand All @@ -78,7 +80,7 @@ export default function GuardrailsPage() {
guardrailsFetch<{
data?: { configs?: SavedValidatorConfig[] } | SavedValidatorConfig[];
configs?: SavedValidatorConfig[];
}>(`/api/guardrails/validators/configs${configsQueryString}`)
}>(`/api/guardrails/validators/configs${configsQueryString}`, apiKey)
.then((data) => {
const nested = data?.data;
const list: SavedValidatorConfig[] = Array.isArray(
Expand All @@ -94,7 +96,7 @@ export default function GuardrailsPage() {
})
.catch(() => toast.error("Failed to load saved configs"))
.finally(() => setSavedConfigsLoading(false));
}, [configsQueryString]);
}, [configsQueryString, apiKey]);

useEffect(() => {
fetchSavedConfigs();
Expand All @@ -115,6 +117,7 @@ export default function GuardrailsPage() {
try {
await guardrailsFetch(
`/api/guardrails/validators/configs/${configId}${configsQueryString}`,
apiKey,
{ method: "DELETE" },
);
toast.success("Config deleted");
Expand Down Expand Up @@ -149,7 +152,7 @@ export default function GuardrailsPage() {

const body = configValues;

await guardrailsFetch(url, {
await guardrailsFetch(url, apiKey, {
method: isUpdate ? "PATCH" : "POST",
body: JSON.stringify(body),
});
Expand Down
7 changes: 5 additions & 2 deletions app/components/guardrails/BanListField.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import BanListModal from "./BanListModal";
import { guardrailsFetch } from "@/app/lib/guardrailsClient";
import { useAuth } from "@/app/lib/context/AuthContext";
import Select from "@/app/components/Select";

interface BanList {
Expand All @@ -14,6 +15,8 @@ interface BanListFieldProps {
}

export default function BanListField({ value, onChange }: BanListFieldProps) {
const { activeKey } = useAuth();
const apiKey = activeKey?.key ?? "";
const [banLists, setBanLists] = useState<BanList[]>([]);
const [loading, setLoading] = useState(true);
const [fetchError, setFetchError] = useState<string | null>(null);
Expand All @@ -29,7 +32,7 @@ export default function BanListField({ value, onChange }: BanListFieldProps) {
guardrailsFetch<{
data?: { ban_lists?: BanList[] } | BanList[];
ban_lists?: BanList[];
}>("/api/guardrails/ban_lists")
}>("/api/guardrails/ban_lists", apiKey)
.then((data) => {
const nested = data?.data;
const list: BanList[] = Array.isArray(
Expand Down Expand Up @@ -57,7 +60,7 @@ export default function BanListField({ value, onChange }: BanListFieldProps) {
guardrailsFetch<{
banned_words?: string[];
data?: { banned_words?: string[] };
}>(`/api/guardrails/ban_lists/${id}`)
}>(`/api/guardrails/ban_lists/${id}`, apiKey)
.then((data) => {
const words: string[] = Array.isArray(data?.banned_words)
? data.banned_words!
Expand Down
4 changes: 4 additions & 0 deletions app/components/guardrails/BanListModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from "react";
import { guardrailsFetch } from "@/app/lib/guardrailsClient";
import { useAuth } from "@/app/lib/context/AuthContext";
import Button from "@/app/components/Button";
import { CloseIcon } from "@/app/components/icons";
import Field from "@/app/components/Field";
Expand All @@ -13,6 +14,8 @@ export default function BanListModal({
onClose,
onCreated,
}: BanListModalProps) {
const { activeKey } = useAuth();
const apiKey = activeKey?.key ?? "";
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [bannedWords, setBannedWords] = useState("");
Expand Down Expand Up @@ -52,6 +55,7 @@ export default function BanListModal({
};
const data = await guardrailsFetch<{ id: string; name?: string }>(
"/api/guardrails/ban_lists",
apiKey,
{ method: "POST", body: JSON.stringify(body) },
);
onCreated({ id: data.id, name: data.name ?? name.trim() });
Expand Down
14 changes: 5 additions & 9 deletions app/components/guardrails/SavedConfigsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
EditIcon,
} from "@/app/components/icons";
import Button from "@/app/components/Button";
import Loader from "@/app/components/Loader";
import { VALIDATOR_META_BY_TYPE } from "@/app/lib/utils/guardrails";

interface SavedConfigsListProps {
Expand Down Expand Up @@ -47,13 +48,8 @@ export default function SavedConfigsList({

<div className="flex-1 overflow-auto p-5">
{isLoading ? (
<div className="space-y-3">
{[...Array(3)].map((_, i) => (
<div
key={i}
className="h-24 rounded-lg animate-pulse border border-border bg-bg-secondary"
/>
))}
<div className="flex items-center justify-center py-16">
<Loader size="md" message="Loading saved configurations..." />
</div>
) : configs.length === 0 ? (
<div className="flex flex-col items-center justify-center border-2 border-dashed border-border rounded-lg py-16 text-center">
Expand All @@ -75,10 +71,10 @@ export default function SavedConfigsList({
return (
<div
key={cfg.id}
className={`rounded-xl border border-border border-l-4 bg-bg-primary shadow-sm cursor-pointer transition-shadow hover:shadow-md ${
className={`rounded-xl border-l-2 bg-bg-primary shadow-sm cursor-pointer transition-shadow hover:shadow-md ${
isSelected
? "border-l-status-success"
: "border-l-amber-600"
: "border-l-[rgb(220,207,195)]"
}`}
onClick={() => onSelectConfig(cfg)}
>
Expand Down
3 changes: 2 additions & 1 deletion app/components/prompt-editor/ConfigEditorPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export default function ConfigEditorPane({
useEffect(() => {
guardrailsFetch<{ data?: { organization_id: number; project_id: number } }>(
"/api/apikeys/verify",
apiKey,
)
.then((data) => {
const org_id = data?.data?.organization_id;
Expand All @@ -85,7 +86,7 @@ export default function ConfigEditorPane({
}
})
.catch(() => {});
}, []);
}, [apiKey]);

const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [expandedConfigId, setExpandedConfigId] = useState<string | null>(null);
Expand Down
3 changes: 3 additions & 0 deletions app/lib/guardrailsClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,19 @@ export function guardrailsUserClient(

/**
* Client-side fetch helper for guardrails Next.js route handlers (/api/guardrails/*).
* Attaches X-API-KEY header and `credentials: "include"` for cookie-based auth.
* Parses JSON and throws on non-OK responses.
*/
export async function guardrailsFetch<T>(
path: string,
apiKey: string,
options: RequestInit = {},
): Promise<T> {
const headers = new Headers(options.headers);
if (!(options.body instanceof FormData) && !headers.has("Content-Type")) {
if (options.body) headers.set("Content-Type", "application/json");
}
if (apiKey) headers.set("X-API-KEY", apiKey);

const res = await fetch(path, {
...options,
Expand Down
Loading