Skip to content

feat(react-dogfood): VisionAgents integration#2222

Merged
oliverlaz merged 11 commits intomainfrom
worktree-vision-ai
May 5, 2026
Merged

feat(react-dogfood): VisionAgents integration#2222
oliverlaz merged 11 commits intomainfrom
worktree-vision-ai

Conversation

@oliverlaz
Copy link
Copy Markdown
Member

@oliverlaz oliverlaz commented Apr 28, 2026

💡 Overview

Adds a one-click Vision Agents flow to the React dogfood sample app:

  • Ask AI Agent / Remove Agent button in the bottom call-controls bar (between record and leave). A pill with a sparkle icon. Clicking it POSTs to https://api.demo.visionagents.ai/calls/{call_id}/sessions to invite a vision agent. Once a participant whose userId starts with agent is detected, the same button flips to Remove Agent, switches to a red→purple gradient, and on click DELETEs the session (DELETE /calls/{call_id}/sessions/{session_id}) with a call.kickUser fallback when the session id is unknown (e.g. after a page refresh) or DELETE fails. Shows Inviting… / Removing… while in flight. The pill has a fixed min-width so it does not jump as labels change, and the gradient does not move on hover.
  • Floating status panel in the bottom-right of the call stage. Appears whenever the agent participant is present. Shows a green dot, an AI agent in call label, a dismissible close button, a Build your own AI Agent link to https://getstream.io/vision-agents/, and a Powered by Vision Agents footer. Re-appears automatically when a new agent session arrives.

📝 Implementation notes

  • New components: sample-apps/react/react-dogfood/components/VisionAgent/{AskAIAgentButton,AIAgentStatusPanel,index}.tsx.
  • Session id is owned by ActiveCall.tsx and passed to the button (used for the DELETE call). The panel is propless and detects the agent on its own via useParticipants().
  • Failures on invite/remove are logged via console.error; no toast surface.
  • Replaces the v1 header pill (InviteVisionAgentButton) shipped earlier on this branch — the v1 file, its SCSS block, and its translation key are removed.

🎫 Ticket: https://linear.app/stream/issue/AI-557/add-a-button-to-invite-an-agent-to-a-demo-call

Summary by CodeRabbit

  • New Features

    • Added AI Vision Agent integration for inviting and removing agents during active calls.
    • Introduced agent status panel displaying "Powered by Vision Agents" indicator during call sessions.
    • Added localized UI prompts for agent management actions.
  • Style

    • Implemented styling for agent controls and status display.

…eader

Adds a "Vision Agent" pill in the active call header that POSTs to
api.demo.visionagents.ai to invite a vision agent into the current call.
Disabled while in flight; logs failures to the console.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 28, 2026

⚠️ No Changeset found

Latest commit: a3bbd85

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@oliverlaz has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 57 minutes and 6 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 499056ae-b07f-49de-929a-600e1bebb9c0

📥 Commits

Reviewing files that changed from the base of the PR and between 3988b5e and a3bbd85.

📒 Files selected for processing (2)
  • sample-apps/react/react-dogfood/components/ActiveCall.tsx
  • sample-apps/react/react-dogfood/style/VisionAgent/index.scss
📝 Walkthrough

Walkthrough

Adds Vision Agent UI to the React dogfood app: a call-aware AskAIAgentButton that invites/removes vision agents via an external API, a dismissible AIAgentStatusPanel derived from call participants, wiring into ActiveCall, new styles, and translations.

Changes

AI Vision Agent Integration

Layer / File(s) Summary
Localization / Data
sample-apps/react/react-dogfood/translations/en.json
Adds translation keys for agent actions, capability labels, and status messages (e.g., "Ask AI Agent", "Remove Agent", "Inviting…", "Removing…", "Powered by Vision Agents").
Core UI: Invite/Remove Logic
sample-apps/react/react-dogfood/components/VisionAgent/AskAIAgentButton.tsx
Replaces simple UI props with call/session-aware behavior: reads current call and participants, POSTs to https://api.demo.visionagents.ai/calls/{id}/sessions to invite (returns session_id), DELETEs /sessions/{sessionId} to remove (falls back to call.kickUser on failure), manages busy state, and emits onSessionCreated / onSessionCleared callbacks.
Core UI: Status Panel
sample-apps/react/react-dogfood/components/VisionAgent/AIAgentStatusPanel.tsx
Prop-less component deriving agent presence from call participants (userId startsWith agent), provides dismissible status UI and external build link; resets dismissed state when agent session changes.
Wiring / Integration
sample-apps/react/react-dogfood/components/ActiveCall.tsx
Imports and renders AskAIAgentButton and AIAgentStatusPanel in desktop call controls, introduces local agentSessionId state, wires onSessionCreated and onSessionCleared to manage that state, and removes the previous CancelCallConfirmButton from that controls group.
Exports
sample-apps/react/react-dogfood/components/VisionAgent/index.ts
Adds barrel exports for AskAIAgentButton and AIAgentStatusPanel.
Styling
sample-apps/react/react-dogfood/style/VisionAgent/index.scss, sample-apps/react/react-dogfood/style/index.scss
New SCSS variables ($ai-gradient, $ai-remove-gradient) and classes (.rd__ask-ai-agent, .rd__ai-agent-status, .rd__ai-agent-anchor); central stylesheet imports the VisionAgent module.

Sequence Diagram

sequenceDiagram
    participant User
    participant AskAIAgentButton
    participant CallState as Call State
    participant VisionAPI as Vision Agents API
    participant ActiveCall
    participant AIAgentStatusPanel
    participant UI

    User->>AskAIAgentButton: Click
    AskAIAgentButton->>CallState: Read current call & participants
    alt Agent not present
        AskAIAgentButton->>VisionAPI: POST /calls/{id}/sessions
        VisionAPI-->>AskAIAgentButton: { session_id }
        AskAIAgentButton->>ActiveCall: onSessionCreated(session_id)
    else Agent present
        AskAIAgentButton->>VisionAPI: DELETE /sessions/{sessionId}
        Note right of AskAIAgentButton: on failure, call.kickUser fallback
        AskAIAgentButton->>ActiveCall: onSessionCleared()
    end
    ActiveCall->>AIAgentStatusPanel: update (agent presence)
    AIAgentStatusPanel->>UI: render status or null
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • jdimovska

"i hopped in with a sparkle, a button, a cheer,
I summoned a helper to bring vision near,
A panel that whispers the agent is here,
Together we call — bright, bold, and clear! 🐇✨"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an Ask AI Agent flow (button and status panel) to the React dogfood sample app, which aligns with all file changes.
Description check ✅ Passed The PR description covers the Overview, Implementation notes, and Ticket link sections as required by the template. All key features and implementation details are documented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-vision-ai

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx (1)

114-145: ⚠️ Potential issue | 🟠 Major

Gate the Vision Agent invite behind the demo flag.

The header already knows whether it is in the demo environment, but this control renders unconditionally. Since it posts to the demo Vision Agents service, please hide it when isDemo is false so non-demo calls do not expose a third-party invite flow or leak call metadata.

Suggested change
-          <InviteVisionAgentButton />
+          {isDemo && <InviteVisionAgentButton />}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx` around lines
114 - 145, The InviteVisionAgentButton is rendered unconditionally but should
only appear in demo environments; update the ActiveCallHeader render so
InviteVisionAgentButton is only rendered when isDemo is true (e.g., wrap the
InviteVisionAgentButton render with an isDemo check), keeping the rest of the
controls (RecordingIndicator, ParticipantCountIndicator, Elapsed,
LatencyIndicator) unchanged and preserving the existing controls group
structure.
🧹 Nitpick comments (1)
sample-apps/react/react-dogfood/components/InviteVisionAgentButton.tsx (1)

1-44: Abort the invite request on unmount.

This POST can outlive the component, which leaves the request running and can still hit setIsInviting(false) after unmount. Please wire the fetch through an AbortController and abort it in cleanup.

Suggested change
-import { useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { Icon, useCall, useI18n } from '@stream-io/video-react-sdk';
 
 export const InviteVisionAgentButton = () => {
   const call = useCall();
   const { t } = useI18n();
   const [isInviting, setIsInviting] = useState(false);
+  const abortControllerRef = useRef<AbortController | null>(null);
+
+  useEffect(() => () => abortControllerRef.current?.abort(), []);
 
   const onClick = async () => {
     if (!call?.id || isInviting) return;
     setIsInviting(true);
+    const abortController = new AbortController();
+    abortControllerRef.current = abortController;
     try {
       const response = await fetch(
         `https://api.demo.visionagents.ai/calls/${call.id}/sessions`,
         {
           method: 'POST',
           headers: { 'Content-Type': 'application/json' },
+          signal: abortController.signal,
           body: JSON.stringify({ call_type: call.type ?? 'default' }),
         },
       );
@@
     } catch (err) {
+      if ((err as DOMException).name === 'AbortError') return;
       console.error('Failed to invite vision agent', err);
     } finally {
+      if (abortControllerRef.current === abortController) {
+        abortControllerRef.current = null;
+      }
       setIsInviting(false);
     }
   };
As per coding guidelines, Use AbortController to cancel network requests and media operations on component unmount.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sample-apps/react/react-dogfood/components/InviteVisionAgentButton.tsx`
around lines 1 - 44, The POST in InviteVisionAgentButton's onClick can continue
after unmount and still call setIsInviting; fix this by wiring the fetch through
an AbortController: create an AbortController for the request (store it in a ref
or closure), pass controller.signal into fetch inside onClick, and add a
useEffect cleanup that calls controller.abort() on unmount (or abort any stored
controller refs). In the try/catch/finally around fetch, check for abort (e.g.,
err.name === 'AbortError') and avoid logging it as an error, and ensure you only
call setIsInviting(false) when the request was not aborted (or the component is
still mounted) so setIsInviting is not invoked after unmount.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx`:
- Around line 114-145: The InviteVisionAgentButton is rendered unconditionally
but should only appear in demo environments; update the ActiveCallHeader render
so InviteVisionAgentButton is only rendered when isDemo is true (e.g., wrap the
InviteVisionAgentButton render with an isDemo check), keeping the rest of the
controls (RecordingIndicator, ParticipantCountIndicator, Elapsed,
LatencyIndicator) unchanged and preserving the existing controls group
structure.

---

Nitpick comments:
In `@sample-apps/react/react-dogfood/components/InviteVisionAgentButton.tsx`:
- Around line 1-44: The POST in InviteVisionAgentButton's onClick can continue
after unmount and still call setIsInviting; fix this by wiring the fetch through
an AbortController: create an AbortController for the request (store it in a ref
or closure), pass controller.signal into fetch inside onClick, and add a
useEffect cleanup that calls controller.abort() on unmount (or abort any stored
controller refs). In the try/catch/finally around fetch, check for abort (e.g.,
err.name === 'AbortError') and avoid logging it as an error, and ensure you only
call setIsInviting(false) when the request was not aborted (or the component is
still mounted) so setIsInviting is not invoked after unmount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fe6182e4-8df5-4408-b27d-24fb28cbdea8

📥 Commits

Reviewing files that changed from the base of the PR and between 0f99f41 and 44e10e9.

📒 Files selected for processing (4)
  • sample-apps/react/react-dogfood/components/ActiveCallHeader.tsx
  • sample-apps/react/react-dogfood/components/InviteVisionAgentButton.tsx
  • sample-apps/react/react-dogfood/style/CallHeader/CallHeader-layout.scss
  • sample-apps/react/react-dogfood/translations/en.json

…anel)

Replaces the v1 header pill with a fuller Vision Agents UI:

- "Ask AI Agent" gradient button in the bottom call controls (between
  the record and leave buttons).
- "Ask an AI agent" modal with two informational cards (Talk to it /
  Knows Stream), an Invite agent CTA that POSTs to
  api.demo.visionagents.ai, and a Build your own AI Agent link
  (https://visionagents.ai/, new tab).
- Floating "AI agent in call" status panel in the bottom-right of the
  stage; appears whenever a participant with a userId starting with
  "agent-" is detected. Remove first DELETEs the session via the
  Vision Agents API and falls back to call.kickUser if the session
  id is unknown or the DELETE fails.
@oliverlaz oliverlaz changed the title feat(react-dogfood): add Vision Agents invite button to active call header feat(react-dogfood): add Ask AI Agent flow (button + modal + status panel) May 4, 2026
oliverlaz added 2 commits May 4, 2026 17:43
- Detect the agent participant via userId.startsWith("agent") so calls
  where the agent joins as user "agent" (no dash) light up the floating
  status panel.
- Give the modal "Invite agent" CTA the same blue→purple gradient as
  "Build your own AI Agent" so the primary action of the popup reads
  as prominent.
flex-shrink: 0 and white-space: nowrap so the button stops collapsing
into two lines when the call controls bar is crowded.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":502,"request":{"method":"PATCH","url":"https://api.github.com/repos/GetStream/stream-video-js/issues/comments/4337392986","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: rate limited by coderabbit.ai -->\n\n> [!WARNING]\n> ## Rate limit exceeded\n> \n> `@oliverlaz` has exceeded the limit for the number of commits that can be reviewed per hour. Please wait **49 minutes and 14 seconds** before requesting another review.\n> \n> To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under [billing](https://app.coderabbit.ai/settings/subscription?tab=usage).\n> \n> <details>\n> <summary>⌛ How to resolve this issue?</summary>\n> \n> After the wait time has elapsed, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR.\n> \n> We recommend that you space out your commits to avoid hitting the rate limit.\n> \n> </details>\n> \n> \n> <details>\n> <summary>🚦 How do rate limits work?</summary>\n> \n> CodeRabbit enforces hourly rate limits for each developer per organization.\n> \n> Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.\n> \n> Please see our [FAQ](https://docs.coderabbit.ai/faq) for further information.\n> \n> </details>\n> \n> <details>\n> <summary>ℹ️ Review info</summary>\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: defaults\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `5391a823-7a7e-49a4-908e-ed00443af7d9`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between 44e10e914185e8a1df1fc7cb64c41a21a10d3ced and 189b4a4e57131175fd8e1ff1517332f868930f03.\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (8)</summary>\n> \n> * `sample-apps/react/react-dogfood/components/ActiveCall.tsx`\n> * `sample-apps/react/react-dogfood/components/VisionAgent/AIAgentStatusPanel.tsx`\n> * `sample-apps/react/react-dogfood/components/VisionAgent/AskAIAgentButton.tsx`\n> * `sample-apps/react/react-dogfood/components/VisionAgent/AskAIAgentModal.tsx`\n> * `sample-apps/react/react-dogfood/components/VisionAgent/index.ts`\n> * `sample-apps/react/react-dogfood/style/VisionAgent/index.scss`\n> * `sample-apps/react/react-dogfood/style/index.scss`\n> * `sample-apps/react/react-dogfood/translations/en.json`\n> \n> </details>\n> \n> </details>\n\n<!-- end of auto-generated comment: rate limited by coderabbit.ai -->\n\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nA Vision Agent feature is added to the React dogfood sample app, including new UI components for inviting agents into calls, displaying agent status, a removal mechanism with API fallback, localized strings, and comprehensive styling.\n\n## Changes\n\n**Vision Agent Integration**\n\n| Layer / File(s) | Summary |\n|---|---|\n| **Localization** <br> `translations/en.json` | New translation keys for Vision Agent UI: \"Ask AI Agent\", \"Invite agent\", \"Powered by Vision Agents\", \"Remove\", and status message \"Inviting…\". |\n| **Component Implementations** <br> `components/VisionAgent/AskAIAgentButton.tsx`, `components/VisionAgent/AskAIAgentModal.tsx`, `components/VisionAgent/AIAgentStatusPanel.tsx` | Three new React components: button to open modal, modal for inviting agent with POST to Vision Agents API endpoint, and status panel with remove button (DELETE API with `kickUser` fallback). |\n| **Component Exports** <br> `components/VisionAgent/index.ts` | Barrel export re-exports the three new Vision Agent components. |\n| **Styling** <br> `style/VisionAgent/index.scss` | New stylesheet defines `$ai-gradient` and styles for button, modal (backdrop, header, cards, actions), status panel, and powered-by branding. |\n| **Style Integration** <br> `style/index.scss` | Main stylesheet imports VisionAgent module via `@use`. |\n| **Feature Wiring** <br> `components/ActiveCall.tsx` | Integrates Vision Agent components into call UI: imports three VisionAgent components, adds `showAskAgentModal` and `agentSessionId` state, wires modal open/close and session creation callbacks, replaces `CancelCallConfirmButton` with `AskAIAgentButton` in desktop controls, and renders `AIAgentStatusPanel` in stage container. |\n\n## Sequence Diagram\n\n```mermaid\nsequenceDiagram\n    actor User\n    participant App as ActiveCall\n    participant Modal as AskAIAgentModal\n    participant API as Vision Agents API\n    participant Status as AIAgentStatusPanel\n    participant SDK as Stream SDK<br/>(call.kickUser)\n\n    User->>App: Click \"Ask AI Agent\" button\n    App->>Modal: Show modal (showAskAgentModal=true)\n    User->>Modal: Click \"Invite agent\"\n    Modal->>API: POST /calls/{callId}/sessions\n    API-->>Modal: { session_id }\n    Modal->>App: onSessionCreated(sessionId)\n    App->>Status: sessionId set, render panel\n    Status-->>User: Display agent in call + Remove button\n\n    User->>Status: Click \"Remove\"\n    Status->>API: DELETE /calls/{callId}/sessions/{sessionId}\n    alt API Success\n        API-->>Status: Success\n    else API Fails\n        Status->>SDK: call.kickUser({ user_id: agent-* })\n    end\n    Status->>App: onSessionCleared()\n    App->>Status: sessionId cleared\n    Status-->>User: Panel removed\n```\n\n## Estimated Code Review Effort\n\n🎯 3 (Moderate) | ⏱️ ~25 minutes\n\n## Poem\n\n> A button springs forth with a sparkle so bright,  \n> Inviting an agent to join the call's flight! 🤖✨  \n> Modal, status, and styling align,  \n> Vision Agents now work in perfect design—  \n> The rabbit hops cheering this feature divine! 🐰\n\n</details>\n\n<!-- walkthrough_end -->\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 5</summary>\n\n<details>\n<summary>✅ Passed checks (5 passed)</summary>\n\n|         Check name         | Status   | Explanation                                                                                                                                      |\n| :------------------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |\n|         Title check        | ✅ Passed | The title accurately captures the main changes: adding an Ask AI Agent button, modal, and status panel to the React dogfood app.                 |\n|      Description check     | ✅ Passed | The description is comprehensive, covering overview, implementation notes, test plan, and all required template sections with sufficient detail. |\n|     Docstring Coverage     | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.                                       |\n|     Linked Issues check    | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                         |\n| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                         |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `worktree-vision-ai`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- announcements_start -->\n\n> [!TIP]\n> <details>\n> <summary>💬 Introducing Slack Agent: The best way for teams to turn conversations into code.</summary>\n> \n> [Slack Agent](https://www.coderabbit.ai/agent) is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.\n> \n> - Generate code and open pull requests\n> - Plan features and break down work\n> - Investigate incidents and troubleshoot customer tickets together\n> - Automate recurring tasks and respond to alerts with triggers\n> - Summarize progress and report instantly\n> \n> Built for teams:\n> \n> - **Shared memory** across your entire org—no repeating context\n> - **Per-thread sandboxes** to safely plan and execute work\n> - **Governance built-in**—scoped access, auditability, and budget controls\n> \n> One agent for your entire SDLC. Right inside Slack.\n> \n> 👉 [Get started](https://agent.coderabbit.ai/)\n> \n> </details>\n\n<!-- announcements_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=GetStream/stream-video-js&utm_content=2222)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n<!-- review_rate_limit_status_start -->\n<sub>Review rate limit: 0/1 reviews remaining, refill in 49 minutes and 14 seconds.</sub>\n<!-- review_rate_limit_status_end -->\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}},"response":{"url":"https://api.github.com/repos/GetStream/stream-video-js/issues/comments/4337392986","status":502,"headers":{"content-length":"32","content-type":"application/json","date":"Mon, 04 May 2026 15:51:36 GMT","etag":"\"69f8a491-20\"","server":"github.com","vary":"Accept-Encoding, Accept, X-Requested-With","x-github-request-id":"4097:1EDB75:1A58DF0:6882F71:69F8C07E","x-ratelimit-limit":"9200","x-ratelimit-remaining":"9053","x-ratelimit-reset":"1777912624","x-ratelimit-resource":"core","x-ratelimit-used":"147"},"data":{"message":"Server Error"}}}

oliverlaz added 3 commits May 4, 2026 18:00
Clicking "Ask AI Agent" in the call controls now POSTs to
api.demo.visionagents.ai immediately instead of opening an interim
modal. The button shows "Inviting…" while the request is in flight
and the floating status panel still appears once the agent joins.

Also removes the now-unused modal component, modal SCSS, and the
translation keys that were only used by the modal copy.
…ent present

- Drop the translateY hover effect so the pill no longer shifts on
  pointer enter/leave; only the gradient glow remains.
- Disable the button while an agent participant (userId starting with
  "agent") is already in the call so the user cannot trigger duplicate
  invites.
The call-controls button now flips to "Remove Agent" once a participant
with a userId starting with "agent" is in the call. Clicking it
DELETEs the Vision Agents session (falling back to call.kickUser if
the session id is unknown). The floating status panel keeps its own
Remove button as a redundant entry point.

Also points the "Build your own AI Agent" link in the floating panel
at https://getstream.io/vision-agents/.
@oliverlaz oliverlaz changed the title feat(react-dogfood): add Ask AI Agent flow (button + modal + status panel) feat(react-dogfood): Add VisionAgents integration May 5, 2026
oliverlaz added 2 commits May 5, 2026 12:20
The call-controls button is now the single Remove entry point. The
floating panel becomes a pure status indicator and adds a small close
(X) so it can be dismissed; it re-appears when a new agent session
arrives.
- Switch the call-controls pill to a red->purple gradient (mirroring
  the invite gradient's blue->purple shift) when an agent is in the
  call, with a resting shadow and a punchier hover glow.
- min-width and justify-content keep the pill the same size as the
  label flips between Ask / Inviting / Remove / Removing.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@sample-apps/react/react-dogfood/components/VisionAgent/AIAgentStatusPanel.tsx`:
- Around line 34-38: The IconButton rendered in AIAgentStatusPanel is icon-only
(icon="close") and lacks an accessible name; update the IconButton props to
include an explicit accessible label (e.g., title or aria-label) using the
translation function (for example title={t('Dismiss')} or
aria-label={t('Dismiss')}) where the component is created (the IconButton with
onClick={() => setDismissed(true)}), so screen readers get a clear label for the
dismiss action.

In `@sample-apps/react/react-dogfood/components/VisionAgent/AskAIAgentButton.tsx`:
- Around line 54-60: The URL path interpolations use unescaped dynamic segments
(call.id and sessionId) which can break routing; update the fetch calls that
build `${VISION_AGENTS_API_BASE}/calls/${call.id}/sessions` and any other places
using `${sessionId}` (e.g., the invite/remove calls around lines 83–85) to wrap
dynamic path segments with encodeURIComponent (encodeURIComponent(call.id) and
encodeURIComponent(sessionId)) before interpolating so reserved characters
cannot alter the endpoint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2eb9ff3-e278-4252-a31f-48721f857fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 44e10e9 and 3988b5e.

📒 Files selected for processing (7)
  • sample-apps/react/react-dogfood/components/ActiveCall.tsx
  • sample-apps/react/react-dogfood/components/VisionAgent/AIAgentStatusPanel.tsx
  • sample-apps/react/react-dogfood/components/VisionAgent/AskAIAgentButton.tsx
  • sample-apps/react/react-dogfood/components/VisionAgent/index.ts
  • sample-apps/react/react-dogfood/style/VisionAgent/index.scss
  • sample-apps/react/react-dogfood/style/index.scss
  • sample-apps/react/react-dogfood/translations/en.json
✅ Files skipped from review due to trivial changes (3)
  • sample-apps/react/react-dogfood/style/index.scss
  • sample-apps/react/react-dogfood/translations/en.json
  • sample-apps/react/react-dogfood/style/VisionAgent/index.scss
🚧 Files skipped from review as they are similar to previous changes (1)
  • sample-apps/react/react-dogfood/components/ActiveCall.tsx

Comment on lines +34 to +38
<IconButton
className="rd__ai-agent-status__close"
icon="close"
onClick={() => setDismissed(true)}
/>
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n sample-apps/react/react-dogfood/components/VisionAgent/AIAgentStatusPanel.tsx

Repository: GetStream/stream-video-js

Length of output: 1905


🏁 Script executed:

# Search for IconButton usage patterns in the codebase to understand how it handles accessible names
rg "IconButton" sample-apps/react/react-dogfood/components/ -A 3 -B 1 | head -50

Repository: GetStream/stream-video-js

Length of output: 4219


🏁 Script executed:

# Check the imports to understand where IconButton comes from
rg "import.*IconButton" sample-apps/react/react-dogfood/

Repository: GetStream/stream-video-js

Length of output: 450


🏁 Script executed:

# Search for IconButton type definitions or implementation in the SDK
find sample-apps -name "*.d.ts" -o -name "*.ts" | xargs grep -l "IconButton" | head -5

Repository: GetStream/stream-video-js

Length of output: 586


🏁 Script executed:

# Check if there are any IconButton implementations or types exported from the SDK package
rg "export.*IconButton|interface IconButton|type IconButton" --type ts --type tsx | head -20

Repository: GetStream/stream-video-js

Length of output: 97


🏁 Script executed:

# Look for node_modules or SDK package to check IconButton props
fd -t d "@stream-io" sample-apps/react/react-dogfood/node_modules 2>/dev/null | head -3

Repository: GetStream/stream-video-js

Length of output: 51


🏁 Script executed:

# Search for aria-label or title patterns used with IconButton in the codebase
rg "IconButton.*aria-label|IconButton.*title|title.*IconButton|aria-label.*IconButton" sample-apps/react/react-dogfood/components/ -A 3 -B 1

Repository: GetStream/stream-video-js

Length of output: 51


🏁 Script executed:

# Search for any IconButton usage that includes title, aria-label, or aria-description
rg "IconButton" sample-apps/react/react-dogfood/ -A 5 | rg -B 5 "title|aria-label|aria-description"

Repository: GetStream/stream-video-js

Length of output: 51


🏁 Script executed:

# Check the package.json to understand the SDK version being used
cat sample-apps/react/react-dogfood/package.json | grep -A 2 -B 2 "video-react-sdk"

Repository: GetStream/stream-video-js

Length of output: 278


🏁 Script executed:

# Look for any other icon-only buttons with accessible names for comparison
rg "icon=" sample-apps/react/react-dogfood/components/ -B 2 -A 2 | head -40

Repository: GetStream/stream-video-js

Length of output: 3839


🏁 Script executed:

# Search for IconButton implementation in the SDK source code
find . -path "./packages/*/src" -name "*.tsx" -o -name "*.ts" | xargs rg "IconButton" -l 2>/dev/null | grep -v node_modules

Repository: GetStream/stream-video-js

Length of output: 121


🏁 Script executed:

# Look specifically in the video-react-sdk package
find packages -name "*IconButton*" -o -path "*/components/*" -name "*.tsx" | head -10

Repository: GetStream/stream-video-js

Length of output: 817


🏁 Script executed:

# Search for IconButton export and definition in the SDK
rg "export.*IconButton|const IconButton" packages/ --type ts --type tsx -B 2 -A 10

Repository: GetStream/stream-video-js

Length of output: 97


🏁 Script executed:

# Search for IconButton in the react-sdk Button directory
cat packages/react-sdk/src/components/Button/index.ts

Repository: GetStream/stream-video-js

Length of output: 164


🏁 Script executed:

# List files in the Button directory
ls -la packages/react-sdk/src/components/Button/

Repository: GetStream/stream-video-js

Length of output: 442


🏁 Script executed:

cat packages/react-sdk/src/components/Button/IconButton.tsx

Repository: GetStream/stream-video-js

Length of output: 944


🏁 Script executed:

# Also check the CompositeButton since IconButton likely extends it
cat packages/react-sdk/src/components/Button/CompositeButton.tsx

Repository: GetStream/stream-video-js

Length of output: 3102


Add an explicit accessible name to the dismiss button.

Line 34 renders an icon-only IconButton without an accessible name. The icon="close" prop only controls visual rendering and does not provide an accessible label. Screen readers will expose this as an unnamed button, and users will not know its purpose. Pass a translated title or aria-label explicitly (e.g., title={t('Dismiss')}).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@sample-apps/react/react-dogfood/components/VisionAgent/AIAgentStatusPanel.tsx`
around lines 34 - 38, The IconButton rendered in AIAgentStatusPanel is icon-only
(icon="close") and lacks an accessible name; update the IconButton props to
include an explicit accessible label (e.g., title or aria-label) using the
translation function (for example title={t('Dismiss')} or
aria-label={t('Dismiss')}) where the component is created (the IconButton with
onClick={() => setDismissed(true)}), so screen readers get a clear label for the
dismiss action.

Comment on lines +54 to +60
const response = await fetch(
`${VISION_AGENTS_API_BASE}/calls/${call.id}/sessions`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ call_type: call.type ?? 'default' }),
},
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Encode dynamic path segments before calling the Vision Agents API.

Lines 55 and 84 interpolate call.id and sessionId directly into the URL path. Any reserved character in either value, such as / or ?, changes the target endpoint and can make invite/remove hit the wrong resource.

Suggested fix
-        `${VISION_AGENTS_API_BASE}/calls/${call.id}/sessions`,
+        `${VISION_AGENTS_API_BASE}/calls/${encodeURIComponent(call.id)}/sessions`,
@@
-            `${VISION_AGENTS_API_BASE}/calls/${call.id}/sessions/${sessionId}`,
+            `${VISION_AGENTS_API_BASE}/calls/${encodeURIComponent(call.id)}/sessions/${encodeURIComponent(sessionId)}`,

Also applies to: 83-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sample-apps/react/react-dogfood/components/VisionAgent/AskAIAgentButton.tsx`
around lines 54 - 60, The URL path interpolations use unescaped dynamic segments
(call.id and sessionId) which can break routing; update the fetch calls that
build `${VISION_AGENTS_API_BASE}/calls/${call.id}/sessions` and any other places
using `${sessionId}` (e.g., the invite/remove calls around lines 83–85) to wrap
dynamic path segments with encodeURIComponent (encodeURIComponent(call.id) and
encodeURIComponent(sessionId)) before interpolating so reserved characters
cannot alter the endpoint.

@oliverlaz oliverlaz requested a review from jdimovska May 5, 2026 11:53
@oliverlaz oliverlaz changed the title feat(react-dogfood): Add VisionAgents integration feat(react-dogfood): add Ask AI Agent flow (button + status panel) May 5, 2026
oliverlaz added 2 commits May 5, 2026 14:33
…ls button

The "AI agent in call" panel now floats centered above the Ask/Remove
Agent pill in the call controls bar instead of in the bottom-right of
the stage, making the link between the panel and the button it
controls visually obvious.
flex-center the IconButton, drop the SDK button's default padding /
min-width, and shrink the inner icon to 14px so it sits in the middle
of the 24x24 close hit area.
@oliverlaz oliverlaz changed the title feat(react-dogfood): add Ask AI Agent flow (button + status panel) feat(react-dogfood): VisionAgents integration May 5, 2026
@oliverlaz oliverlaz merged commit 29de74d into main May 5, 2026
12 of 13 checks passed
@oliverlaz oliverlaz deleted the worktree-vision-ai branch May 5, 2026 12:37
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.

1 participant