Skip to content

26 chat transcript - #24

Merged
abhilance merged 1 commit into
mainfrom
26---chat-and-transcript
Aug 1, 2026
Merged

26 chat transcript#24
abhilance merged 1 commit into
mainfrom
26---chat-and-transcript

Conversation

@abhilance

@abhilance abhilance commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added real-time chat for completed meetings, including message threads and composition.
    • Added automated assistant responses using meeting summaries and instructions.
    • Added searchable meeting transcripts with speaker names, avatars, timestamps, and highlighted matches.
    • Added loading and authentication states for chat and transcript views.
  • Improvements

    • Chat and transcript content are now integrated into the completed meeting experience.
    • Improved handling of missing transcript speakers with an “Unknown” label.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Completed meetings now include searchable transcripts and Stream Chat conversations. The server provisions chat tokens, enriches transcript speakers, and handles message.new events by generating agent responses with OpenAI.

Changes

Meeting chat and transcript

Layer / File(s) Summary
Server transcript and chat contracts
src/lib/stream-chat.ts, src/modules/meetings/server/procedure.ts
Adds the server-side Stream Chat client, chat-token mutation, and ownership-checked transcript query with resolved speaker data.
Meeting chat client integration
package.json, src/app/globals.css, src/modules/meetings/ui/components/chat-provider.tsx, src/modules/meetings/ui/components/chat-ui.tsx, src/modules/meetings/ui/components/completed-state.tsx
Adds authenticated meeting channels, chat rendering, required styles, dependencies, and completed-meeting tab wiring.
Searchable transcript view
src/modules/meetings/ui/components/transcript.tsx
Adds transcript search, speaker details, timestamps, scrolling, and highlighted matches.
AI webhook response flow
src/app/api/webhook/route.ts
Processes new chat messages, validates meeting and agent state, sends context to OpenAI, and posts generated agent replies.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatProvider
  participant ChatUI
  participant meetingsRouter
  participant StreamChat
  ChatProvider->>ChatUI: pass meetingId and authenticated user
  ChatUI->>meetingsRouter: request generateChatToken
  meetingsRouter->>StreamChat: upsert user and create token
  StreamChat-->>meetingsRouter: return user token
  meetingsRouter-->>ChatUI: return token
  ChatUI->>StreamChat: initialize meeting channel
  StreamChat-->>ChatUI: render messages, composer, and threads
Loading
sequenceDiagram
  participant StreamChat
  participant webhookRoute
  participant meetingData
  participant OpenAI
  StreamChat->>webhookRoute: deliver message.new event
  webhookRoute->>meetingData: load completed meeting and agent
  meetingData-->>webhookRoute: return meeting context and agent
  webhookRoute->>StreamChat: load recent channel history
  StreamChat-->>webhookRoute: return recent messages
  webhookRoute->>OpenAI: send meeting context and message history
  OpenAI-->>webhookRoute: return generated response
  webhookRoute->>StreamChat: post response as agent
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the pull request's main changes: chat and transcript functionality.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 26---chat-and-transcript

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.

@abhilance
abhilance merged commit c1660c7 into main Aug 1, 2026
1 check was pending

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

🧹 Nitpick comments (4)
package.json (1)

73-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move ngrok out of production dependencies.

ngrok is added under production dependencies at Line 73 as "ngrok": "^5.0.0-beta.2". None of the reviewed files import or reference ngrok. This package is typically used to tunnel a local server for webhook testing during development, not at runtime in production. Keeping it in dependencies increases the production install size and includes a pre-release (beta) version in the deployed artifact.

Move it to devDependencies unless it is genuinely required at runtime, and confirm whether the beta version is intentional.

♻️ Proposed fix
   "dependencies": {
-    "ngrok": "^5.0.0-beta.2",
     "nuqs": "^2.4.3",

Add under devDependencies instead, if still needed for local webhook testing tooling.

🤖 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 `@package.json` around lines 73 - 89, Move the ngrok entry from the production
dependencies section to devDependencies in package.json, preserving the existing
version unless the project confirms it is intentionally required at runtime.
Keep ngrok available for local development tooling without including it in
production installs.
src/modules/meetings/ui/components/chat-ui.tsx (2)

32-46: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No error path when token generation fails.

useCreateChatClient only returns a client once connected; it does not itself surface a token/connection error. If generateChatToken (Line 33-35) rejects, client stays undefined and the user sees "Loading Chat" indefinitely with no retry option, as shown by the guard at Line 58.

Surface the mutation's error state (already available from useMutation at Line 33) and render an error UI when it is set, instead of relying solely on !client.

♻️ Proposed fix
   const trpc = useTRPC();
-  const { mutateAsync: generateChatToken } = useMutation(
+  const { mutateAsync: generateChatToken, isError: tokenError } = useMutation(
     trpc.meetings.generateChatToken.mutationOptions(),
   );
   ...
-  if (!client) {
+  if (tokenError) {
+    return (
+      <LoadingState
+        tittle="Unable to load chat"
+        description="Please refresh the page to try again"
+      />
+    );
+  }
+
+  if (!client) {

Also applies to: 58-65

🤖 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 `@src/modules/meetings/ui/components/chat-ui.tsx` around lines 32 - 46, Update
the useMutation call for generateChatToken to retain its error state, then use
that state in the chat loading guard near the client check to render an error UI
instead of indefinitely showing “Loading Chat” when token generation fails.
Preserve the existing client-rendering path when no error exists and the client
connects.

17-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unused meetingName prop.

meetingName is accepted by ChatUIProps and included in the useEffect dependency array at Line 56, but it is never used inside the effect body or anywhere else in the component. The channel is created without a name field.

Either use meetingName when creating the channel (for example, client.channel("messaging", meetingId, { members: [userId], name: meetingName })), or remove it from the props and dependency array if it is not needed.

♻️ Proposed fix
     const channel = client.channel("messaging", meetingId, {
       members: [userId],
+      name: meetingName,
     });

Also applies to: 48-56

🤖 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 `@src/modules/meetings/ui/components/chat-ui.tsx` around lines 17 - 23, Update
ChatUIProps and the ChatUI channel-creation effect so meetingName is used when
creating the channel, including it in the channel data as the channel name;
retain it in the effect dependencies and ensure the existing meetingId and
userId behavior remains unchanged.
src/app/api/webhook/route.ts (1)

151-253: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

No idempotency safeguard against duplicate webhook delivery.

This handler has no check for whether a reply was already generated for this specific incoming message (e.g., by message id). If Stream Chat retries the message.new webhook (for example after a timeout while awaiting the OpenAI completion), the handler runs again and sends a second AI reply for the same user message.

Track processed message ids (or check the channel for an existing agent reply to this message) before calling the OpenAI completion, to avoid duplicate responses on retry.

🤖 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 `@src/app/api/webhook/route.ts` around lines 151 - 253, Update the message.new
handling in the webhook route to enforce idempotency for each incoming message,
using its unique message id or an equivalent existing agent-reply check before
calling openaiClient.chat.completions.create. Return or skip processing when
that message has already been handled, and record the message as processed only
when appropriate so webhook retries cannot send duplicate replies.
🤖 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 `@src/app/api/webhook/route.ts`:
- Around line 184-202: Update the instructions template construction around
existingMeeting.summary to use a non-null fallback when the meeting summary is
absent, such as “No summary available yet.” Preserve the existing summary
content for non-null values and keep the rest of the prompt unchanged.
- Around line 238-251: Update the webhook flow around streamChat.upsertUser and
channel.sendMessage to await both asynchronous calls before returning. Keep them
inside the existing try/catch so failures propagate to the error handler and
prevent the success response when delivery fails.
- Line 224: Update the GPTResponseText assignment in the webhook handler to use
optional chaining when accessing the first choice’s message content, so an empty
choices array yields undefined and follows the existing “No response from GPT”
branch instead of throwing.
- Line 12: Move OpenAI client construction out of module scope and into the
message.new branch of the webhook handler. Check that OPENAI_API_KEY is present
before constructing the client, and preserve the existing try/catch flow so
missing configuration is handled per request rather than during module import.
- Around line 183-202: In the follow-up prompt branch around existingMeeting and
existingAgent, validate that userId equals existingMeeting.userId before reading
existingMeeting.summary or existingAgent.instructions; only build and send the
summary-based response for the meeting owner, while preserving the existing
agent-sender check.

In `@src/modules/meetings/server/procedure.ts`:
- Around line 50-52: Update the transcript parsing flow in the server procedure
around JSONL.parse so each Stream Video row is validated for required start_time
and stop_time fields, rejecting invalid rows before returning the transcript and
mapping those provider fields to StreamTranscriptItem’s start_ts and stop_ts. In
transcript.tsx, retain the existing rendering behavior; no direct change is
required there because server-side validation and mapping prevent undefined keys
or date operands. Affected sites: src/modules/meetings/server/procedure.ts lines
50-52 require the parsing and validation fix;
src/modules/meetings/ui/components/transcript.tsx lines 43-58 require no direct
change.
- Around line 20-25: Update generateChatToken so authenticated callers receive
the default Stream Chat user role, granting admin only through an explicit
application-authorization check. Enforce meeting access through channel
membership or permissions, and create short-lived tokens with both iat and exp
claims; ensure the Chat UI refreshes the token before expiration.

---

Nitpick comments:
In `@package.json`:
- Around line 73-89: Move the ngrok entry from the production dependencies
section to devDependencies in package.json, preserving the existing version
unless the project confirms it is intentionally required at runtime. Keep ngrok
available for local development tooling without including it in production
installs.

In `@src/app/api/webhook/route.ts`:
- Around line 151-253: Update the message.new handling in the webhook route to
enforce idempotency for each incoming message, using its unique message id or an
equivalent existing agent-reply check before calling
openaiClient.chat.completions.create. Return or skip processing when that
message has already been handled, and record the message as processed only when
appropriate so webhook retries cannot send duplicate replies.

In `@src/modules/meetings/ui/components/chat-ui.tsx`:
- Around line 32-46: Update the useMutation call for generateChatToken to retain
its error state, then use that state in the chat loading guard near the client
check to render an error UI instead of indefinitely showing “Loading Chat” when
token generation fails. Preserve the existing client-rendering path when no
error exists and the client connects.
- Around line 17-23: Update ChatUIProps and the ChatUI channel-creation effect
so meetingName is used when creating the channel, including it in the channel
data as the channel name; retain it in the effect dependencies and ensure the
existing meetingId and userId behavior remains unchanged.
🪄 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 Plus

Run ID: bd86bd10-5c74-4338-815b-003e4c146361

📥 Commits

Reviewing files that changed from the base of the PR and between 44e40fa and 6d1bb73.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • package.json
  • src/app/api/webhook/route.ts
  • src/app/globals.css
  • src/lib/stream-chat.ts
  • src/modules/meetings/server/procedure.ts
  • src/modules/meetings/ui/components/chat-provider.tsx
  • src/modules/meetings/ui/components/chat-ui.tsx
  • src/modules/meetings/ui/components/completed-state.tsx
  • src/modules/meetings/ui/components/transcript.tsx

import { genetratedAvatarUri } from "@/lib/avatar"
import {ChatCompletionMessageParam} from "openai/resources/index.mjs"
import OpenAI from "openai";
const openaiClient = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check OpenAI node SDK version and constructor behavior on missing apiKey.
fd -e ts -e tsx . | xargs rg -n 'process.env.OPENAI_API_KEY' -C2

Repository: abhilance/meet-ai

Length of output: 1439


🏁 Script executed:

#!/bin/bash
set -u

echo "== files =="
git ls-files | sed -n '1,120p'

echo "== package manifests =="
fd -a 'package.json$|pnpm-lock.yaml$|package-lock.json$|yarn.lock$' . | sed -n '1,80p' | while read -r f; do
  echo "--- $f"
  if grep -q '"openai"' "$f"; then
    sed -n "/openai/p" "$f"
  fi
done

echo "== route outline/contents =="
wc -l src/app/api/webhook/route.ts
cat -n src/app/api/webhook/route.ts | sed -n '1,140p'

echo "== openai imports/usage =="
rg -n '"openai"|openai from|OpenAI from|new OpenAI|OPENAI_API_KEY' -g '!node_modules' -g '!dist' -g '!build' .

Repository: abhilance/meet-ai

Length of output: 12246


🌐 Web query:

OpenAI Node.js SDK constructor apiKey environment variable OPENAI_API_KEY missing throws

💡 Result:

The OpenAI Node.js SDK constructor performs validation of credentials during initialization and will throw an error if an API key is not provided [1][2][3]. If the OPENAI_API_KEY environment variable is not set and no apiKey is passed as an option to the constructor, the SDK throws an error similar to: "Missing credentials. Please pass an apiKey, workloadIdentity, adminAPIKey, or set the OPENAI_API_KEY or OPENAI_ADMIN_KEY environment variable." [1][2] Key points regarding this behavior: - Validation Timing: The check occurs at the time of client instantiation (new OpenAI({...})), before any network requests are made [1][3]. - Strictness: The SDK enforces this requirement regardless of whether a custom baseURL is used [4]. - Workarounds: If you are using a custom endpoint or proxy that does not require an API key, you can bypass this validation by explicitly passing an empty string or a placeholder string as the apiKey, such as: new OpenAI({ apiKey: "none", baseURL: "https://your-custom-url.com" }); [1][4] Note that recent versions of the SDK have refined this validation logic to handle various authentication types, but the fundamental requirement for a provided (even if placeholder) credential remains to prevent misconfiguration [1][5].

Citations:


Defer OpenAI client construction until a key is present.

new OpenAI({ apiKey: process.env.OPENAI_API_KEY! }) runs at module import before the request handler try/catch. If OPENAI_API_KEY is unset, the constructor throws once and breaks every webhook event path. Create the client only in branch message.new after checking the key.

🤖 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 `@src/app/api/webhook/route.ts` at line 12, Move OpenAI client construction out
of module scope and into the message.new branch of the webhook handler. Check
that OPENAI_API_KEY is present before constructing the client, and preserve the
existing try/catch flow so missing configuration is handled per request rather
than during module import.

Comment on lines +183 to +202
if (userId !== existingAgent.id) {
const instructions = `
You are an AI assistant helping the user revisit a recently completed meeting.
Below is a summary of the meeting, generated from the transcript:

${existingMeeting.summary}

The following are your original instructions from the live meeting assistant. Please continue to follow these behavioral guidelines as you assist the user:

${existingAgent.instructions}

The user may ask questions about the meeting, request clarifications, or ask for follow-up actions.
Always base your responses on the meeting summary above.

You also have access to the recent conversation history between you and the user. Use the context of previous messages to provide relevant, coherent, and helpful responses. If the user's question refers to something discussed earlier, make sure to take that into account and maintain continuity in the conversation.

If the summary does not contain enough information to answer a question, politely let the user know.

Be concise, helpful, and focus on providing accurate information from the meeting and the ongoing conversation.
`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)webhook/route\.ts$|chat-ui\.tsx$|webhook|chat' | head -200

echo
echo "== locate route and chat-ui =="
fd -a 'route\.ts$|chat-ui\.tsx$' . | sed 's#^\./##'

echo
echo "== outline webhook route if present =="
if [ -f src/app/api/webhook/route.ts ]; then
  wc -l src/app/api/webhook/route.ts
  ast-grep outline src/app/api/webhook/route.ts || true
fi

echo
echo "== relevant route lines 130-230 =="
if [ -f src/app/api/webhook/route.ts ]; then
  sed -n '130,230p' src/app/api/webhook/route.ts | nl -ba -v130
fi

Repository: abhilance/meet-ai

Length of output: 993


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant webhook route lines 130-230 =="
sed -n '130,230p' src/app/api/webhook/route.ts | awk '{printf "%5d  %s\n", NR+129, $0}'

echo
echo "== relevant chat-ui lines =="
wc -l src/modules/meetings/ui/components/chat-ui.tsx
sed -n '1,260p' src/modules/meetings/ui/components/chat-ui.tsx | awk '{printf "%5d  %s\n", NR, $0}'

echo
echo "== text search for channel membership and owner references =="
rg -n "members|userId|existingMeeting|createChannel|channel|owner|membership|StreamChat" src/modules/meetings/ui/components/chat-ui.tsx src/app/api/webhook/route.ts src/lib/stream-chat.ts

Repository: abhilance/meet-ai

Length of output: 9388


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== full webhook route outline with search =="
rg -n "function |export async function POST|generateChatToken|createChannel|members|sendMessage|response|messages|status|user|channel_id|message\\.new|userId|meeting\\.userId|own|owner" src/app/api/webhook/route.ts src/modules/meetings/ui/components/chat-ui.tsx src/lib/stream-chat.ts

echo
echo "== webhook route lines 1-260 =="
sed -n '1,260p' src/app/api/webhook/route.ts | awk '{printf "%5d  %s\n", NR, $0}'

echo
echo "== chat token route candidates =="
git ls-files | rg 'generateChatToken|chatToken|meetings' | head -200
rg -n "generateChatToken|chatToken|trpc\\.meetings" src

Repository: abhilance/meet-ai

Length of output: 21217


🌐 Web query:

Stream Chat Python webhooks message.new permissions check membership channel watch restrictions members

💡 Result:

In Stream Chat, the message.new webhook is an event notification triggered when a new message is created [1][2]. Because webhooks are server-to-server notifications sent by Stream to your backend, they are not subject to client-side channel watch restrictions or membership checks [3][4]. Key considerations for your implementation: 1. Webhook Security: You must verify that incoming webhooks originate from Stream to prevent unauthorized processing [5][6]. The Python SDK provides the verify_and_parse_webhook method, which validates the X-Signature header using your API secret [6][7]. If validation fails, it raises an InvalidWebhookError [6][7]. 2. Membership and Restrictions: - Webhooks receive events regardless of the specific client-side access controls of the users involved [5][3]. - Watching a channel is a client-side real-time subscription mechanism, while membership is a persistent association [3][4]. Webhooks deliver the message data (including the members array in the payload) once the message has been processed by the API [1][2]. - If you need to intercept messages before they are finalized or visible to others, you should use the Before Message Send webhook, which allows your server to reject or modify messages before they reach the channel [8]. 3. Python Implementation: When handling the webhook, ensure you are using the verify_and_parse_webhook method on your Stream Chat client instance [6][7]. This method automatically handles payload parsing and signature verification [6][7]. client.verify_and_parse_webhook(request_body, x_signature_header) [8] Always filter for message.new in your event handler logic if you have subscribed to multiple event types [5][6]. Because webhooks do not enforce client-side membership checks, your server-side logic must manually verify any business rules regarding channel membership or message visibility if those requirements exist for your specific application architecture.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== meetings schema relevant fields =="
rg -n "export const meetings|userId|summary|agentId|ChatToken|token|chat" src/db src/modules/meetings src/app/api -g '!**/node_modules/**' | head -250

echo
echo "== procedure around generateChatToken =="
sed -n '1,140p' src/modules/meetings/server/procedure.ts | awk '{printf "%5d  %s\n", NR, $0}'

echo
echo "== page usage of ChatUI =="
sed -n '1,90p' 'src/app/(dashborad)/meetings/[meetingId]/page.tsx' 2>/dev/null | awk '{printf "%5d  %s\n", NR, $0}' || true
sed -n '1,90p' 'src/app/call/[meetingId]/page.tsx' 2>/dev/null | awk '{printf "%5d  %s\n", NR, $0}' || true

echo
echo "== precise schema candidate files =="
(fd -a schema.ts src/db | sed 's#^\./##' | xargs -r sed -n '1,260p')

Repository: abhilance/meet-ai

Length of output: 18888


Add ownership validation before building the follow-up prompt.

The UI sets channel membership to [userId], but this webhook only filters out the agent sender. Add an explicit check for userId === existingMeeting.userId before reading existingMeeting.summary and existingAgent.instructions, so only the completed meeting owner can get a response from their summary.

🤖 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 `@src/app/api/webhook/route.ts` around lines 183 - 202, In the follow-up prompt
branch around existingMeeting and existingAgent, validate that userId equals
existingMeeting.userId before reading existingMeeting.summary or
existingAgent.instructions; only build and send the summary-based response for
the meeting owner, while preserving the existing agent-sender check.

Comment on lines +184 to +202
const instructions = `
You are an AI assistant helping the user revisit a recently completed meeting.
Below is a summary of the meeting, generated from the transcript:

${existingMeeting.summary}

The following are your original instructions from the live meeting assistant. Please continue to follow these behavioral guidelines as you assist the user:

${existingAgent.instructions}

The user may ask questions about the meeting, request clarifications, or ask for follow-up actions.
Always base your responses on the meeting summary above.

You also have access to the recent conversation history between you and the user. Use the context of previous messages to provide relevant, coherent, and helpful responses. If the user's question refers to something discussed earlier, make sure to take that into account and maintain continuity in the conversation.

If the summary does not contain enough information to answer a question, politely let the user know.

Be concise, helpful, and focus on providing accurate information from the meeting and the ongoing conversation.
`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Nullable summary interpolated without a fallback.

existingMeeting.summary is nullable in the meetings schema (summary: text("summary") with no .notNull()), but it is interpolated directly into the instructions template at Line 188 without a null check. If summary is null, the prompt literally contains null in place of the meeting summary.

Add a fallback (e.g., existingMeeting.summary ?? "No summary available yet.") before building the instructions.

🤖 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 `@src/app/api/webhook/route.ts` around lines 184 - 202, Update the instructions
template construction around existingMeeting.summary to use a non-null fallback
when the meeting summary is absent, such as “No summary available yet.” Preserve
the existing summary content for non-null values and keep the rest of the prompt
unchanged.

model: "gpt-4o",
});

const GPTResponseText = GPTResponse.choices[0].message.content;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against an empty choices array.

GPTResponse.choices[0].message.content assumes choices[0] always exists. If the API returns an empty choices array (for example due to content filtering), this throws a TypeError accessing .message on undefined, which is only caught by the generic outer catch. Use optional chaining so the existing "No response from GPT" branch handles this case explicitly.

🐛 Proposed fix
-      const GPTResponseText = GPTResponse.choices[0].message.content;
+      const GPTResponseText = GPTResponse.choices[0]?.message?.content;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const GPTResponseText = GPTResponse.choices[0].message.content;
const GPTResponseText = GPTResponse.choices[0]?.message?.content;
🤖 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 `@src/app/api/webhook/route.ts` at line 224, Update the GPTResponseText
assignment in the webhook handler to use optional chaining when accessing the
first choice’s message content, so an empty choices array yields undefined and
follows the existing “No response from GPT” branch instead of throwing.

Comment on lines +238 to +251
streamChat.upsertUser({
id: existingAgent.id,
name: existingAgent.name,
image: avatarUrl,
});

channel.sendMessage({
text: GPTResponseText,
user: {
id: existingAgent.id,
name: existingAgent.name,
image: avatarUrl,
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Await the Stream Chat calls before returning.

streamChat.upsertUser(...) (Line 238-242) and channel.sendMessage(...) (Line 244-251) are not awaited. The function returns before these promises settle. Any rejection (network failure, invalid payload, rate limiting) becomes an unhandled promise rejection instead of being caught by the surrounding try/catch at Line 255-258. The webhook always responds with {status:"ok"} at Line 259, even if the AI reply never reached the channel.

Await both calls so failures propagate to the existing error handler and the response accurately reflects whether the reply was delivered.

🐛 Proposed fix
-      streamChat.upsertUser({
+      await streamChat.upsertUser({
         id: existingAgent.id,
         name: existingAgent.name,
         image: avatarUrl,
       });

-      channel.sendMessage({
+      await channel.sendMessage({
         text: GPTResponseText,
         user: {
           id: existingAgent.id,
           name: existingAgent.name,
           image: avatarUrl,
         },
       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
streamChat.upsertUser({
id: existingAgent.id,
name: existingAgent.name,
image: avatarUrl,
});
channel.sendMessage({
text: GPTResponseText,
user: {
id: existingAgent.id,
name: existingAgent.name,
image: avatarUrl,
},
});
await streamChat.upsertUser({
id: existingAgent.id,
name: existingAgent.name,
image: avatarUrl,
});
await channel.sendMessage({
text: GPTResponseText,
user: {
id: existingAgent.id,
name: existingAgent.name,
image: avatarUrl,
},
});
🤖 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 `@src/app/api/webhook/route.ts` around lines 238 - 251, Update the webhook flow
around streamChat.upsertUser and channel.sendMessage to await both asynchronous
calls before returning. Keep them inside the existing try/catch so failures
propagate to the error handler and prevent the success response when delivery
fails.

Comment on lines +20 to +25
generateChatToken: protectedProcedure.mutation(async ({ ctx }) => {
const token = streamChat.createToken(ctx.auth.user.id);
await streamChat.upsertUser({
id: ctx.auth.user.id,
role: "admin",
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/modules/meetings --items all --type function
rg -nP -C 4 '\b(generateChatToken|createToken|upsertUser|connectUser|tokenProvider)\b' src

Repository: abhilance/meet-ai

Length of output: 3507


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package files =="
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' || true

echo
echo "== stream chat package references =="
for f in $(git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$'); do
  echo "-- $f --"
  rg -n '"[A-Za-z0-9@/_-]+stream-chat|stream-chat' "$f" || true
done

echo
echo "== stream-chat implementation/usages =="
rg -n "streamChat\.(createToken|upsertUser|connectUser|disconnectUser|createToken)" src --glob '!src/**/*' || true
fd -a 'stream-chat|streamChat' src | sed 's#^\./##' | while read -r f; do
  echo "-- $f --"
  sed -n '1,220p' "$f"
done

echo
echo "== meetings procedure chat token section =="
sed -n '1,80p' src/modules/meetings/server/procedure.ts

echo
echo "== chat UI token/provider usage =="
sed -n '1,120p' src/modules/meetings/ui/components/chat-ui.tsx

Repository: abhilance/meet-ai

Length of output: 5663


🌐 Web query:

Stream Chat JavaScript SDK ChatClient createChannel channel permissions admin role token exp iat documentation

💡 Result:

In the Stream Chat SDK, channel creation permissions are controlled by the CreateChannel permission, which can be managed via the Stream Dashboard for each channel type [1]. Key details regarding your query: Channel Permissions - The CreateChannel permission determines whether a user can initiate channel creation from the client side [1]. - By default, users with the user role have permission to create channels in standard channel types (e.g., messaging) [1]. - To restrict channel creation to server-side only, you should revoke this permission for user roles in the Stream Dashboard [2][1]. - Permissions are hierarchical: Application-level permissions apply globally, Channel-Type permissions apply to all channels of that type, and Channel-specific permissions (via grants modifiers) override others [3][4]. Role Information - The admin role is a built-in role for users performing administrative tasks with elevated permissions [5][6]. Admins typically have all permissions granted, including CreateChannel [7]. JWT Tokens (exp, iat) - Token claims: - exp (Expiration): Defines when the token becomes invalid. This should be a Unix timestamp (in seconds) representing the absolute expiration time [8][9]. - iat (Issued At): A Unix timestamp (in seconds) representing when the token was issued [9]. - Importance of iat: Including the iat claim is a security best practice and a requirement for per-user token revocation. Stream compares the token's iat against a revoke_tokens_issued_before setting to determine validity [8][9]. - SDK Implementation: When using the createToken method, you can pass the iat as the third parameter [9][10]. Since the SDK handles token refresh automatically when provided with a tokenProvider function, ensure your server-side logic generates tokens with accurate exp and iat values [8]. For authoritative documentation, refer to the official Stream Chat guides on Permissions [3][4] and Authentication [9].

Citations:


🌐 Web query:

Stream Chat JavaScript SDK 9 createToken default token expiration JWT exp iat code

💡 Result:

In the Stream Chat JavaScript SDK, the createToken method is used on the server side to generate a JSON Web Token (JWT) for authenticating users [1][2]. Default Expiration By default, user tokens generated with createToken are valid indefinitely [1][3]. Setting Expiration (exp) To set an expiration, you must pass the desired expiration time as the second argument to the createToken method [1][4]. This argument must be a Unix timestamp in seconds (the number of seconds since 00:00:00 UTC on January 1, 1970) [1][5]. Example for a 1-hour expiration: const expiresAt = Math.floor(Date.now / 1000) + 60 * 60; const token = client.createToken("user-id", expiresAt); Issued At (iat) The iat (issued at) claim is optional but highly recommended for security, as it is required for per-user token revocation features [1][6]. By default, tokens generated via this method do not include an iat claim [1][3]. To include the iat claim, pass the current Unix timestamp as the third argument [1][5]: const issuedAt = Math.floor(Date.now / 1000); client.createToken("user-id", expiresAt, issuedAt); Summary of createToken signature: client.createToken(userID: string, exp?: number, iat?: number) [2][5] The exp and iat values should be absolute Unix timestamps in seconds, not relative durations [6]. Including iat allows you to use the revoke_tokens_issued_before feature to invalidate existing tokens for a user [1][6].

Citations:


Do not give every authenticated user the Stream Chat admin role or an unbounded token.

generateChatToken assigns each caller the global admin role and calls createToken(ctx.auth.user.id) without exp or iat; Stream Chat tokens default to indefinite validity. This makes a copied token usable after the application session ends and grants elevated Chat permissions to every attendee.

Assign the default user role unless application authorization establishes Chat administration. Enforce meeting access with channel membership or channel permissions. Issue short-lived tokens, include iat to enable per-user token revocation, and verify the Chat UI refreshes them.

Proposed direction
-    const token = streamChat.createToken(ctx.auth.user.id);
     await streamChat.upsertUser({
       id: ctx.auth.user.id,
-      role: "admin",
+      role: "user",
     });
+    const now = Math.floor(Date.now() / 1000);
+    const token = streamChat.createToken(
+      ctx.auth.user.id,
+      now + 60 * 60,
+      now,
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generateChatToken: protectedProcedure.mutation(async ({ ctx }) => {
const token = streamChat.createToken(ctx.auth.user.id);
await streamChat.upsertUser({
id: ctx.auth.user.id,
role: "admin",
});
generateChatToken: protectedProcedure.mutation(async ({ ctx }) => {
await streamChat.upsertUser({
id: ctx.auth.user.id,
role: "user",
});
const now = Math.floor(Date.now() / 1000);
const token = streamChat.createToken(
ctx.auth.user.id,
now + 60 * 60,
now,
);
🤖 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 `@src/modules/meetings/server/procedure.ts` around lines 20 - 25, Update
generateChatToken so authenticated callers receive the default Stream Chat user
role, granting admin only through an explicit application-authorization check.
Enforce meeting access through channel membership or permissions, and create
short-lived tokens with both iat and exp claims; ensure the Chat UI refreshes
the token before expiration.

Comment on lines +50 to +52
const transcript = await fetch(existingMeeting.transcriptUrl)
.then((res) => res.text())
.then((text) => JSONL.parse<StreamTranscriptItem>(text))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/modules/meetings --items all --type function
rg -n -C 5 'StreamTranscriptItem|start_ts|stop_ts|start_time|stop_time|JSONL\.parse|getTranscript' src
sed -n '1,100p' src/modules/meetings/ui/components/transcript.tsx

Repository: abhilance/meet-ai

Length of output: 10379


🌐 Web query:

Stream Video server SDK transcript JSONL start_time stop_time schema

💡 Result:

Stream Video (getstream.io) provides transcriptions as a JSONL (JSON Lines) file, where each line represents a speech fragment [1][2]. The schema for each JSON object in the file consists of the following fields [1][3]: - type: A string indicating the type of entry (e.g., "speech"). - start_time: An ISO-8601 formatted timestamp indicating when the speech fragment began. - stop_time: An ISO-8601 formatted timestamp indicating when the speech fragment ended. - speaker_id: A string identifying the speaker. - text: A string containing the transcribed text. Example of a JSONL entry: {"type":"speech", "start_time": "2024-02-28T08:18:18.061031795Z", "stop_time":"2024-02-28T08:18:22.401031795Z", "speaker_id": "Sacha_Arbonel", "text": "hello"} Please note that Stream Video and VideoSDK (videosdk.live) are distinct services with different APIs; the information above is specific to Stream Video (getstream.io) documentation [1][4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '29,70p' src/modules/meetings/server/procedure.ts
sed -n '13,65p' src/inngest/function.ts
node - <<'JS'
const values = [7000, 120000, 3720000];
for (const v of values) {
  const d = new Date(0, 0, 0, 0, 0, 0, v);
  const mm = String(d.getUTCHours()*60 + d.getUTCMinutes()).padStart(2, "0");
  const ss = String(d.getUTCSeconds()).padStart(2, "0");
  console.log(`${v}ms -> ${mm}:${ss}`);
}
JS

Repository: abhilance/meet-ai

Length of output: 3209


Reject Stream Video transcript entries with missing timestamp fields.

Stream Video JSONL entries provide start_time and stop_time, but StreamTranscriptItem expects start_ts and stop_ts. Because JSONL.parse<StreamTranscriptItem> casts without validation, absent fields become undefined; src/modules/meetings/ui/components/transcript.tsx then uses undefined as the React key and date operand, so the transcript rows do not render. Require the provider timestamp fields on parsed rows and reject rows that do not have them before returning the transcript.

📍 Affects 2 files
  • src/modules/meetings/server/procedure.ts#L50-L52 (this comment)
  • src/modules/meetings/ui/components/transcript.tsx#L43-L58
🤖 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 `@src/modules/meetings/server/procedure.ts` around lines 50 - 52, Update the
transcript parsing flow in the server procedure around JSONL.parse so each
Stream Video row is validated for required start_time and stop_time fields,
rejecting invalid rows before returning the transcript and mapping those
provider fields to StreamTranscriptItem’s start_ts and stop_ts. In
transcript.tsx, retain the existing rendering behavior; no direct change is
required there because server-side validation and mapping prevent undefined keys
or date operands. Affected sites: src/modules/meetings/server/procedure.ts lines
50-52 require the parsing and validation fix;
src/modules/meetings/ui/components/transcript.tsx lines 43-58 require no direct
change.

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