feat: multimodal AI event creation with image support #1

Merged
old4ever merged 20 commits from image-parse into main 2026-04-07 15:21:28 -04:00
Owner

Summary

  • Add multimodal AI event creation: Users can attach images (screenshots, flyers) alongside or instead of text prompts for AI-powered calendar event extraction
  • Strengthen input/output validation: Zod schemas with regex MIME validation, binary size checking, and ISO datetime format enforcement on both request and response boundaries
  • Fix all linter and typecheck warnings: Zero errors across bun run typecheck and bun run lint (ESLint + Biome)

Changes

New Features

  • ImagePicker component for file upload (PNG, JPEG, WebP)
  • Image preview with remove button in AIToolbar
  • Drag-and-drop image support in DragDropContainer
  • Multimodal API route with OpenRouter chat API for base64 images
  • Zod schemas for AI event request validation and response parsing

Security & Validation

  • Server-side: regex MIME type validation (data:image/(png|jpeg|webp);base64,...), actual binary size check (≤10MB)
  • Server-side: ISO datetime validation on AI response start/end fields
  • Server-side: generic error messages to client (no raw AI response leakage)
  • Client-side: file type and size validation before base64 encoding
  • DATABASE_URL env var validation with descriptive error instead of non-null assertion

Code Quality

  • Extracted extractJsonFromText to shared src/lib/json-utils.ts
  • Extracted shared image constants to src/lib/constants.ts
  • Refactored 88-line handleAiCreate into focused functions (sendAiRequest, populateEventForm, persistAiEvents)
  • Fixed Promise constructor anti-pattern and setAiLoading race condition
  • Used extractContentFromChatResponse() instead of nested ternary fallthrough
  • Derived CalendarEvent type from AiEventResponseItem
  • Derived ImagePicker variant/size types from buttonVariants via VariantProps
  • Environment-based model config via process.env.AI_MODEL

Tooling

  • Added biome.json with Tailwind CSS directive support
  • Added src/css.d.ts module declaration for TypeScript
  • CSS formatting standardized via Biome
  • All ESLint warnings resolved (unused vars, catch params)
  • All Biome warnings resolved (parseInt radix, array index keys, aria-hidden, non-null assertions)

Files Changed

  • 253 files changed (+39,109 / -415 lines)
  • Most changes are .opencode/ context files and lockfile updates
  • Core feature: ~10 source files

Test Plan

  • bun run typecheck passes with zero errors
  • bun run lint passes with zero errors/warnings
  • Manual: text-only AI event creation
  • Manual: image-only AI event creation
  • Manual: text + image AI event creation
  • Manual: drag-and-drop image onto calendar
  • Manual: oversized/invalid file rejection
## Summary - **Add multimodal AI event creation**: Users can attach images (screenshots, flyers) alongside or instead of text prompts for AI-powered calendar event extraction - **Strengthen input/output validation**: Zod schemas with regex MIME validation, binary size checking, and ISO datetime format enforcement on both request and response boundaries - **Fix all linter and typecheck warnings**: Zero errors across `bun run typecheck` and `bun run lint` (ESLint + Biome) ## Changes ### New Features - `ImagePicker` component for file upload (PNG, JPEG, WebP) - Image preview with remove button in `AIToolbar` - Drag-and-drop image support in `DragDropContainer` - Multimodal API route with OpenRouter chat API for base64 images - Zod schemas for AI event request validation and response parsing ### Security & Validation - Server-side: regex MIME type validation (`data:image/(png|jpeg|webp);base64,...`), actual binary size check (≤10MB) - Server-side: ISO datetime validation on AI response `start`/`end` fields - Server-side: generic error messages to client (no raw AI response leakage) - Client-side: file type and size validation before base64 encoding - `DATABASE_URL` env var validation with descriptive error instead of non-null assertion ### Code Quality - Extracted `extractJsonFromText` to shared `src/lib/json-utils.ts` - Extracted shared image constants to `src/lib/constants.ts` - Refactored 88-line `handleAiCreate` into focused functions (`sendAiRequest`, `populateEventForm`, `persistAiEvents`) - Fixed Promise constructor anti-pattern and `setAiLoading` race condition - Used `extractContentFromChatResponse()` instead of nested ternary fallthrough - Derived `CalendarEvent` type from `AiEventResponseItem` - Derived `ImagePicker` variant/size types from `buttonVariants` via `VariantProps` - Environment-based model config via `process.env.AI_MODEL` ### Tooling - Added `biome.json` with Tailwind CSS directive support - Added `src/css.d.ts` module declaration for TypeScript - CSS formatting standardized via Biome - All ESLint warnings resolved (unused vars, catch params) - All Biome warnings resolved (parseInt radix, array index keys, aria-hidden, non-null assertions) ## Files Changed - 253 files changed (+39,109 / -415 lines) - Most changes are `.opencode/` context files and lockfile updates - Core feature: ~10 source files ## Test Plan - [x] `bun run typecheck` passes with zero errors - [x] `bun run lint` passes with zero errors/warnings - [x] Manual: text-only AI event creation - [x] Manual: image-only AI event creation - [x] Manual: text + image AI event creation - [x] Manual: drag-and-drop image onto calendar - [x] Manual: oversized/invalid file rejection
old4ever added 20 commits 2026-04-07 15:21:08 -04:00
Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
Add @typescript/native-preview for tsgo typechecking, integrate biome
check into the lint script, and add a dedicated typecheck script.
Reformat JSON configs and TypeScript scripts to use consistent
tab indentation, semicolons, and double quotes.
Sort imports alphabetically, convert value imports to type-only where
appropriate, normalize indentation to tabs, and sort exports
alphabetically across UI components, pages, and lib modules.
Move image extensions, MIME types, and size limit into a dedicated
constants module. Extract JSON-from-text parsing into a pure utility
function for reuse across the codebase.
Add regex-based data URL validation for images, compute binary size
from base64 for accurate 10MB limit, enforce datetime strings with
offset for start/end fields, and derive CalendarEvent from the AI
response item type to eliminate field duplication.
Replace inline JSON extraction with the shared json-utils module,
extract chat response content parsing into a dedicated helper, make
the AI model configurable via AI_MODEL env var, and improve error
messages for production safety.
Break down the monolithic handleAiCreate into focused helpers
(sendAiRequest, persistAiEvents, populateEventForm), add client-side
image file validation before upload, and use toast.promise finally
callback for loading state cleanup.
Add unoptimized prop to image preview to support blob URLs, contain
overflow on preview container, replace div with semantic section
element in DragDropContainer with aria-label, and import shared
image constants.
Replace manually duplicated variant/size type literals with
VariantProps<typeof buttonVariants> for type safety and
consistency with the Button component.
old4ever merged commit be70a4579f into main 2026-04-07 15:21:28 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: old4ever/local-cal#1
No description provided.