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
486 changes: 486 additions & 0 deletions crates/admin-ui/ui/src/components/guardrails/guardrail-form.tsx

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/admin-ui/ui/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Monitor,
Moon,
Settings,
Shield,
Server,
Sun,
Zap,
Expand All @@ -26,6 +27,7 @@ const NAV_GROUPS = [
{ to: '/playground', labelKey: 'nav.playground', icon: LayoutDashboard },
{ to: '/providers', labelKey: 'nav.providers', icon: Server },
{ to: '/models', labelKey: 'nav.models', icon: Boxes },
{ to: '/guardrails', labelKey: 'nav.guardrails', icon: Shield },
{ to: '/apikeys', labelKey: 'nav.apiKeys', icon: KeyRound },
],
},
Expand Down
68 changes: 68 additions & 0 deletions crates/admin-ui/ui/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"playground": "Playground",
"providers": "Providers",
"models": "Models",
"guardrails": "Guardrails",
"apiKeys": "API Keys",
"settings": "Settings"
},
Expand Down Expand Up @@ -62,6 +63,10 @@
"deepseek": "DeepSeek",
"bedrock": "AWS Bedrock"
},
"guardrailTypes": {
"regex": "Regex",
"bedrock": "AWS Bedrock"
},
"playground": {
"title": "Playground",
"addComparison": "Add Comparison",
Expand Down Expand Up @@ -170,6 +175,69 @@
"concurrency": "Concurrency"
}
},
"guardrails": {
"title": "Guardrails",
"addGuardrail": "Add Guardrail",
"deleteGuardrail": "Delete Guardrail",
"createTitle": "Create guardrail resource",
"createDesc": "Required: name, type, config. Config fields depend on the selected guardrail type.",
"editTitle": "Edit guardrail resource",
"createGuardrail": "Create Guardrail",
"columns": {
"id": "ID",
"name": "Name",
"type": "Type",
"config": "Config",
"action": "Action"
},
"search": "Search guardrails…",
"count_one": "{{count}} guardrail",
"count_other": "{{count}} guardrails",
"empty": "No guardrails yet.",
"emptyAction": "Add one",
"errorLoad": "Failed to load guardrails.",
"errorLoadSingle": "Failed to load guardrail.",
"deleteFailed": "Failed to delete guardrail.",
"bulkDeleteFailed": "Deleted {{successCount}} guardrails; {{failureCount}} deletions failed: {{details}}",
"deleteConfirm": "Delete guardrail \"{{id}}\"?",
"regexSummary": "Pattern: {{pattern}}",
"bedrockSummary": "{{identifier}} · v{{version}} · {{region}}",
"form": {
"basicInfo": "Basic Information",
"nameLabel": "Name *",
"namePlaceholder": "e.g. tenant-a-output-guardrail",
"nameRequired": "Please enter a guardrail name before saving.",
"typeLabel": "Guardrail Type *",
"typePlaceholder": "Select a guardrail type",
"config": "Guardrail Config",
"patternLabel": "Regex Pattern *",
"patternPlaceholder": "e.g. secret token",
"patternRequired": "Please enter a regex pattern.",
"blockReasonLabel": "Block Reason",
"blockReasonPlaceholder": "Optional reason returned to the client",
"identifierLabel": "Guardrail Identifier *",
"identifierPlaceholder": "e.g. gr-123abc",
"identifierRequired": "Please enter the Bedrock guardrail identifier.",
"versionLabel": "Version *",
"versionPlaceholder": "e.g. 1",
"versionRequired": "Please enter the Bedrock guardrail version.",
"regionLabel": "AWS Region *",
"regionHint": "Used to choose the default Bedrock runtime endpoint.",
"regionRequired": "Please enter an AWS region.",
"accessKeyIdLabel": "AWS Access Key ID *",
"accessKeyIdRequired": "Please enter the AWS access key ID.",
"secretAccessKeyLabel": "AWS Secret Access Key *",
"secretAccessKeyPlaceholder": "AWS secret access key",
"secretAccessKeyRequired": "Please enter the AWS secret access key.",
"sessionTokenLabel": "AWS Session Token",
"sessionTokenHint": "Optional temporary credential when using STS or assumed roles.",
"sessionTokenPlaceholder": "Optional temporary credential",
"endpointLabel": "Bedrock Endpoint Override",
"endpointHint": "Leave blank to use the standard runtime endpoint for the selected region.",
"showSecret": "Show value",
"hideSecret": "Hide value"
}
},
"providers": {
"title": "Providers",
"addProvider": "Add Provider",
Expand Down
68 changes: 68 additions & 0 deletions crates/admin-ui/ui/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"playground": "Playground",
"providers": "模型提供商",
"models": "模型",
"guardrails": "Guardrail",
Comment thread
bzp2010 marked this conversation as resolved.
"apiKeys": "API 密钥",
"settings": "设置"
},
Expand Down Expand Up @@ -62,6 +63,10 @@
"deepseek": "DeepSeek",
"bedrock": "AWS Bedrock"
},
"guardrailTypes": {
"regex": "Regex",
"bedrock": "AWS Bedrock"
},
"playground": {
"title": "Playground",
"addComparison": "添加对比",
Expand Down Expand Up @@ -170,6 +175,69 @@
"concurrency": "并发"
}
},
"guardrails": {
"title": "Guardrail",
"addGuardrail": "添加 Guardrail",
"deleteGuardrail": "删除 Guardrail",
"createTitle": "创建 Guardrail 资源",
"createDesc": "必填:name、type、config。config 字段会随 Guardrail 类型变化。",
"editTitle": "编辑 Guardrail 资源",
"createGuardrail": "创建 Guardrail",
"columns": {
"id": "ID",
"name": "名称",
"type": "类型",
"config": "配置",
"action": "操作"
},
"search": "搜索 Guardrail…",
"count_one": "{{count}} 个 Guardrail",
"count_other": "{{count}} 个 Guardrail",
"empty": "暂无 Guardrail。",
"emptyAction": "添加一个",
"errorLoad": "加载 Guardrail 失败。",
"errorLoadSingle": "加载 Guardrail 失败。",
"deleteFailed": "删除 Guardrail 失败。",
"bulkDeleteFailed": "已删除 {{successCount}} 个 Guardrail,{{failureCount}} 个删除失败:{{details}}",
"deleteConfirm": "确认删除 Guardrail \"{{id}}\"?",
"regexSummary": "模式:{{pattern}}",
"bedrockSummary": "{{identifier}} · v{{version}} · {{region}}",
"form": {
"basicInfo": "基础信息",
"nameLabel": "名称 *",
"namePlaceholder": "例如:tenant-a-output-guardrail",
"nameRequired": "保存前请先输入 Guardrail 名称。",
"typeLabel": "Guardrail 类型 *",
"typePlaceholder": "选择 Guardrail 类型",
"config": "Guardrail 配置",
"patternLabel": "正则表达式 *",
"patternPlaceholder": "例如:secret token",
"patternRequired": "请输入正则表达式。",
"blockReasonLabel": "拦截原因",
"blockReasonPlaceholder": "返回给客户端的可选原因",
"identifierLabel": "Guardrail Identifier *",
"identifierPlaceholder": "例如:gr-123abc",
"identifierRequired": "请输入 Bedrock guardrail identifier。",
"versionLabel": "Version *",
"versionPlaceholder": "例如:1",
"versionRequired": "请输入 Bedrock guardrail version。",
"regionLabel": "AWS Region *",
"regionHint": "用于选择默认的 Bedrock 运行时地址。",
"regionRequired": "请输入 AWS Region。",
"accessKeyIdLabel": "AWS Access Key ID *",
"accessKeyIdRequired": "请输入 AWS Access Key ID。",
"secretAccessKeyLabel": "AWS Secret Access Key *",
"secretAccessKeyPlaceholder": "AWS secret access key",
"secretAccessKeyRequired": "请输入 AWS Secret Access Key。",
"sessionTokenLabel": "AWS Session Token",
"sessionTokenHint": "使用 STS 或 AssumeRole 临时凭证时可选。",
"sessionTokenPlaceholder": "可选的临时凭证",
"endpointLabel": "Bedrock Endpoint Override",
"endpointHint": "留空则根据所选 Region 使用标准运行时地址。",
"showSecret": "显示明文",
"hideSecret": "隐藏明文"
}
},
"providers": {
"title": "模型提供商",
"addProvider": "添加模型提供商",
Expand Down
19 changes: 19 additions & 0 deletions crates/admin-ui/ui/src/lib/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ApiError,
ApiKey,
DeleteResponse,
Guardrail,
ItemResponse,
ListResponse,
Model,
Expand Down Expand Up @@ -105,3 +106,21 @@ export const apiKeysApi = {
delete: (adminKey: string, id: string) =>
request<DeleteResponse>('DELETE', `/apikeys/${id}`, adminKey),
};

// ── Guardrails ─────────────────────────────────────────────────────────────────
export const guardrailsApi = {
list: (adminKey: string) =>
request<ListResponse<Guardrail>>('GET', '/guardrails', adminKey),

get: (adminKey: string, id: string) =>
request<ItemResponse<Guardrail>>('GET', `/guardrails/${id}`, adminKey),

create: (adminKey: string, data: Guardrail) =>
request<ItemResponse<Guardrail>>('POST', '/guardrails', adminKey, data),

update: (adminKey: string, id: string, data: Guardrail) =>
request<ItemResponse<Guardrail>>('PUT', `/guardrails/${id}`, adminKey, data),

delete: (adminKey: string, id: string) =>
request<DeleteResponse>('DELETE', `/guardrails/${id}`, adminKey),
};
31 changes: 31 additions & 0 deletions crates/admin-ui/ui/src/lib/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,37 @@ export interface Model {
rate_limit?: RateLimit;
}

export const GUARDRAIL_TYPE_VARIANTS = ['regex', 'bedrock'] as const;

export type GuardrailType = (typeof GUARDRAIL_TYPE_VARIANTS)[number];

export interface RegexGuardrailConfig {
pattern: string;
block_reason?: string;
}

export interface BedrockGuardrailConfig {
identifier: string;
version: string;
region: string;
access_key_id: string;
secret_access_key: string;
session_token?: string;
endpoint?: string;
}

export type Guardrail =
| {
name: string;
type: 'regex';
config: RegexGuardrailConfig;
}
| {
name: string;
type: 'bedrock';
config: BedrockGuardrailConfig;
};
Comment thread
bzp2010 marked this conversation as resolved.

export const PROVIDER_TYPE_VARIANTS = [
'openai',
'openrouter',
Expand Down
88 changes: 88 additions & 0 deletions crates/admin-ui/ui/src/lib/queries/guardrails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

import { useAdminKey } from '@/hooks/use-admin-key';
import { ApiClientError, guardrailsApi } from '@/lib/api/client';
import type { Guardrail } from '@/lib/api/types';

export const guardrailKeys = {
all: ['guardrails'] as const,
list: () => [...guardrailKeys.all, 'list'] as const,
detail: (id: string) => [...guardrailKeys.all, 'detail', id] as const,
};

export function useGuardrails() {
const { key, openModal } = useAdminKey();
return useQuery({
queryKey: guardrailKeys.list(),
queryFn: () => guardrailsApi.list(key!),
enabled: !!key,
retry: (count, err) => {
if (err instanceof ApiClientError && err.status === 401) {
openModal();
return false;
}
return count < 2;
},
});
}

export function useGuardrail(id: string) {
const { key, openModal } = useAdminKey();
return useQuery({
queryKey: guardrailKeys.detail(id),
queryFn: () => guardrailsApi.get(key!, id),
enabled: !!key && !!id,
retry: (count, err) => {
if (err instanceof ApiClientError && err.status === 401) {
openModal();
return false;
}
return count < 2;
},
});
}

export function useCreateGuardrail() {
const qc = useQueryClient();
const { key, openModal } = useAdminKey();
return useMutation({
mutationFn: (data: Guardrail) => guardrailsApi.create(key!, data),
onSuccess: () => qc.invalidateQueries({ queryKey: guardrailKeys.list() }),
onError: (err) => {
if (err instanceof ApiClientError && err.status === 401) {
openModal();
}
},
});
}

export function useUpdateGuardrail(id: string) {
const qc = useQueryClient();
const { key, openModal } = useAdminKey();
return useMutation({
mutationFn: (data: Guardrail) => guardrailsApi.update(key!, id, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: guardrailKeys.list() });
qc.invalidateQueries({ queryKey: guardrailKeys.detail(id) });
},
onError: (err) => {
if (err instanceof ApiClientError && err.status === 401) {
openModal();
}
},
});
}

export function useDeleteGuardrail() {
const qc = useQueryClient();
const { key, openModal } = useAdminKey();
return useMutation({
mutationFn: (id: string) => guardrailsApi.delete(key!, id),
onSuccess: () => qc.invalidateQueries({ queryKey: guardrailKeys.list() }),
onError: (err) => {
if (err instanceof ApiClientError && err.status === 401) {
openModal();
}
},
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading