Skip to content

Add stop button to interrupt agent response#41

Merged
MrOrz merged 4 commits into
masterfrom
feat-stop-button-1373008896763086594
May 3, 2026
Merged

Add stop button to interrupt agent response#41
MrOrz merged 4 commits into
masterfrom
feat-stop-button-1373008896763086594

Conversation

@MrOrz

@MrOrz MrOrz commented Apr 29, 2026

Copy link
Copy Markdown
Member

Added a stop button to the chat interface to allow users to interrupt agent responses.

Changes:

  • ChatInput Component: Added isStreaming and onStop props. The send button now switches to a stop button when isStreaming is true. The textarea is disabled during streaming.
  • ChatArea Component: Now accepts an onStop prop and passes it along with isStreaming to ChatInput.
  • Session Page: Extracts stopGeneration from the useChat hook and passes it to ChatArea.
  • run-sse API Route: Replaced the runChat server function (createServerFn) with a plain /api/run-sse proxy route. TanStack Start's serverFnFetcher swallows AbortError inside its internal IIFE, so calling AbortController.abort() on the client never unblocked the for-await loop. A plain API route lets the client use fetch() directly, where reader.read() throws AbortError immediately. request.signal is also forwarded server-side so the upstream ADK connection terminates on abort.

This fulfills the requirement from the 20260428 meeting notes.

Fixes #36

stop-session.mp4

(Speed 1.5x)


PR created automatically by Jules for task 1373008896763086594 started by @MrOrz

@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the ability to stop message generation in the chat interface. It updates the ChatArea and ChatInput components to support an onStop callback and dynamically toggles the input button between 'send' and 'stop' icons based on the streaming state. A review comment points out that disabling the textarea during streaming prevents the 'Enter' key from triggering the stop action; using readOnly instead of disabled is suggested to ensure keyboard events are still captured.

Comment thread src/components/ChatInput.tsx
- Implement 'stop' button in `ChatInput` that appears during streaming
- Disable textarea while streaming to prevent concurrent messages
- Map 'Enter' key to stop generation during streaming
- Connect `stopGeneration` from `useChat` hook to the UI

Co-authored-by: MrOrz <108608+MrOrz@users.noreply.github.com>
@MrOrz MrOrz force-pushed the feat-stop-button-1373008896763086594 branch from fae9893 to d778052 Compare May 2, 2026 11:19
@MrOrz MrOrz force-pushed the feat-stop-button-1373008896763086594 branch from 17f424d to d778052 Compare May 2, 2026 12:15
…tion

Replace runChat server function with a /api/run-sse proxy route so that
AbortController.abort() propagates directly through fetch's reader.read()
instead of being swallowed by serverFnFetcher's internal IIFE error handler.
Server-side request.signal is forwarded to ADK so the upstream connection
also terminates on client abort.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enter key should only submit, not trigger onStop during streaming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MrOrz

MrOrz commented May 2, 2026

Copy link
Copy Markdown
Member Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, comment @claude review on this pull request to trigger a review.

@claude

claude Bot commented May 2, 2026

Copy link
Copy Markdown

Claude finished @MrOrz's task in 3m 23s —— View job


Code Review Complete ✓

  • Gather context and read repository guidelines
  • Review changed files
  • Analyze implementation for bugs, security, and best practices
  • Provide comprehensive review feedback

Overall Assessment

Status: Approved with minor suggestions

This is a well-implemented feature that successfully adds stop button functionality to interrupt agent responses. The core architectural decision to bypass TanStack's serverFnFetcher in favor of direct fetch() calls is sound and well-documented.

Key Strengths

  1. Proper abort signal propagation - The new /api/run-sse.ts route correctly propagates request.signal to the ADK client, ensuring abort calls flow through the entire stack
  2. Race condition handling - Excellent defensive programming in chatCache.ts (lines 135-148) that guards against concurrent streams
  3. Clean separation of concerns - Props flow logically: Session → ChatArea → ChatInput
  4. Good documentation - Clear comments explaining the architectural decision (why fetch() instead of server functions)
  5. Abort controller cleanup - Proper cleanup logic prevents memory leaks

Issues & Suggestions

1. Missing Optional Chaining Safety

File: src/components/ChatInput.tsx:57
Severity: Low

onClick={isStreaming ? onStop : handleSubmit}

While onStop should always be defined when isStreaming is true in practice, this could be more defensive:

onClick={isStreaming ? onStop ?? (() => {}) : handleSubmit}

Or better yet, guard the button state:

onClick={isStreaming && onStop ? onStop : !isStreaming ? handleSubmit : undefined}

2. Accessibility - Missing ARIA Labels

File: src/components/ChatInput.tsx:56-64
Severity: Medium

The button only has a visual icon with no accessible label. Screen reader users won't know what the button does or when its function changes.

Suggestion:

<button
  onClick={isStreaming ? onStop : handleSubmit}
  disabled={(!isStreaming && !value.trim()) || disabled}
  ? '停止生成' : '發送訊息'}
  className="absolute right-2 bottom-2 p-1.5 bg-primary text-black rounded-lg hover:bg-primary-hover transition-colors flex items-center justify-center disabled:opacity-40 disabled:cursor-not-allowed"
>
  <span className="material-symbols-outlined text-sm" aria-hidden="true">
    {isStreaming ? 'stop' : 'send'}
  </span>
</button>

3. Error Message Could Be More Specific

File: src/lib/chatCache.ts:75
Severity: Low

throw new Error(`ADK request failed: HTTP ${response.status}`)

Consider including response.statusText for better debugging:

throw new Error(`ADK request failed: HTTP ${response.status} ${response.statusText}`)

4. Potential Edge Case - Reader Already Consumed

File: src/lib/chatCache.ts:82
Severity: Low

const reader = response.body!.getReader()

The non-null assertion assumes response.body exists. While this should be true for successful SSE responses, adding a guard would be safer:

if (!response.body) {
  throw new Error('ADK response missing body')
}
const reader = response.body.getReader()

Code Quality Notes

Positive Patterns

  • ✅ Proper cleanup in finally blocks
  • ✅ Controller deduplication via abortControllers Map
  • ✅ Idempotent reader.cancel() call
  • ✅ Good use of useCallback for stable references
  • ✅ Disabled textarea prevents confusing UX during streaming

Fixed Issues

The Enter key handler concern from the initial review has been properly addressed in commit 0627a6d - the unreachable streaming check was correctly removed.

Security & Performance

Security: ✅ No issues found

  • User input properly handled through React
  • Same-origin API calls
  • No injection vectors identified

Performance: ✅ Improved

  • Direct fetch() eliminates intermediate buffering
  • Proper stream cleanup prevents resource leaks
  • Abort controller prevents duplicate streams (DoS protection)

Testing Recommendations

Consider testing these edge cases:

  1. Rapid clicking of stop button
  2. Sending new message while previous is still aborting
  3. Network failure during streaming
  4. Browser tab backgrounding during long streams

Summary

This PR successfully implements the stop button feature with a well-reasoned architectural approach. The abort signal propagation issue with TanStack's RPC layer is correctly identified and solved. The suggestions above are minor improvements for robustness and accessibility, but the core implementation is solid.

Recommendation: Approve and merge (address accessibility issue if possible)


@MrOrz MrOrz marked this pull request as ready for review May 2, 2026 17:41

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@MrOrz MrOrz requested review from bil4444, nonumpa and yutin1987 May 2, 2026 17:41
@MrOrz MrOrz merged commit 95fa3f2 into master May 3, 2026
6 checks passed
@MrOrz MrOrz deleted the feat-stop-button-1373008896763086594 branch May 3, 2026 14:46
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: 新增停止 agent 回應的按鈕

2 participants