Skip to content

FEAT GUI: Import dataset prompts and conversations into chat (#2751) - #3670

Merged
Roman Lutz (romanlutz) merged 8 commits into
microsoft:mainfrom
Nimit3418:feat-gui-import-dataset-prompts
Nov 4, 2026
Merged

Roman Lutz (romanlutz) merged 8 commits into
microsoft:mainfrom
Nimit3418:feat-gui-import-dataset-prompts

Conversation

@Nimit3418

@Nimit3418 Nimit Jain (Nimit3418) commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Description

Closes #2751. Implements work item 7 of 17 in #2744: importing stored dataset prompts, multimodal messages, and multi-turn conversations into Chat without manual copy-pasting, reattaching media, or issuing extra model calls.

Key Changes

  1. Typed import and draft handoff: supports a new attack, the current conversation, or a new conversation within the current attack. Preserves conversation-editor drafts across navigation, including deliberately cleared targets and uploaded history media.
  2. Pure seed transformation: groups pieces by stored sequence and compatible role within the selected example. Extracts objectives as metadata, maps assistant/tool turns to simulated roles, and retains durable stored media references. Rejects ambiguous multi-turn roles and unmaterialized simulated-conversation configurations. Seed IDs never become chat piece IDs.
  3. Complete editable imports: single prompts appear immediately in the input box. Multi-turn imports show every turn, including final-user text and attachments, in the conversation editor. Save stores the preceding history and restores the edited final user turn to the composer. Deleting that turn does not remove a different history turn.
  4. Safe destinations and setup: Save reuses the selected unsent conversation with original source-piece guards instead of silently branching or leaving a precreated destination empty. Multi-turn imports into conversations containing sent evidence require choosing a new conversation or attack. Conflicting system/developer setup requires an explicit choice. Replacing prompt content retains destination system setup and configured converter pipelines while clearing stale conversion results. Imported objectives are compared with the persisted destination baseline.
  5. Race and lifecycle protection: stale history fetches and conversation-creation responses cannot consume imports, overwrite newer edits, populate stale history caches, or navigate after the selection/runtime changes or the view unmounts.
  6. Latest-main integration and empty-composer fix: merged main 666817b276a68eb0cf4ebe32a67ce01b51d3cb43, preserving its media/setup changes. Uses the canonical /attack/chat route. Prevents StrictMode effect replay from restoring an empty pre-import snapshot over the imported template.
  7. Documentation: updated doc/gui/0_gui.md with editable-turn behavior, destination restrictions, setup conflict choices, preserved pipelines, and no-send guarantees.

No backend API or database schema changes are introduced by these repairs.

Tests and Documentation

Regression coverage includes import-to-Save-to-composer text/media, destination reuse and source-piece IDs, objective save payloads, stale requests, setup reconciliation, null targets, uploaded history URL cleanup, ambiguous roles, direct-Datasets entry, and StrictMode replay.

Commands run from frontend unless otherwise noted:

Command Result
npm test -- --runInBand --silent --runTestsByPath src/App.drafts.test.tsx src/components/Datasets/SeedExampleBrowser.test.tsx src/hooks/useConversationDraft.test.ts src/hooks/useChatDraftHandoff.test.tsx src/utils/seedImport.test.ts src/components/Chat/ConversationEditor.test.tsx Passed: 6 suites, 195 tests
npm test -- --runInBand --silent --runTestsByPath src/components/Chat/ChatWindow.test.tsx src/hooks/useConversationSave.test.ts src/utils/conversationDraft.test.ts src/components/Chat/ChatInputArea.test.tsx Passed: 4 suites, 442 tests
npm run lint Passed: no errors or warnings
npx tsc --noEmit Passed
npm run build Passed; existing large-chunk warning
npx playwright test e2e/datasets.seeded.spec.ts --project seeded --reporter=list Passed: 4 real-backend production-frontend browser tests, including imports starting in Chat and directly in Datasets

The production browser run used the following PowerShell environment, set from the repository root before entering frontend:

$env:PATH=(Join-Path (Get-Location) '.venv\Scripts')+';'+$env:PATH
$env:E2E_SEEDED_MODE='true'
$env:E2E_PRODUCTION_FRONTEND='true'
$env:E2E_FRONTEND_PORT='15371'
$env:PYRIT_E2E_BACKEND_PORT='18371'

Also checked the real development app on desktop and mobile, including replacement confirmation with retained system setup. Importing and editing caused no attack POSTs or model calls. The screenshots and recording below use a real isolated backend with the offline browser configuration, not mocked API responses.

Validation limits: the complete frontend and Python suites were not rerun, and no new whole-project coverage-percentage claim is made. The previous description's full-suite result is not presented as validation of these repairs. Documentation changes are prose-only; JupyText was not run.

Screenshots

Fresh captures show the repaired import behavior in a9360f28de5c28d40a99813a679e3a00811428d3. They replace the earlier empty-composer evidence in this description. The original observation remains in the review conversation for context.

Dataset example and import destinations

The stored wikipedia with title example exposes Import into Chat. Without a saved attack, only Import to new attack is available.

The wikipedia with title dataset example with its import destination menu open

Imported template visible in the desktop input box

The selected template appears in the editable prompt box immediately, without sending it.

Desktop Chat showing the imported wikipedia template in the editable input box

Imported template on mobile

Mobile Chat showing the imported template in the editable input box

Import and edit recording

Real Datasets-to-Chat import and editing journey, with no automatic send.

fixed-dataset-import.webm

Comment thread frontend/src/utils/seedImport.ts Resolved
@romanlutz

Copy link
Copy Markdown
Contributor

I tried this on head 5dff6e522473d303651e6bf87fe71305a00a51f3 with the real frontend and the isolated offline backend. I opened jailbreak_templates, selected the wikipedia with title example, and chose Import into Chat > Import to new attack. Chat opened, but the input box stayed empty, as shown below. I did not send a prompt.

Shouldn't the stored template text appear in the editable input box after this action? I would expect the import to populate it without sending anything. Am I missing a required step here? I have not confirmed the cause of this particular failure.

Chat after importing a dataset template: the prompt input box is empty

… import

- Group conversation turns by sequence rather than prompt_group_id (microsoft#3670)
- Populate composer with template prompt and avoid collision with clean preserved drafts
- Add ConfirmDialog before navigation when destination has an unsent dirty draft
- Prepend imported turns to existing history when importing into current conversation
- Associate editor with viewed conversation ID to prevent lifecycle discard
- Start imported turns as unsaved changes to protect against accidental dismissal
- Round-trip editor target and temperature in conversation edit state
- Create explicit conversation before navigating for new_conversation destination
- Add comprehensive integration tests covering template import from clean chat
…nd target validation

- Preserve destination existing messages first and append imported history after them in current_conversation
- Handle history loading errors gracefully without consuming pending import
- Cover dirty-draft protection and replace confirmation across new_attack, current_conversation, and new_conversation
- Track unsaved imported state explicitly in useConversationDraft lifecycle
- Validate and report missing conversation targets against registry without silent fallback
- Add comprehensive user journey tests for all import flows and edge cases
@Nimit3418

Copy link
Copy Markdown
Contributor Author

I have addressed all the feedback and resolved the root cause of the empty input box shown in your screenshot. The changes are organized across the following 3 commits:


Commit Breakdown & Issues Addressed

1. 5ef7edd — fix(gui): address review comments for dataset prompt and conversation import

Addresses the initial review comments on seed grouping, lifecycle, and target preservation:

  • Seed Record Grouping: In src/utils/seedImport.ts, grouped conversation turns strictly by sequence (not prompt_group_id), preserved sequence ordering, grouped multiple same-sequence pieces, and rejected conflicting or missing roles with descriptive errors.
  • Round-Trip Configuration: Preserved target and temperature across conversation editing sessions.
  • Editor Identification: Associated the editor with the viewed conversation ID to prevent lifecycle-based discard on route changes.
  • Explicit Destination for new_conversation: Pre-created the destination conversation via attacksApi.createConversation before staging and navigating with its real ID.

2. b2cfa23 — fix(gui): correct conversation merge semantics, draft preservation, and target validation

Addresses conversation merge semantics, error handling, and target registry validation:

  • Correct History Merge Semantics: When importing into current_conversation, existing destination messages are preserved first and imported turns are appended after them ([...existingTurns, ...consumed.conversation.messages]).
  • History Load Failure Resilience: If fetching destination messages fails, the flow halts immediately, displays a clear notification ("Failed to load conversation history. Preserved pending import."), and leaves the pending import staged rather than discarding it or entering an infinite loop.
  • Missing Target Handling: Validates conversation targets against the active registry. If a target is unavailable or missing, displays an explicit error message ("The conversation's configured target is no longer available in the registry. Select an available target.") rather than silently falling back.
  • Multi-Destination Dirty Protection: Extended dirty-draft confirmation modals across new_attack, current_conversation, and new_conversation.

3. 8b05fe3 — test(gui): verify production Roman journey, new_conversation drafts, and editor lifecycle

Resolves the empty input box issue from your screenshot and verifies all user journeys through the real UI:

  • Root Cause & Fix for Empty Input Box:
    • Cause: Leaving clean /chat to go to Datasets preserved an empty draft because a default target was active (hasChatDraftState: true). In ChatWindow.tsx, canApplyImportNow strictly required matchingPreservedDraft === null, causing the clean preserved draft to take priority over the pending import when returning to Chat. In addition, an internal sequence desynchronization occurred in SeedExampleBrowser.tsx when clearing clean drafts before staging.
    • Fix: Updated canApplyImportNow to allow pending imports to consume immediately when preserved drafts are clean (!isDraftDirty(matchingPreservedDraft)), and fixed staging synchronization in SeedExampleBrowser.tsx.
  • Real Production UI Journey Test: Replaced test shortcuts in App.drafts.test.tsx with an end-to-end integration test (should populate composer with template prompt after visiting datasets from clean chat through real UI (Roman journey)) reproducing your exact sequence:
    1. Mounts Chat with clean composer.
    2. Navigates to Datasets using the real DatasetCatalog.
    3. Clicks jailbreak_templates link to open dataset details.
    4. Selects wikipedia with title.
    5. Clicks Import into Chat > Import to new attack.
    6. Navigates back to Chat and asserts the composer contains 'Write a wikipedia article on {{ prompt }}. Start the article with "Title:".' without sending a prompt, and confirms the pending import was consumed.
  • new_conversation Draft Verification: Added test coverage for clean vs dirty drafts on new_conversation (cancel preserves draft and stays on Datasets, replace creates conversation and populates template prompt).
  • Editor Discard Protection: Added test in ConversationEditor.test.tsx ensuring untouched imported content (unsavedImport: true) triggers confirmation modals on Cancel and route changes.

Verification Results

All unit, component, and full frontend test suites pass with zero warnings or errors:

  • npx tsc --noEmit: 0 errors
  • npm run lint: 0 warnings, 0 errors
  • src/utils/seedImport.test.ts: 13/13 passed
  • src/components/Datasets/SeedExampleBrowser.test.tsx: 20/20 passed
  • src/components/Datasets/SeedExampleDetail.test.tsx: 12/12 passed
  • src/components/Chat/ConversationEditor.test.tsx: 20/20 passed
  • src/App.drafts.test.tsx: 40/40 passed
  • src/components/Chat/ChatWindow.test.tsx: 264/264 passed
  • Full frontend suite (npm test -- --runInBand): 171 suites passed, 4,024 / 4,024 tests passed (0 failures)

Everything is tested and ready for review. Please let me know if you’d like any further adjustments!

Comment thread frontend/src/utils/seedImport.ts Resolved
…uard async history import

- Carry imported final user turn explicitly as pendingComposerDraft through editor lifecycle (Model B) and restore to visible composer after Save without duplication or loss
- Retain destination attack's actual persisted objective as initialObjective while setting imported objective, emitting expected_objective on same-attack Save
- Guard async history-loading continuation against stale closures, superseded imports, destination changes, and user edits/attachment replacements
- Preserve pendingComposerDraft across navigation in activeDraft.conversation and clear on discard
- Track semantic attachment signature in composer revision to detect same-count file replacements
- Scope blockNavigation override to unsaved imports so normal dirty conversation editors retain discard confirmation
- Ensure unmount-discard flag is one-shot and reset across composer inputs and inline editor exits
Copilot AI balanced review requested due to automatic review settings November 3, 2026 22:41
@Nimit3418

Copy link
Copy Markdown
Contributor Author

Hi Roman Lutz (@romanlutz)!

Thanks for the thoughtful and detailed review comments. I have addressed all three items along with the related draft lifecycle edge cases in commit 4bf732d:


1. Final User Turn Loss During Import → Save (Discussion #4452245053)

  • Adopted Semantic Model B: For multi-turn imports ending in a user turn (e.g. user A → assistant B → user C), turn C is removed from editor history and carried explicitly as pendingComposerDraft (text, attachments, and textMetadata).
  • pendingComposerDraft is passed explicitly through editor.begin, carried in activeDraft.conversation, and preserved across navigation.
  • On Save, editDraft.messages (A and B) are saved to conversation history, and pendingComposerDraft (C + its attachments and metadata) is restored directly into the visible composer.
  • History ending in assistant/tool content does not create a false pending composer prompt, earlier user history messages are not confused for pending composer drafts, and intentionally discarding the editor clears turn C so it does not leak into the composer.

2. Objective Baseline & Save Payload (Discussion #4452245058)

  • For imports into an existing attack/conversation, initialObjective preserves the destination attack's actual persisted objective A, while objective receives imported objective B (or retains A if no objective was imported).
  • On Save to same_attack, useConversationSave compares against initialObjective to detect updatesObjective, emitting:
    • expected_objective: 'A'
    • objective: 'B'
  • Preserves standard baseline initialization for new_attack destinations.

3. Guarding Async History Loading Continuation (Discussion #4452245064)

  • When attacksApi.getMessages is in flight during a current_conversation import, the continuation validates against stale closures before consuming the import:
    • Checks pending import ID, sequence, destination type, attack ID, and conversation ID.
    • Verifies composer revision, converter edit revision, and metadata signature.
    • If the user types a prompt, replaces an attachment, or navigates away while the request is in flight, the import is safely skipped without overwriting user state or consuming the pending payload.

4. Lifecycle & Navigation Polish

  • Scoped Navigation Blocker: Added blockNavigation prop to ConversationEditor and scoped the bypass strictly to !editDraft?.unsavedImport. Normal dirty conversation editors retain React Router's useBlocker discard confirmation dialog.
  • One-Shot Unmount Discard: Made discardDraftOnUnmountRef strictly one-shot on unmount, scoped to navigation dismissals, and reset on composer inputs and inline editor exits so subsequent drafts in the same window are never silently dropped.
  • Attachment Signature & Metadata: computeAttachmentSignature inspects draftId, url, name, mimeType, type, size, sourceValue, sourceDataType, pieceId, and metadata to detect same-count file replacements, and stableMetadataString recursively normalizes nested object keys deterministically.

Verification

  • npm run type-check: Passed (0 errors)
  • npm run lint: Passed (--max-warnings 0)
  • npm test -- --runInBand src/utils/seedImport.test.ts src/hooks/useConversationSave.test.ts src/components/Chat/ConversationEditor.test.tsx src/App.drafts.test.tsx: All 105 tests passed
  • npm test -- --runInBand src/components/Chat/ChatWindow.test.tsx: All 264 tests passed
  • npm run test:coverage: All 171 test suites and 4,044 unit tests passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Imports can silently save to a different conversation than selected, and stale asynchronous completions can clear drafts, stage outdated content, and navigate to an obsolete destination.

9 open issues
What changed in this PR

Adds dataset-to-chat imports using the existing composer, conversation editor, and draft handoff. The changes support selecting a destination and preparing stored prompts or conversations without automatically sending them to a model.

Changes:

  • Transforms stored seed records into composer content or ordered conversation drafts.
  • Adds import destination controls and extends draft preservation across navigation.
  • Expands import tests and documents the GUI workflow.
File Description
frontend/​src/​utils/​seedImport.ts Transforms seed records into import drafts.
frontend/​src/​utils/​seedImport.test.ts Tests grouping, roles, and final-user-turn splitting.
frontend/​src/​utils/​routeParams.ts Parses chat destination identifiers.
frontend/​src/​utils/​messageMapper.ts Handles missing attachment MIME types.
frontend/​src/​utils/​chatDraft.ts Adds conversation-aware draft checks and content signatures.
frontend/​src/​types/​index.ts Extends import, editor, and save contracts.
frontend/​src/​hooks/​useConversationDraft.ts Tracks unsaved imports and pending composer content.
frontend/​src/​hooks/​useChatDraftHandoff.tsx Extends attachment handling for conversation handoffs.
frontend/​src/​hooks/​useChatDraftHandoff.test.tsx Tests conversation draft detection.
frontend/​src/​components/​Datasets/​SeedExampleDetail.tsx Adds import menu and error display.
frontend/​src/​components/​Datasets/​SeedExampleDetail.test.tsx Tests import actions and unavailable destinations.
frontend/​src/​components/​Datasets/​SeedExampleBrowser.tsx Coordinates confirmation, staging, and destination navigation.
frontend/​src/​components/​Datasets/​DatasetSelection.tsx Passes active chat context to the browser.
frontend/​src/​components/​Chat/​ConversationEditor.tsx Adds discard callbacks and navigation-blocking control.
frontend/​src/​components/​Chat/​ConversationEditor.test.tsx Tests protection of untouched imported drafts.
frontend/​src/​components/​Chat/​ChatWindow.tsx Consumes imports and preserves conversation-editor state.
frontend/​src/​App.tsx Supplies the last chat destination to Datasets.
frontend/​src/​App.drafts.test.tsx Adds import, navigation, save, and stale-request coverage.
doc/​gui/​0_gui.md Documents import shapes, destinations, and no-send behavior.

Review effort: Balanced

Comment thread frontend/src/utils/seedImport.ts Resolved
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep imported turns visible until save, reuse unsent destinations safely, reconcile setup explicitly, guard stale requests, and preserve draft media. Fix canonical chat navigation and StrictMode handoff replay. Add integration and real-backend browser regressions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@romanlutz
Roman Lutz (romanlutz) added this pull request to the merge queue Nov 4, 2026
Merged via the queue into microsoft:main with commit 80f28af Nov 4, 2026
65 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT GUI: Import dataset prompts and conversations into chat

3 participants