Skip to content

Latest commit

 

History

History
121 lines (90 loc) · 5.54 KB

File metadata and controls

121 lines (90 loc) · 5.54 KB

Architecture

High-Level Diagram

Here's how everything connects together:

Architecture Diagram

Basically the flow is: user types in browser → frontend sends to backend → backend calls gemini → gemini sends back json → backend sends to frontend → frontend shows results

Frontend Section

The frontend is built with React + TypeScript using Vite. The main file is App.tsx and it does a bunch of stuff:

State Management:

  • inputText - stores what the user types
  • tone - which tone they picked (Professional, Friendly, Concise, or More Polite)
  • loading - shows spinner while waiting for api
  • error - displays error messages if something goes wrong
  • result - stores the response from the backend
  • showValidation - for inline validation errors
  • copied - tracks if they copied to clipboard

User Interactions:

  • Textarea for entering the message
  • Radio buttons for selecting tone (took me a while to style these nicely)
  • Submit button that calls the api
  • Keyboard shortcut - ctrl+enter or cmd+enter to submit (this was a nice touch)
  • Copy button for the rewritten message

API Calls:

  • Sends POST request to http://localhost:3000/api/message
  • Body is { text: string, tone: string }
  • Handles errors (400, 500, network failures)
  • Shows loading state while waiting

UI Rendering:

  • Input form with textarea and tone selector
  • Results section that only shows when we have data:
    • Side by side comparison (original on left, rewritten on right)
    • Analysis card with:
      • Perceived tone (what the ai thinks the original sounds like)
      • Clarity score out of 100
      • Risk flags (potential issues, or "no risks" if empty)
    • Explanation card with bullet points about what changed

I used inline styles instead of a separate css file cause it keeps everything in one place and makes it easier to see what styles apply to what. Plus it's simpler for a small project like this.

Backend Section

The backend is a Node.js Express server in server/index.js. It's pretty straightforward:

Middleware Setup:

  • CORS enabled for http://localhost:5173 (my vite dev server)
  • JSON body parsing so i don't have to parse manually
  • dotenv to load the api key from .env file

Main Endpoint: /api/message (POST)

This is where all the magic happens. Here's what it does step by step:

  1. Input Validation:

    • Checks if text exists and isn't empty (returns 400 if invalid)
    • Validates tone is one of the 4 allowed values
    • Defaults to "Professional" if they send something weird
  2. Build the Prompt:

    • Gemini doesn't use separate system/user messages like openai
    • So i combine everything into one prompt
    • Includes the json schema, original message, target tone, and instructions
    • Tells it to analyze tone/clarity/risks, rewrite, and explain changes
  3. Call Gemini API:

    • Using gemini-2.5-flash cause it's fast and cheap
    • Tried gemini-pro first but it was too slow
    • Endpoint: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent
    • Sends the prompt with temperature 0.7 (not too creative, not too boring)
    • Uses responseMimeType: 'application/json' to force json output
  4. Parse and Validate:

    • Gemini wraps the response in candidates[0].content.parts[0].text
    • Had to debug this structure for a while
    • Parse the json from that text
    • Check that all required fields are present (rewritten, analysis, explanation)
    • Return 500 error if parsing fails or structure is wrong
  5. Return Response:

    • Send the parsed json back to frontend
    • All errors are caught and returned as 500 with error messages

The backend is stateless - no database, no sessions. Each request is independent. This keeps it simple and focused on the ai feature.

Design Choices & Trade-offs

Single REST endpoint instead of GraphQL: For a small app like this, REST is way simpler. GraphQL would be overkill and add complexity i don't need.

Strict JSON schema: I defined exactly what the response should look like. This makes the frontend/backend integration predictable. The frontend knows exactly what fields to expect, so no need for defensive checks everywhere.

No database or auth: This is a demo project, so i kept it simple. No saving messages, no user accounts. Just paste, rewrite, done. Could add those later but wanted to focus on the ai integration first.

Inline styles in React: I know css modules or styled-components would be more "professional" but inline styles keep everything together. For a small project this is fine. Plus it's easier to see what styles apply to what component.

Error handling: Backend returns { error: string } on errors. Frontend shows it in a red banner. Simple but effective. Could add more sophisticated error handling but this works for now.

Model choice (gemini-2.5-flash): Chose this cause it's fast and cheap. Good quality too. Could easily switch to gemini-pro or another model by changing one line, but this one works great.

Performance optimizations: Added a bunch of stuff to make scrolling smooth:

  • Hardware acceleration with transform: translateZ(0)
  • CSS containment to isolate rendering
  • Memoized styles to prevent re-creation
  • Optimized transitions (only animate what needs to animate)
  • Fixed background using pseudo-element instead of background-attachment: fixed

The architecture is intentionally simple. It's a focused student project that demonstrates ai integration without unnecessary complexity. Everything is in one repo, easy to understand, and easy to run locally.