Skip to content

fix(chat): move Judge AI Assistant to Render backend - #19

Open
gonzagramaglia wants to merge 1 commit into
mainfrom
fix/judge-chat-render
Open

fix(chat): move Judge AI Assistant to Render backend#19
gonzagramaglia wants to merge 1 commit into
mainfrom
fix/judge-chat-render

Conversation

@gonzagramaglia

@gonzagramaglia gonzagramaglia commented Jul 10, 2026

Copy link
Copy Markdown
Owner

🚀 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

  • Backend (temporal/server.ts): Added /api/judge-chat endpoint with dedicated rate limiter (10 req/min), message validation (role + content + 1000 char cap), 15s request timeout via AbortSignal, and module-scoped system prompt.
  • Frontend (components/JudgeChat.tsx): Updated to call Render backend via EXPO_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

  • Tested on Web
  • Zero new diagnostics
  • Conventional Commits applied
  • Requires Render redeploy + OPENAI_API_KEY env var

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Judge AI Assistant returning 500 errors by moving the endpoint from Expo API Routes (incompatible with Vercel static export) to the Express backend on Render.
    • Added dedicated rate limiter (10 req/min), per-message validation (role + content type checks, 1000 char limit), and 15s AbortSignal timeout for the OpenAI request.
    • Frontend now fails fast with a user-facing message when the backend URL is not configured, instead of attempting a broken same-origin request.

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fitty Ready Ready Preview, Comment Jul 10, 2026 9:28pm

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The 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.

Changes

Judge chat flow

Layer / File(s) Summary
Judge chat backend route
temporal/server.ts
Adds POST /api/judge-chat with rate limiting, message validation, a Fitty-specific system prompt, recent-message normalization, OpenAI integration, timeout handling, and error responses.
Judge chat client endpoint wiring
components/JudgeChat.tsx
Derives the judge-chat URL from EXPO_PUBLIC_CHAT_API_URL and shows an unavailable-backend assistant message when the variable is missing.

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: migrating the Judge AI Assistant to the Render backend.
✨ 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 fix/judge-chat-render

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

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 83743a3 and 561b6c3.

📒 Files selected for processing (2)
  • components/JudgeChat.tsx
  • temporal/server.ts

Comment thread components/JudgeChat.tsx Outdated
Comment on lines +93 to +96
// 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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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`.

Comment thread components/JudgeChat.tsx
Comment thread temporal/server.ts Outdated
Comment thread temporal/server.ts
Comment thread temporal/server.ts Outdated
Comment thread temporal/server.ts

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 561b6c3 and aff2b21.

📒 Files selected for processing (2)
  • components/JudgeChat.tsx
  • temporal/server.ts

Comment thread temporal/server.ts
Comment on lines +283 to +337
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.' });
}
});

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 | 🔵 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.

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