fix(chat): move Judge AI Assistant to Render backend - #19
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThe client now targets a dedicated judge-chat endpoint. The server validates requests, applies rate limiting, adds a Fitty-specific prompt, forwards recent messages to OpenAI, and returns completion or error responses. ChangesJudge chat flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant JudgeChat
participant JudgeChatRoute
participant OpenAI
JudgeChat->>JudgeChatRoute: POST /api/judge-chat with messages
JudgeChatRoute->>OpenAI: POST chat completions with system prompt and recent messages
OpenAI-->>JudgeChatRoute: completion response
JudgeChatRoute-->>JudgeChat: response content or error
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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 `@components/JudgeChat.tsx`:
- Around line 93-96: Replace the string substitution in the judge chat URL
construction with a shared base URL environment variable that excludes endpoint
paths, then append `/api/judge-chat` explicitly; update the related Vet AI chat
URL usage to use the same base configuration and preserve the fallback behavior
in `JudgeChat`.
- Around line 93-97: Remove the same-origin `/api/judge-chat` fallback in the
judge chat URL construction. Update the base URL logic in the JudgeChat
component to use the configured backend URL when EXPO_PUBLIC_CHAT_API_URL is
unset, or fail fast with a clear error before constructing the request URL.
In `@temporal/server.ts`:
- Around line 240-286: Move the static JUDGE_SYSTEM_PROMPT template literal out
of the app.post request handler and startChatServer function to module scope,
preserving its content and using the module-level constant from the handler.
- Around line 230-234: Strengthen validation in the request handler around the
messages destructuring and the corresponding later validation block: require
every message to be a non-null object with a valid string role and string
content, enforce a reasonable per-message content length cap, and reject
malformed or oversized items with the existing 400 response before forwarding
anything to OpenAI.
- Line 228: Shared limiter usage causes /api/judge-chat traffic to consume the
quota for /api/analyze and /api/chat. Define a dedicated rate limiter and use it
in the app.post('/api/judge-chat', ...) registration, leaving the existing
limiter assigned only to the other endpoints.
- Around line 296-308: Add a 15-second timeout to the OpenAI request in the
/api/judge-chat handler by passing signal: AbortSignal.timeout(15000) in the
options object for the fetch call to https://api.openai.com/v1/chat/completions.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3484651f-f4e6-47f3-bace-85a5a1133b0c
📒 Files selected for processing (2)
components/JudgeChat.tsxtemporal/server.ts
| // Build the judge chat URL from the same base as the Vet AI chat | ||
| const baseUrl = process.env.EXPO_PUBLIC_CHAT_API_URL | ||
| ? process.env.EXPO_PUBLIC_CHAT_API_URL.replace('/api/chat', '/api/judge-chat') | ||
| : '/api/judge-chat'; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
String substitution to derive endpoint URL is fragile.
Deriving /api/judge-chat by string-replacing /api/chat in EXPO_PUBLIC_CHAT_API_URL couples this component to the exact literal format of that env var. Consider introducing a base URL env var (without the endpoint suffix) so endpoint paths are appended rather than substituted, reducing risk if the base URL format changes.
🤖 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 `@components/JudgeChat.tsx` around lines 93 - 96, Replace the string
substitution in the judge chat URL construction with a shared base URL
environment variable that excludes endpoint paths, then append `/api/judge-chat`
explicitly; update the related Vet AI chat URL usage to use the same base
configuration and preserve the fallback behavior in `JudgeChat`.
561b6c3 to
aff2b21
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@temporal/server.ts`:
- Around line 283-337: Protect the unauthenticated judge-chat endpoint from
distributed spend abuse by adding a global daily request or token budget around
the handler registered by app.post('/api/judge-chat', judgeLimiter, ...). Track
usage atomically, reject requests once the configured cap is reached, and reset
the budget daily; alternatively configure equivalent OpenAI usage limits and
billing alerts as a backstop.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2f492b15-f0ae-4e36-8acb-9dfcd411c4b6
📒 Files selected for processing (2)
components/JudgeChat.tsxtemporal/server.ts
| app.post('/api/judge-chat', judgeLimiter, async (req, res) => { | ||
| try { | ||
| const { messages } = req.body; | ||
|
|
||
| if (!messages || !Array.isArray(messages)) { | ||
| return res.status(400).json({ error: 'Invalid messages format' }); | ||
| } | ||
|
|
||
| // Validate each message object | ||
| for (const msg of messages) { | ||
| if (!msg || typeof msg.role !== 'string' || typeof msg.content !== 'string' || msg.content.length > 1000) { | ||
| return res.status(400).json({ error: 'Invalid message: each must have role (string) and content (string, max 1000 chars)' }); | ||
| } | ||
| } | ||
|
|
||
| if (!process.env.OPENAI_API_KEY) { | ||
| return res.status(500).json({ error: 'Server configuration error' }); | ||
| } | ||
|
|
||
| const openAiMessages = [ | ||
| { role: 'system', content: JUDGE_SYSTEM_PROMPT }, | ||
| ...messages.slice(-6).map((msg: { role: string; content: string }) => ({ | ||
| role: msg.role === 'user' ? 'user' : 'assistant', | ||
| content: msg.content | ||
| })) | ||
| ]; | ||
|
|
||
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, | ||
| }, | ||
| body: JSON.stringify({ | ||
| model: 'gpt-4o-mini', | ||
| messages: openAiMessages, | ||
| max_tokens: 300, | ||
| temperature: 0.3, | ||
| }), | ||
| signal: AbortSignal.timeout(15000), | ||
| }); | ||
|
|
||
| const data = await response.json(); | ||
|
|
||
| if (!response.ok) { | ||
| console.error('OpenAI error:', data); | ||
| return res.status(500).json({ error: 'Failed to communicate with AI' }); | ||
| } | ||
|
|
||
| return res.json({ response: data.choices[0].message.content }); | ||
| } catch (error: unknown) { | ||
| console.error('Judge Chat API error:', error); | ||
| return res.status(500).json({ error: 'An internal error occurred. Please try again.' }); | ||
| } | ||
| }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔵 Trivial
Consider a spend safeguard for this unauthenticated OpenAI endpoint. The per-IP limiter (10/min) caps single-source abuse, but /api/judge-chat is public and each call incurs OpenAI cost; distributed/rotating-IP traffic can still accumulate spend. Consider a global daily request/token cap or OpenAI usage-limit/billing alerts as a backstop.
🤖 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 `@temporal/server.ts` around lines 283 - 337, Protect the unauthenticated
judge-chat endpoint from distributed spend abuse by adding a global daily
request or token budget around the handler registered by
app.post('/api/judge-chat', judgeLimiter, ...). Track usage atomically, reject
requests once the configured cap is reached, and reset the budget daily;
alternatively configure equivalent OpenAI usage limits and billing alerts as a
backstop.
🚀 What is this PR?
Fixes the Judge AI Assistant chatbot which was returning 500 errors because Expo API Routes don't work with Vercel static exports. Moves the endpoint to the Express backend on Render.
🛠️ Key Changes
temporal/server.ts): Added/api/judge-chatendpoint with dedicated rate limiter (10 req/min), message validation (role + content + 1000 char cap), 15s request timeout via AbortSignal, and module-scoped system prompt.components/JudgeChat.tsx): Updated to call Render backend viaEXPO_PUBLIC_CHAT_API_URL. Fails fast with user-facing message if backend URL is not configured.📸 Screenshot
N/A (same floating chatbot UI — now backed by a working endpoint)
✅ Checklist
OPENAI_API_KEYenv varSummary by CodeRabbit