Skip to content
Merged
20 changes: 20 additions & 0 deletions doc/gui/0_gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,26 @@ Browse datasets already loaded into backend memory. Open a dataset to inspect
its examples, search text, or filter by seed type. Supported media can be
previewed on request.

Use **Import into Chat** to stage an example directly into Chat without copying
text or reattaching files:
- **Single prompt or multimodal message**: loads the text and attachments into the
editable composer.
- **Multi-turn conversation**: opens the conversation editor with all turns intact,
mapping assistant and tool messages to simulated roles. If the conversation ends
in a user turn, that turn is also visible and editable in the editor. Saving
stores the preceding history and restores the edited final turn in the prompt box,
ready for an explicit send.
- **Import destinations**: choose between starting a new attack, continuing the
current conversation, or creating a new conversation within the current attack.
- **Preserve evidence**: multi-turn imports can extend an unsent conversation in
place. For a conversation containing sent responses or request traces, choose a
new conversation or new attack instead. Conflicting system or developer instructions
require an explicit choice; they are never silently combined.
- **Keep setup**: replacing a prompt retains the destination's system prompt and
configured converter pipelines, but clears generated conversion results.
- **No automatic sending**: importing and saving do not make model calls or execute
tools; edits remain under your control until you explicitly send or save.

Load data through PyRIT; the visible catalog checks for changes each minute. See
[datasets](../code/datasets/0_dataset.md) for loading options.

Expand Down
46 changes: 46 additions & 0 deletions frontend/e2e/datasets.seeded.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,52 @@ test.describe("dataset examples against the real backend @seeded", () => {
await waitForBackend(request);
});

for (const entry of ["chat", "dataset"]) {
test(`imports a template from ${entry} into the visible prompt box without sending @seeded`, async ({ page, request }) => {
test.setTimeout(90_000);
const headers = compatibilityHeaders();
const list = await request.get(
`/api/datasets/seeds?selection_key=${encodeURIComponent(SELECTION_KEY)}&limit=20`,
{ headers },
);
expect(list.ok(), await list.text()).toBe(true);
const examples = await list.json() as SeedExamplePage;
const selected = examples.items.find((example: SeedExampleSummary) => example.name === "wikipedia with title");
if (!selected) throw new Error("Expected the bundled wikipedia with title template");
const response = await request.get(
`/api/datasets/seeds/${selected.example_id}?selection_key=${encodeURIComponent(SELECTION_KEY)}`,
{ headers },
);
expect(response.ok(), await response.text()).toBe(true);
const detail = await response.json() as SeedExampleDetail;
const prompt = detail.members.find((member: SeedMember) => member.data_type === "text");
if (!prompt) throw new Error("Expected template text");
const mutations: string[] = [];
page.on("request", (outgoing) => {
if (outgoing.method() === "POST") mutations.push(new URL(outgoing.url()).pathname);
});

if (entry === "chat") {
await page.goto("/attack/chat");
await expect(page.getByPlaceholder("Type prompt here")).toBeEnabled();
await page.getByRole("button", { name: "Datasets", exact: true }).click();
await page.getByRole("link", { name: "jailbreak_templates", exact: true }).click();
} else {
await page.goto(`/datasets/detail?selection_key=${encodeURIComponent(SELECTION_KEY)}`);
}
await page.getByRole("button", { name: "Show wikipedia with title", exact: true }).click();
await page.getByTestId("import-into-chat-button").click();
await page.getByTestId("import-new-attack-item").click();
await expect(page).toHaveURL(/\/attack\/chat$/);
const composer = page.getByPlaceholder("Type prompt here");
await expect(composer).toBeVisible();
await expect(composer).toHaveValue(prompt.value);
await composer.fill("Edited imported template");
await expect(composer).toHaveValue("Edited imported template");
expect(mutations.filter((path: string) => path.startsWith("/api/attacks"))).toEqual([]);
});
}

test("lists stored examples and opens one member @seeded", async ({ page, request }) => {
test.setTimeout(90_000);
const headers = compatibilityHeaders();
Expand Down
Loading
Loading