Add archived channel chat - #7
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds an authenticated channel archive workspace with S3 transcript parsing, provider-backed streaming chat, request validation, rate and concurrency limits, citations, responsive layouts, archive links, and analytics. ChangesArchived channel chat
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ChannelWorkspace
participant ChatRoute
participant AuthRateLimit
participant ChannelTranscript
participant LanguageModel
ChannelWorkspace->>ChatRoute: POST chat request
ChatRoute->>AuthRateLimit: Check rate and concurrency limits
ChatRoute->>ChannelTranscript: Retrieve archived transcript
ChatRoute->>LanguageModel: Generate response with archive evidence
LanguageModel-->>ChatRoute: Stream citation-aware response
ChatRoute-->>ChannelWorkspace: Return UI message stream
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds a new “archived channel chat” flow that lets authenticated members open a single archived HTML channel, view it read-only, and ask BYOK (bring-your-own-key) AI questions against that channel with streamed responses and message-level citations. It also introduces Vercel Web Analytics.
Changes:
- Adds
/channelUI workspace that embeds the archived HTML in a sandboxed iframe and provides a client-side chat UI that calls a new/api/chatroute. - Implements server-side transcript extraction from archived HTML exports stored in S3, plus chat request validation, rate limiting, and concurrency limits.
- Adds AI SDK providers (OpenAI/Anthropic/Google), Cheerio for HTML parsing, and Vercel Analytics; bumps Node engine requirement to
>=22.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks new AI SDK / analytics / parsing dependencies and updated transitive graph. |
| package.json | Adds AI/analytics/parser deps and sets engines.node >= 22 to match new packages. |
| app/page.tsx | Adds “Ask” link for HTML channel entries and adjusts card structure/classes. |
| app/layout.tsx | Wires in @vercel/analytics/next <Analytics />. |
| app/globals.css | Styles the new channel workspace + chat UI and updates file-card layout. |
| app/chatConfig.ts | Centralizes provider/model options and provider type guards for chat. |
| app/channel/page.tsx | Adds the /channel route entry point and passes the selected key into the workspace. |
| app/channel/ChannelWorkspace.tsx | Implements the client-side channel viewer + BYOK chat UX and request shaping. |
| app/api/shared/channelTranscriptParser.ts | Parses archived HTML exports into a normalized, bounded transcript with message IDs. |
| app/api/shared/channelTranscript.ts | Reads the channel HTML from S3 with size/time bounds and converts it to a transcript. |
| app/api/shared/channelArchive.ts | Validates a requested key exists in the archived HTML collection in S3. |
| app/api/shared/boundedStream.ts | Adds a reusable bounded stream reader for request/S3 body size + timeout caps. |
| app/api/shared/authRateLimit.ts | Extends existing auth rate limiting with chat budgets and concurrency slots. |
| app/api/chat/route.ts | Adds the server-side streaming chat endpoint with membership enforcement and citations metadata. |
| app/api/chat/request.ts | Validates and bounds chat request payloads (provider/model/key/channelKey/messages). |
| app/api/chat/provider.ts | Creates provider-specific language models from the BYOK API key. |
| app/api/channel/route.ts | Restricts signed URL issuance to archived HTML channels and disables caching via response headers. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (8)
app/api/shared/channelArchive.ts (1)
10-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider
HeadObjectCommandfor the existence check.
ListObjectsV2CommandwithPrefix: keyneedss3:ListBucketand returns object metadata for neighbours. AHeadObjectCommandfor the exact key answers the same question with one lookup and narrower IAM scope. Also note the!object.Key.endsWith("/")guard is unreachable:object.Key === keyalready guarantees the.htmlsuffix checked on Line 8.♻️ Proposed refactor
-import { ListObjectsV2Command } from "`@aws-sdk/client-s3`"; +import { HeadObjectCommand } from "`@aws-sdk/client-s3`"; import { getS3Bucket, s3Client } from "../../config"; export async function isArchivedHtmlChannel(key: string, signal: AbortSignal) { if (!key.toLowerCase().endsWith(".html")) return false; - const result = await s3Client.send( - new ListObjectsV2Command({ - Bucket: getS3Bucket(), - MaxKeys: 2, - Prefix: key, - }), - { abortSignal: signal }, - ); - - return Boolean( - result.Contents?.some( - (object) => object.Key === key && !object.Key.endsWith("/"), - ), - ); + try { + await s3Client.send( + new HeadObjectCommand({ Bucket: getS3Bucket(), Key: key }), + { abortSignal: signal }, + ); + return true; + } catch (error) { + if ((error as { name?: string }).name === "NotFound") return false; + throw error; + } }🤖 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 `@app/api/shared/channelArchive.ts` around lines 10 - 23, Replace the ListObjectsV2Command-based existence check with a HeadObjectCommand targeting the exact key, preserving the existing abort signal and bucket configuration. Update the surrounding logic to return true when the head request succeeds and false when the object is absent, and remove the unnecessary Contents iteration and trailing-slash guard.app/api/chat/request.ts (1)
89-106: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winEnforce the per-provider model allowlist declared in
app/chatConfig.ts.
MODEL_ID_PATTERNaccepts any syntactically valid identifier.CHAT_PROVIDERS[provider].modelsdeclares the supported set, but the server never checks it. A client can therefore request any model string for the selected provider, which defeats the allowlist and can produce provider errors or unexpected output sizes that the 400-token cap and 250-word instruction assume. Validate the model against the provider entry.♻️ Proposed refactor
-import { isInferenceProvider, type InferenceProvider } from "../../chatConfig"; +import { + CHAT_PROVIDERS, + isInferenceProvider, + type InferenceProvider, +} from "../../chatConfig"; @@ if ( !isInferenceProvider(candidate.provider) || @@ !MODEL_ID_PATTERN.test(model) || + !(CHAT_PROVIDERS[candidate.provider].models as readonly string[]).includes( + model, + ) || !messages ) { return null; }🤖 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 `@app/api/chat/request.ts` around lines 89 - 106, Update the validation condition in the request handler to verify that the trimmed model is included in the selected provider’s `CHAT_PROVIDERS[provider].models` allowlist, in addition to passing `MODEL_ID_PATTERN`. Reject the request when the provider entry does not support the requested model, while preserving the existing validation behavior.app/api/chat/route.ts (1)
81-94: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftThe full transcript is re-sent on every turn.
buildArchiveEnvelopeserializes up toMAX_CHANNEL_TRANSCRIPT_CHARACTERS(320,000) of transcript into the first user message. The route rebuilds it from S3 for each request, so a 12-turn conversation performs 12 object reads, 12 HTML parses, and 12 full-context uploads. Latency and the member's provider cost grow linearly with turns. Consider caching the parsed transcript per channel key with a short TTL, and consider provider prompt caching, which both Anthropic and OpenAI expose for repeated large prefixes.Also applies to: 194-203
🤖 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 `@app/api/chat/route.ts` around lines 81 - 94, Update the transcript handling around buildArchiveEnvelope and its route call sites to avoid rebuilding and re-uploading the full transcript on every turn: cache the parsed transcript per channelKey with a short TTL, reuse valid cached data across requests, and investigate enabling the provider’s prompt-caching mechanism for the repeated transcript prefix where supported.app/api/shared/channelTranscriptParser.ts (1)
83-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBuild the transcript label once.
Lines 83-90 and Lines 110-117 construct the same label and line format. The two copies can drift, and the second pass re-walks every message. Collect
transcriptLinein the loop and join it at the end.♻️ Proposed refactor
+function buildTranscriptLine(message: ChannelTranscriptMessage) { + const label = [ + `M:${message.id}`, + message.timestamp ? `time:${message.timestamp}` : undefined, + `author:${message.author}`, + ] + .filter(Boolean) + .join(" | "); + return `[${label}] ${message.content}`; +}- const label = [ - `M:${id}`, - timestamp ? `time:${timestamp}` : undefined, - `author:${lastAuthor}`, - ] - .filter(Boolean) - .join(" | "); - const transcriptLine = `[${label}] ${content}`; + const message: ChannelTranscriptMessage = { + author: lastAuthor, + content, + id, + timestamp, + }; + const transcriptLine = buildTranscriptLine(message); characterCount += transcriptLine.length + 1; if (characterCount > MAX_CHANNEL_TRANSCRIPT_CHARACTERS) { throw new ChannelTranscriptTooLargeError(); } - messages.push({ - author: lastAuthor, - content, - id, - timestamp, - }); + messages.push(message); + lines.push(transcriptLine); }); return { characterCount, messages, - text: messages - .map((message) => { /* duplicated label logic */ }) - .join("\n"), + text: lines.join("\n"), };🤖 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 `@app/api/shared/channelTranscriptParser.ts` around lines 83 - 119, Update the transcript parsing loop to collect each generated transcriptLine alongside messages, then build the returned text by joining those collected lines instead of remapping messages to reconstruct labels. Remove the duplicate label-formatting logic from the return path while preserving character counting, message contents, and newline-separated output.app/api/shared/authRateLimit.ts (2)
117-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueName the chat limits and avoid consuming a member token on a global rejection.
Lines 118 and 121 use inline
12and240, while the concurrency caps above use named constants. Also,checkAuthRateLimitincrements the member counter before the global budget is checked, so a request rejected by the global budget still costs the member one token in the window.♻️ Proposed refactor
+const CHAT_RATE_LIMIT_PER_MEMBER = 12; +const CHAT_GLOBAL_BUDGET = 240; + export function checkChatRateLimit(identity: string) { - const identityBudget = checkAuthRateLimit("chat", identity, 12); + const identityBudget = checkAuthRateLimit( + "chat", + identity, + CHAT_RATE_LIMIT_PER_MEMBER, + ); if (!identityBudget.allowed) return identityBudget; - return checkAuthGlobalBudget("chat", 240); + return checkAuthGlobalBudget("chat", CHAT_GLOBAL_BUDGET); }🤖 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 `@app/api/shared/authRateLimit.ts` around lines 117 - 122, Update checkChatRateLimit to use named constants for both the member limit of 12 and global limit of 240, alongside the existing concurrency-cap constants. Check the global budget before calling checkAuthRateLimit so globally rejected requests do not consume a member token, while preserving the existing budget return behavior.
124-150: 🩺 Stability & Availability | 🔵 TrivialThe concurrency counters are process-local.
activeChatRequestsandactiveChatRequestsByMemberlive in module scope. On a serverless or multi-instance deployment each instance keeps its own counters, so the effective global cap is 12 multiplied by the instance count. The same applies torateLimitEntriesandglobalBudgets. If the caps must hold across the fleet, back them with shared state, for example Redis or Vercel KV. If the caps are only a per-instance guard, record that intent in a comment.A second operational note: the counter only decreases when the returned callback runs. If a future code path returns before calling it, the slot leaks for the process lifetime and chat stays unavailable on that instance. A timestamp on each slot plus a sweep of stale slots would make the leak self-healing.
🤖 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 `@app/api/shared/authRateLimit.ts` around lines 124 - 150, Update acquireChatConcurrencySlot and the related rateLimitEntries/globalBudgets state so concurrency and rate limits use shared, atomic storage such as Redis or Vercel KV across instances. Add expiration or stale-slot sweeping so unreleased slots self-heal; otherwise explicitly document that these limits are intentionally per-instance guards.app/globals.css (1)
1222-1241: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAnchor the jump control to the composer.
.chat-jump-latestuses a fixedbottom: 8.25remthat matches the current composer height. The composer textarea setsresize: verticalat Line 1248. When a user enlarges the textarea, the composer grows and the button overlaps the composer or floats inside the message list. Position the button relative to the composer instead of the session container.💅 Proposed approach
.chat-composer { position: relative; margin: 0 1rem; } + +.chat-composer > .chat-jump-latest { + bottom: calc(100% + 0.5rem); +}Move the
.chat-jump-latestelement inside the.chat-composerelement inapp/channel/ChannelWorkspace.tsx, then drop the fixedbottom: 8.25remvalue.🤖 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 `@app/globals.css` around lines 1222 - 1241, Move the `.chat-jump-latest` element inside the `.chat-composer` element in `ChannelWorkspace.tsx`, then update `.chat-jump-latest` in `app/globals.css` to remove the fixed `bottom: 8.25rem` positioning and anchor it relative to the composer while preserving the existing styling.app/channel/ChannelWorkspace.tsx (1)
103-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMatch on stable error codes instead of message text.
The branches match English substrings from server responses. Any wording change on the server silently reroutes an authentication or terminal error into the generic retry branch. The user then sees a retry button for an unrecoverable state. Return a machine-readable
codefield from/api/chatand branch on that value. Keep the text match as a fallback.🤖 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 `@app/channel/ChannelWorkspace.tsx` around lines 103 - 136, Update the error classification logic around the message normalization branches to prefer a machine-readable code returned by /api/chat, using distinct codes for authentication, terminal, and retryable errors; retain the existing serverMessage text matching only as a fallback when no code is available. Ensure unrecoverable coded errors still return the archive or none actions, while retryable codes return retry.
🤖 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 `@app/api/chat/route.ts`:
- Line 31: Adjust the timeout budget in the route’s stream configuration and
related timeout handling so the worst-case body read, archive lookup, object
read, and streaming duration remain below maxDuration. Update totalMs and any
corresponding remaining-budget calculation near the stream setup, preserving the
intended timeout error instead of allowing platform termination.
In `@app/api/shared/boundedStream.ts`:
- Around line 24-41: Track whether the read loop completed normally in the
bounded stream logic around reader.read(), setting the completion flag when done
is observed. Before the final options.createTimeoutError() check, require that
the loop did not complete, so a timer firing after the final read cannot turn a
fully consumed body into a timeout.
In `@app/api/shared/channelTranscriptParser.ts`:
- Around line 75-76: Update the fallback assignment in the message ID parsing
flow around rawId and id so invalid or missing data-message-id values use a
distinct prefixed identifier instead of String(index + 1). Preserve valid
numeric IDs unchanged and ensure fallback IDs remain unique per message for
citation resolution.
- Around line 42-49: Update findTimestamp so candidates must match a strict
date-like format before Date.parse is attempted, preventing values such as
avatar, emoji, or reaction-count titles from being selected. Preserve the
existing length limit and finite-date validation, and continue returning the
first valid timestamp candidate.
In `@app/channel/ChannelWorkspace.tsx`:
- Around line 481-487: Update the explanatory copy in ChannelWorkspace to derive
the displayed question-and-answer count from CHAT_HISTORY_MESSAGE_LIMIT instead
of hardcoding 12, accounting for the limit’s message-to-pair relationship so the
text stays accurate when the constant changes.
- Around line 596-611: Remove the nested live-region semantics from the thinking
indicator near `chat-messages`: either move that indicator outside the
`.chat-messages` container or remove its `role="status"` and `aria-live`
attributes. Keep only the outer `role="log"` region, preserving the indicator’s
visual and loading behavior.
- Around line 284-295: Update the access-loss reset effect in ChannelWorkspace
around resetChat to call the useChat stop() function before clearing
activeSettings, the API key, consent, and messages. Ensure any in-flight stream
is aborted before the reset state is applied, while preserving the existing
loading and verified-access guards.
In `@app/chatConfig.ts`:
- Around line 21-25: Update isInferenceProvider to validate that the string is
an own property of CHAT_PROVIDERS rather than using the prototype-chain-aware in
operator, rejecting values such as "constructor" and "toString" while preserving
valid provider checks.
- Around line 1-17: Update the Google entry in CHAT_PROVIDERS so defaultModel
uses a currently supported, non-retiring Gemini model instead of
gemini-2.5-flash; ensure the selected default is included in that provider’s
models list and preserve existing user-selected model compatibility.
In `@app/globals.css`:
- Around line 1157-1185: Update the .chat-thinking-dots > span styles to respect
prefers-reduced-motion by adding a reduced-motion media query that disables the
chat-thinking-dot animation for users who request less motion.
In `@app/layout.tsx`:
- Line 12: Update the Analytics integration in app/layout.tsx to prevent Vercel
Web Analytics from recording the private key query parameter on /channel URLs:
configure its beforeSend hook to remove or redact key while preserving other
analytics data, or exclude Analytics for that route. Ensure page-view URLs never
retain the S3 key.
---
Nitpick comments:
In `@app/api/chat/request.ts`:
- Around line 89-106: Update the validation condition in the request handler to
verify that the trimmed model is included in the selected provider’s
`CHAT_PROVIDERS[provider].models` allowlist, in addition to passing
`MODEL_ID_PATTERN`. Reject the request when the provider entry does not support
the requested model, while preserving the existing validation behavior.
In `@app/api/chat/route.ts`:
- Around line 81-94: Update the transcript handling around buildArchiveEnvelope
and its route call sites to avoid rebuilding and re-uploading the full
transcript on every turn: cache the parsed transcript per channelKey with a
short TTL, reuse valid cached data across requests, and investigate enabling the
provider’s prompt-caching mechanism for the repeated transcript prefix where
supported.
In `@app/api/shared/authRateLimit.ts`:
- Around line 117-122: Update checkChatRateLimit to use named constants for both
the member limit of 12 and global limit of 240, alongside the existing
concurrency-cap constants. Check the global budget before calling
checkAuthRateLimit so globally rejected requests do not consume a member token,
while preserving the existing budget return behavior.
- Around line 124-150: Update acquireChatConcurrencySlot and the related
rateLimitEntries/globalBudgets state so concurrency and rate limits use shared,
atomic storage such as Redis or Vercel KV across instances. Add expiration or
stale-slot sweeping so unreleased slots self-heal; otherwise explicitly document
that these limits are intentionally per-instance guards.
In `@app/api/shared/channelArchive.ts`:
- Around line 10-23: Replace the ListObjectsV2Command-based existence check with
a HeadObjectCommand targeting the exact key, preserving the existing abort
signal and bucket configuration. Update the surrounding logic to return true
when the head request succeeds and false when the object is absent, and remove
the unnecessary Contents iteration and trailing-slash guard.
In `@app/api/shared/channelTranscriptParser.ts`:
- Around line 83-119: Update the transcript parsing loop to collect each
generated transcriptLine alongside messages, then build the returned text by
joining those collected lines instead of remapping messages to reconstruct
labels. Remove the duplicate label-formatting logic from the return path while
preserving character counting, message contents, and newline-separated output.
In `@app/channel/ChannelWorkspace.tsx`:
- Around line 103-136: Update the error classification logic around the message
normalization branches to prefer a machine-readable code returned by /api/chat,
using distinct codes for authentication, terminal, and retryable errors; retain
the existing serverMessage text matching only as a fallback when no code is
available. Ensure unrecoverable coded errors still return the archive or none
actions, while retryable codes return retry.
In `@app/globals.css`:
- Around line 1222-1241: Move the `.chat-jump-latest` element inside the
`.chat-composer` element in `ChannelWorkspace.tsx`, then update
`.chat-jump-latest` in `app/globals.css` to remove the fixed `bottom: 8.25rem`
positioning and anchor it relative to the composer while preserving the existing
styling.
🪄 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: 5006d636-27c9-4337-86bb-37317b75bc12
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
app/api/channel/route.tsapp/api/chat/provider.tsapp/api/chat/request.tsapp/api/chat/route.tsapp/api/shared/authRateLimit.tsapp/api/shared/boundedStream.tsapp/api/shared/channelArchive.tsapp/api/shared/channelTranscript.tsapp/api/shared/channelTranscriptParser.tsapp/channel/ChannelWorkspace.tsxapp/channel/page.tsxapp/chatConfig.tsapp/globals.cssapp/layout.tsxapp/page.tsxpackage.json
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/channel/ChannelWorkspace.tsx (1)
287-296: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winClear the chat error during an access reset.
The reset clears messages and settings, but it does not clear
error.changeSettingscallsclearError()separately, so the old error can survive a wallet disconnect. After reconnecting, a terminal error can remain visible and keep the new composer disabled. CallclearError()in the reset callback and add it to the effect dependencies.Proposed fix
const resetChat = window.setTimeout(() => { + clearError(); setActiveSettings(null); setApiKey(""); setConsentGiven(false); setMessages([]); }, 0); return () => window.clearTimeout(resetChat); - }, [hasVerifiedAccess, isSessionLoading, setMessages, stop]); + }, [clearError, hasVerifiedAccess, isSessionLoading, setMessages, stop]);🤖 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 `@app/channel/ChannelWorkspace.tsx` around lines 287 - 296, Update the access-reset callback in ChannelWorkspace’s effect to call clearError() alongside resetting messages, settings, API key, and consent state, then add clearError to the effect dependency array.
🤖 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.
Outside diff comments:
In `@app/channel/ChannelWorkspace.tsx`:
- Around line 287-296: Update the access-reset callback in ChannelWorkspace’s
effect to call clearError() alongside resetting messages, settings, API key, and
consent state, then add clearError to the effect dependency array.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0d4cc6f5-e057-446a-ac25-805f307eb01b
📒 Files selected for processing (7)
app/api/chat/route.tsapp/api/shared/boundedStream.tsapp/api/shared/channelTranscriptParser.tsapp/api/shared/memberAuth.tsapp/channel/ChannelWorkspace.tsxapp/chatConfig.tsapp/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- app/chatConfig.ts
- app/api/shared/channelTranscriptParser.ts
- app/api/shared/boundedStream.ts
- app/api/chat/route.ts
Summary
Verification
pnpm lintpnpm exec tsc --noEmitpnpm buildgit diff --checkDeployment note
Enable Web Analytics in the Vercel project dashboard before expecting production data.
Summary by CodeRabbit
New Features
Security & Reliability