Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ describe('GuardrailChecksTab', () => {
await user.type(
await screen.findByRole(
'textbox',
{ name: 'General instruction' },
{ name: 'General Instructions' },
{ timeout: XL_SELECTOR_TIMEOUT }
),
'Be extremely cautious.'
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { RailsConfig } from '@nemo/sdk/generated/platform/schema';
import { TooltipProvider } from '@nvidia/foundations-react-core';
import type { Meta, StoryObj } from '@storybook/react';
import { GuardrailConfigurationPanel } from '@studio/routes/guardrails/GuardrailConfigTab/GuardrailConfigurationPanel';
import {
type GuardrailFormValues,
mapConfigToForm,
} from '@studio/routes/guardrails/GuardrailForm/formModel';
import type { FC } from 'react';
import { FormProvider, useForm } from 'react-hook-form';

/**
* Renders the panel against a real form, so the switches and the settings side panel
* behave exactly as they do in the app — without needing a running platform.
*/
const WithForm: FC<{ config?: RailsConfig }> = ({ config }) => {
const form = useForm<GuardrailFormValues>({ defaultValues: mapConfigToForm(config) });
return (
<TooltipProvider>
<FormProvider {...form}>
<div className="max-w-[1100px] p-density-2xl">
<GuardrailConfigurationPanel />
</div>
</FormProvider>
</TooltipProvider>
);
};

const meta = {
title: 'Guardrails/GuardrailConfigurationPanel',
component: WithForm,
} satisfies Meta<typeof WithForm>;

export default meta;

type Story = StoryObj<typeof meta>;

/** A brand new guardrail: every rail off, nothing configured yet. */
export const Empty: Story = {
args: {},
};

/** Self check running on both stages, with the instructions a real config carries. */
export const SelfCheckEnabled: Story = {
args: {
config: {
instructions: [
{
type: 'general',
content:
'Below is a conversation between a user and a bot called the ABC Bot. The bot is designed to answer employee questions about the ABC Company. The bot is knowledgeable about the employee handbook and company policies. If the bot does not know the answer to a question, it truthfully says it does not know.',
},
],
rails: {
input: { flows: ['self check input'] },
output: { flows: ['self check output'] },
},
prompts: [
{ task: 'self_check_input', content: 'Should the user message be blocked?' },
{ task: 'self_check_output', content: 'Should the bot message be blocked?' },
],
},
},
};

/**
* Switched off but still holding prompts, which is when the list offers to discard them.
*/
export const DisabledWithStoredSettings: Story = {
args: {
config: {
prompts: [{ task: 'self_check_input', content: 'My tuned policy.' }],
},
},
};

/**
* A config whose other rails Studio cannot configure yet. They stay in the saved document
* untouched and remain visible in the read-only sections further down the tab.
*/
export const AlongsideUnsupportedRails: Story = {
args: {
config: {
models: [
{ type: 'content_safety', engine: 'nim', model: 'system/nemoguard-8b-content-safety' },
],
rails: {
input: {
flows: ['content safety check input $model=content_safety', 'self check input'],
},
config: { gliner: { server_endpoint: 'http://gliner.local' } },
},
prompts: [
{ task: 'self_check_input', content: 'Should the user message be blocked?' },
{
task: 'content_safety_check_input $model=content_safety',
content: 'Check for unsafe content.',
output_parser: 'nemoguard_parse_prompt_safety',
},
],
},
},
};
Loading
Loading