Chat · Components

ChatInterruptPanelComponent

ChatInterruptPanelComponent is a composition that renders a styled interrupt card when the LangGraph agent pauses for human input. It displays the interrupt payload and provides action buttons for the user to accept, edit, respond to, or ignore the interrupt.

Selector: chat-interrupt-panel

Import:

import { ChatInterruptPanelComponent } from '@threadplane/chat';
import type { InterruptAction } from '@threadplane/chat';

#How It Works

LangGraph agents can pause execution using interrupts -- checkpoints where the agent waits for human input before proceeding. The ChatInterruptPanelComponent reads the interrupt state from an Agent and renders a warning card with action buttons.

When no interrupt is active, the component renders nothing.

#Basic Usage

<chat-interrupt-panel
  [agent]="chatRef"
  (action)="handleInterrupt($event)"
/>
import type { InterruptAction } from '@threadplane/chat';
 
handleInterrupt(action: InterruptAction) {
  switch (action) {
    case 'accept':
      this.chatRef.submit({ resume: true });
      break;
    case 'edit':
      this.openEditor();
      break;
    case 'respond':
      this.focusInput();
      break;
    case 'ignore':
      // Do nothing, dismiss the panel
      break;
  }
}
The panel doesn't resume the agent for you

chat-interrupt-panel only emits which button the user clicked — it never calls agent.submit() itself. You own resumption. openEditor() and focusInput() above are your own helpers (collect an edited proposal or a typed reply); once you have the value, send it back with agent.submit({ resume }). The shape of resume is whatever your graph's interrupt handler expects.

// After the user edits the agent's proposal:
async resumeWithEdit(edited: Record<string, unknown>) {
  await this.chatRef.submit({ resume: { action: 'edit', data: edited } });
}
 
// After the user types a free-text reply:
async resumeWithResponse(text: string) {
  await this.chatRef.submit({ resume: { action: 'respond', text } });
}

#API

#Inputs

InputTypeDefaultDescription
agentAgentRequiredThe agent providing interrupt state

#Outputs

OutputTypeDescription
actionInterruptActionEmits when the user clicks an action button

#InterruptAction Type

type InterruptAction = 'accept' | 'edit' | 'respond' | 'ignore';
ActionButton LabelTypical Use
'accept'AcceptApprove the agent's proposed action and resume execution
'edit'EditOpen an editor to modify the agent's proposal before resuming
'respond'RespondSend a text response back to the agent
'ignore'IgnoreDismiss the interrupt without taking action

#Interrupt Payload Display

The component extracts the interrupt payload and displays it as text:

  • If the interrupt value is a string, it is displayed directly
  • If the interrupt value is an object, it is serialized with JSON.stringify()

#Styling

The panel uses the chat theme's warning variables:

VariableApplied To
--ngaf-chat-warning-bgPanel background
--ngaf-chat-warning-textHeader and message text
--ngaf-chat-separatorPanel border
--ngaf-chat-radius-cardPanel border radius
--ngaf-chat-surface-altAction button backgrounds
--ngaf-chat-textAction button text
--ngaf-chat-text-mutedIgnore button text

#Primitive Alternative: ChatInterruptComponent

If you need full control over the interrupt UI, use the lower-level ChatInterruptComponent primitive instead. It provides content projection via an ng-template:

<chat-interrupt [agent]="chatRef">
  <ng-template let-interrupt>
    <div class="my-custom-interrupt">
      <p>Agent paused: {{ interrupt.value }}</p>
      <button (click)="resume()">Continue</button>
    </div>
  </ng-template>
</chat-interrupt>

The primitive also exports a getInterrupt() helper function:

import { getInterrupt } from '@threadplane/chat';
 
const interrupt = getInterrupt(chatRef); // Interrupt<any> | undefined

#Full Example

import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
import { injectAgent, provideAgent } from '@threadplane/langgraph';
import { ChatComponent, ChatInterruptPanelComponent } from '@threadplane/chat';
import type { InterruptAction } from '@threadplane/chat';
 
@Component({
  selector: 'app-interrupt-demo',
  standalone: true,
  imports: [ChatComponent, ChatInterruptPanelComponent],
  providers: [provideAgent({ assistantId: 'interrupt_agent', threadId: signal(null) })],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div style="height: 100vh; display: flex; flex-direction: column;">
      <div style="flex: 1;">
        <chat [agent]="chatRef" />
      </div>
 
      <chat-interrupt-panel
        [agent]="chatRef"
        (action)="onInterruptAction($event)"
      />
    </div>
  `,
})
export class InterruptDemoComponent {
  chatRef = injectAgent();
 
  onInterruptAction(action: InterruptAction) {
    if (action === 'accept') {
      this.chatRef.submit({ resume: true });
    }
  }
}