Add Upstash rate limiting to AI chat endpoints with error toast notifications (#14)
## Add AI rate limiting and improve API error handling
### Rate Limiting
Adds per-user rate limiting for AI endpoints using Upstash Redis and `@upstash/ratelimit`. Users are limited to **10 requests per minute** and **120 requests per hour**. When a limit is exceeded, the API returns a `429` response with a human-readable retry message (e.g. "Rate limit exceeded. Please try again in 2 minutes.") along with standard `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. Rate limiting is enforced before inserting user messages, so messages are only persisted when the request is allowed through.
Also fixes a bug where the title generation condition checked `messages.length === 1` instead of `messages.length === 0`, and corrects the message list passed to `streamChat` to include the new user message that was previously being omitted.
### API Error Handling
Introduces a shared `ApiError` class and `expectApiResponse` helper that extracts error messages from API responses (preferring the `message` field from JSON bodies) and throws typed errors. All hooks now use this helper instead of inline `!response.ok` checks.
The `QueryClient` is configured with global `QueryCache` and `MutationCache` error handlers that display `ApiError` messages as toast notifications via `sonner`, giving users visible feedback when API calls fail, including rate limit errors.
### Environment
Adds `.env.example` files for both `apps/api` and `apps/web` documenting the required environment variables, including the new `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` variables. The `.gitignore` is updated to allow `.env.example` files to be committed.
authored by