feat: add Saturn Exa; a real-time AI meeting copilot using Smallest AI STT and Exa#23
feat: add Saturn Exa; a real-time AI meeting copilot using Smallest AI STT and Exa#23malikaa-27 wants to merge 6 commits into
Conversation
Saturn is a real-time AI meeting intelligence app built with Next.js. It transcribes your microphone via Smallest AI Pulse STT, detects questions automatically, searches Exa for answers, and generates full meeting notes with Claude when the meeting ends. Features: - Live transcription via Smallest AI Pulse STT (5s rolling chunks) - Auto question detection + Exa semantic web search - Claude-powered insight summaries shown in a side panel - Push-to-talk Tab search for manual queries - End-of-meeting summary: decisions, action items, topics - Chrome extension for Google Meet (captions + tab audio) - Responsive layout — works as a Chrome side panel alongside Meet
|
@malikaa-27 is attempting to deploy a commit to the developer-2074's projects Team on Vercel. A member of the Team first needs to authorize it. |
| export async function POST(req: NextRequest) { | ||
| try { | ||
| const { text, segments } = await req.json(); | ||
|
|
||
| if (!text || typeof text !== "string") { | ||
| return NextResponse.json({ error: "text is required" }, { status: 400, headers: CORS }); | ||
| } | ||
|
|
||
| const rawSpeaker: string | undefined = segments?.[0]?.speaker; | ||
| const speaker = rawSpeaker ?? "You"; | ||
|
|
||
| const segment: TranscriptSegment = { | ||
| id: `seg-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, | ||
| speaker, | ||
| speakerColor: speakerColor(speaker), | ||
| text: text.trim(), | ||
| timestamp: new Date().toLocaleTimeString("en-US", { hour12: false }), | ||
| isQuestion: text.trimEnd().endsWith("?"), | ||
| isHighlighted: false, | ||
| words: [], | ||
| }; | ||
|
|
||
| transcriptBridge.push(segment); | ||
|
|
||
| return NextResponse.json({ ok: true }, { headers: CORS }); | ||
| } catch { | ||
| return NextResponse.json({ error: "Invalid request" }, { status: 400, headers: CORS }); | ||
| } |
There was a problem hiding this comment.
Correctness: The POST endpoint has no authentication or API key validation — any external actor can push arbitrary transcript segments to all connected SSE clients by calling this endpoint directly, enabling data injection attacks.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In file `speech-to-text/saturn-meeting-copilot/app/api/transcript/push/route.ts`, the POST handler at line 23 accepts requests from anyone (CORS is set to `*`). Add authentication/authorization — for example, verify a shared secret or bearer token in the request headers before processing the body. Without this, any caller can inject transcript segments into all active SSE sessions.
| )} | ||
|
|
||
| {/* Sources */} |
There was a problem hiding this comment.
Correctness: source.urlis rendered directly as anhrefwithout sanitization — ajavascript:` URL would execute arbitrary script when clicked, enabling XSS.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In speech-to-text/saturn-meeting-copilot/components/insights/InsightPanel.tsx around line 248-250, the `source.url` value is used directly as an anchor `href` without validation. This allows `javascript:` protocol URLs to execute XSS. Add a check before rendering: only allow URLs that start with `http://` or `https://`, otherwise fall back to `#` or omit the href. Example fix: `href={/^https?:\/\//i.test(source.url) ? source.url : '#'}`.
| { targetTabId: sender.tab.id }, | ||
| (streamId) => { |
There was a problem hiding this comment.
Correctness: sender.tab
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In speech-to-text/saturn-meeting-copilot/extensions/google-meet/background.js, lines 18-19, `sender.tab` can be undefined when a message is sent from a non-tab context. Add a guard before accessing `sender.tab.id`: check `if (!sender.tab)` and call `sendResponse({ error: 'No tab context available' }); return;` before the `chrome.tabCapture.getMediaStreamId` call.
| { | ||
| variants: { | ||
| variant: { | ||
| default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", |
There was a problem hiding this comment.
Correctness: The class [a]:hover:bg-primary/80 uses an arbitrary variant selector [a] which targets an ancestor <a> element — this is almost certainly a typo for hover:bg-primary/80, meaning the default button never shows a hover background change when used as a standalone button.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In file `speech-to-text/saturn-meeting-copilot/components/ui/button.tsx`, line 13, the `default` variant class string contains `[a]:hover:bg-primary/80`. This is an arbitrary Tailwind variant that only applies when the element is a descendant of an `<a>` tag, which is likely unintended. Replace `[a]:hover:bg-primary/80` with `hover:bg-primary/80` so the hover style applies to all default buttons.
| const segment: TranscriptSegment = { | ||
| id: `seg-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, | ||
| speaker, | ||
| speakerColor: speakerColor(speaker), |
There was a problem hiding this comment.
Duplicate Code:
This function speakerColor duplicates existing code.
📍 Original Location:
speech-to-text/saturn-meeting-copilot/hooks/useGoogleMeetTranscript.ts:25-29
Function: speakerColor
💡 Recommendation:
Extract speakerColor and the SPEAKER_COLORS constant into a shared utility module (e.g., lib/speakerColor.ts) and import it in both transcript/push/route.ts and hooks/useGoogleMeetTranscript.ts.
Consider importing and reusing the existing function instead of duplicating the logic.
|
copied from slack chat: the pr is good overall but i have few nitpicks/suggestions: recommended fixs: also, let's keep it in you can use the following for the Exa + Smallest AI
Integrations combining Exa semantic web search with Smallest AI's voice and audio APIs.
Projects
Saturn — AI Meeting Copilot
Real-time meeting intelligence app that transcribes audio with Smallest AI Pulse STT, detects questions in
conversation, and automatically searches the web via Exa to surface relevant answers — all summarized by Claude in
a live side panel.
Stack: Next.js, Smallest AI Pulse STT, Exa Search, Anthropic Claude
Features:
- Live transcription via Pulse STT
- Auto question detection → Exa semantic search
- AI-synthesized answers in real-time
- Push-to-talk manual search
- Meeting summary generation
- Google Meet Chrome extension
View project →view project should have an url linked to the stt folder where you have added the exa cookbook rest is good to go. |
- Fix comment in research route: "OpenAI" → "Claude" - Fix metadata title: "Saturn for Meetings" → "AI Meeting Intelligence" - Switch model from claude-opus-4-6 to claude-sonnet-4-20250514 in research and summary routes - Add Production Notes section to README - Add integrations/exa/README.md linking to Saturn project
- Switch modal background from bg-zinc-950 to bg-white with proper text colors - Add empty state messages for Key Decisions and Action Items sections - Guard all AudioContext.close() calls with state !== "closed" check
- Replace regex detectQuestion + isResearchWorthy with a single Claude Haiku call (extractResearchQuery) that decides AND extracts the query - Add sliding window (last 5 segments per speaker) so chunked speech is combined before evaluation, catching questions split across chunks - Fix race condition: only mark window IDs after confirmed research, not before — prevents in-flight skip from blocking concurrent questions - Add removeInsight to meetingStore for cleaning up skipped placeholders - Add SOCIAL_PATTERNS regex as a cheap pre-filter for obvious non-research text
Confidence Score: 2/5 - Changes Needed
Files requiring special attention
|
| }; | ||
|
|
||
| export async function OPTIONS() { | ||
| return new NextResponse(null, { status: 204, headers: CORS }); | ||
| } | ||
|
|
||
| export async function POST(req: NextRequest) { | ||
| try { | ||
| const { text, segments } = await req.json(); | ||
|
|
There was a problem hiding this comment.
Correctness: 🔐 The PUSH_TOKEN / x-saturn-token authentication guard has been entirely removed, meaning this endpoint now accepts transcript pushes from any unauthenticated caller on the internet — combined with Access-Control-Allow-Origin: *, any website or script can inject arbitrary transcript segments into active sessions.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In file `speech-to-text/saturn-meeting-copilot/app/api/transcript/push/route.ts`, lines 17-26, the diff removed the SATURN_PUSH_TOKEN-based authentication guard that previously validated the `x-saturn-token` request header before allowing transcript pushes. Restore the token check so that the POST handler rejects requests without a valid token:
1. Re-add `const PUSH_TOKEN = process.env.SATURN_PUSH_TOKEN;` before the OPTIONS handler.
2. Re-add `"x-saturn-token"` to `Access-Control-Allow-Headers` in the CORS object.
3. Re-add the guard at the top of the POST handler body (before the try block):
```ts
if (PUSH_TOKEN && req.headers.get("x-saturn-token") !== PUSH_TOKEN) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401, headers: CORS });
}
This ensures only callers with the correct shared secret can push transcript segments.
</details>
<!-- ai_prompt_end -->
Confidence Score: 1/5 - Blocking IssuesNot safe to merge — this PR introduces a critical unauthenticated endpoint in Key Findings:
Files requiring special attention
|
Saturn — AI Meeting Copilot
Saturn is a full-stack real-time meeting intelligence app built with Next.js that uses Smallest AI Pulse STT for audio transcription.
What it does
Taband speak to search anything directlyStack
Setup
API Keys needed
SMALLEST_API_KEYEXA_API_KEYANTHROPIC_API_KEYEntelligenceAI PR Summary
This PR introduces Saturn Meeting Copilot, a complete Next.js 16 application and companion Google Meet Chrome extension providing real-time AI-powered meeting assistance using Smallest.ai, Exa, and Anthropic Claude.
/api/transcribe(Smallest.ai STT),/api/synthesize(Waves TTS streaming),/api/research(Exa + Claude insight generation),/api/summary(Claude meeting summary),/api/transcript/push(Chrome extension segment ingestion with CORS),/api/transcript/stream(SSE live broadcast)tabCapture, popup with health-check polling, and iframe-based side paneluseGoogleMeetTranscript(SSE + AudioWorklet mic pipeline with WAV encoding, silence detection, and Web Speech API fallback),useExaBot(automatic question detection and research pipeline),useDirectSearch(push-to-talk Tab key recording and search)useMeetingStoremanaging full meeting lifecycle, transcript segments, AI insights, action items, topics, summary, voice/STT settings, and credit balancetranscriptionService(Smallest.ai Pulse STT),voiceService(Waves TTS),exaSearchService(Exa semantic search),researchAgent(question detection +runResearch+buildInsight)TranscriptPanel,InsightPanel,TopicTimeline,MeetingSummaryModal,TopBar,BottomBar,AudioWaveform,ExaBotStatus; shadcn/ui primitives (Button, Badge, Progress, ScrollArea, Separator, Tooltip) on@base-ui/reactTranscriptBridgeEventEmitter singleton onglobalThisfor cross-route pub/sub, demo simulator with word-by-word typing playback, Tailwind v4 with oklch design tokens,next.config.tswith CORS andX-Frame-Options: ALLOWALLConfidence Score: 1/5 - Blocking Issues
Files requiring special attention
speech-to-text/saturn-meeting-copilot/app/api/transcript/push/route.tsspeech-to-text/saturn-meeting-copilot/components/insights/InsightPanel.tsxspeech-to-text/saturn-meeting-copilot/extensions/google-meet/content.jsspeech-to-text/saturn-meeting-copilot/hooks/useGoogleMeetTranscript.tsspeech-to-text/saturn-meeting-copilot/extensions/google-meet/background.jsConfidence Score: 2/5 - Changes Needed
source.urlas an href without sanitization enables XSS via javascript: URLs — another security issueFiles requiring special attention
speech-to-text/saturn-meeting-copilot/app/api/transcript/push/route.tsspeech-to-text/saturn-meeting-copilot/components/insights/InsightPanel.tsxspeech-to-text/saturn-meeting-copilot/extensions/google-meet/background.jsspeech-to-text/saturn-meeting-copilot/components/ui/button.tsx