247 lines
8.4 KiB
Markdown
247 lines
8.4 KiB
Markdown
# English Style Converter — Design Spec
|
|
|
|
**Date:** 2025-04-12
|
|
**Status:** Approved
|
|
|
|
## Overview
|
|
|
|
A web app that takes a normal English sentence and converts it into various English styles and tones using an LLM. Single-page, minimal, light-themed, with an intensity slider for fine control and prompt transparency.
|
|
|
|
## Architecture
|
|
|
|
**Approach:** Simple and Direct — single SvelteKit project with API routes handling LLM calls. No separate backend, no database, no auth.
|
|
|
|
## Tech Stack
|
|
|
|
- **Framework:** SvelteKit (latest)
|
|
- **UI:** Svelte 5 with runes (, )
|
|
- **Language:** TypeScript
|
|
- **Testing:** Vitest (unit + integration)
|
|
- **LLM:** OpenAI-compatible API (Ollama default, any compatible provider)
|
|
- **Styling:** Delegated to UI specialist tools (uncodixfy, stitch MCP). Direction: minimal, clean, light colors only, no dark mode.
|
|
|
|
## Project Structure
|
|
|
|
english-styler/
|
|
src/
|
|
lib/
|
|
styles.ts - Style definitions (categories, subtypes, prompt text)
|
|
llm.ts - OpenAI-compatible client abstraction
|
|
types.ts - Shared TypeScript types
|
|
routes/
|
|
+page.svelte - Main converter UI
|
|
+page.ts - Page load (optional)
|
|
api/
|
|
convert/
|
|
+server.ts - POST endpoint
|
|
app.html - SvelteKit HTML shell
|
|
static/
|
|
.env - LLM config
|
|
svelte.config.js
|
|
vite.config.ts
|
|
tsconfig.json
|
|
package.json
|
|
|
|
## Style System
|
|
|
|
### Types
|
|
|
|
Style:
|
|
id: string (e.g. "sarcastic", "british-polite", "got-kingslanding")
|
|
label: string (e.g. "Sarcastic", "Polite (British)")
|
|
categoryId: string (e.g. "general", "british", "american", "got")
|
|
promptModifier: string (e.g. "Rewrite in a sarcastic, snarky tone with biting wit")
|
|
|
|
StyleCategory:
|
|
id: string
|
|
label: string
|
|
emoji: string
|
|
|
|
ConversionRequest:
|
|
text: string
|
|
styleId: string
|
|
intensity: number (1-5)
|
|
|
|
ConversionResponse:
|
|
original: string
|
|
converted: string
|
|
styleId: string
|
|
intensity: number
|
|
systemPrompt: string (Full system prompt for transparency display)
|
|
userMessage: string (Full user message for transparency display)
|
|
|
|
### Style Categories and Sub-styles
|
|
|
|
| Category | Emoji | Sub-styles |
|
|
|----------|-------|-----------|
|
|
| General | drama | Sarcastic, Formal, Casual, Academic, Poetic, Passive-Aggressive |
|
|
| British Slang | gb | Polite, Formal, Witty, Gentlemanly, Upper Class, Royal, Victorian, Downton Abbey |
|
|
| American Slang | us | New Yorker, Black American Slang, Southern, Redneck |
|
|
| Fun | pirate | Pirate, Shakespearean, Gen Z Slang |
|
|
| Game of Thrones | dragon | Kings Landing, Wildlings, Winterfell |
|
|
| Dystopian | newspaper | Newspeak (Orwellian) |
|
|
|
|
### Intensity Levels
|
|
|
|
| Level | Label | Prompt effect |
|
|
|-------|-------|---------------|
|
|
| 1 | Subtle | lightly hint at a [style] tone |
|
|
| 2 | Moderate | rewrite with a [style] tone |
|
|
| 3 | Strong | rewrite strongly in a [style] style |
|
|
| 4 | Heavy | rewrite completely in [style] - fully commit to the voice |
|
|
| 5 | Maximum | go absolutely all-out [style] - no restraint |
|
|
|
|
Intensity mapping stored in lib/styles.ts, not hardcoded in LLM call.
|
|
|
|
## LLM Abstraction
|
|
|
|
### Configuration (.env only)
|
|
|
|
OPENAI_BASE_URL=http://localhost:11434/v1
|
|
OPENAI_API_KEY=ollama
|
|
OPENAI_MODEL=llama3
|
|
|
|
No UI-based provider config. Server-side only.
|
|
|
|
### Client (lib/llm.ts)
|
|
|
|
Single OpenAI-compatible client using fetch against /v1/chat/completions. Works with Ollama (default) and any OpenAI-compatible API.
|
|
|
|
Returns converted text plus full prompt for transparency display.
|
|
|
|
### System Prompt Template
|
|
|
|
You are an expert English style converter.
|
|
Rewrite the users text {intensityInstruction}.
|
|
{stylePromptModifier}
|
|
Preserve the core meaning but fully transform the voice and tone.
|
|
Output ONLY the converted text - no explanations, no labels, no quotes.
|
|
|
|
## API Endpoint
|
|
|
|
POST /api/convert
|
|
|
|
- Request body: { text, styleId, intensity }
|
|
- Response: { original, converted, styleId, intensity, systemPrompt, userMessage }
|
|
- Validation:
|
|
- text non-empty (after trimming)
|
|
- styleId exists in styles
|
|
- intensity is integer 1-5
|
|
- Errors:
|
|
- 400 for bad input (with human-readable message)
|
|
- 502 for LLM call failure (with friendly message)
|
|
- No retry logic for MVP1
|
|
|
|
## Frontend UI
|
|
|
|
### Layout
|
|
|
|
Single page, centered, minimal. Light palette only.
|
|
|
|
- Title: "English Style Converter" with subtitle
|
|
- Input textarea for text entry
|
|
- Two-step style selector: Category dropdown then Sub-style dropdown
|
|
- Intensity slider (1-5, default 3) with labels "Subtle" to "Maximum"
|
|
- Convert button with loading state
|
|
- Output display area with Copy button
|
|
- Collapsible "Show prompt" section below output revealing system and user prompts
|
|
|
|
### Loading Modal
|
|
|
|
When the user clicks Convert and the LLM request is in flight, a **modal dialog** appears with an animated loading word. This replaces a boring spinner with delightful, personality-driven feedback.
|
|
|
|
**Behavior:**
|
|
- Modal overlays the page content, preventing interaction during conversion
|
|
- Displays one playful verb at a time, cycling every ~2 seconds
|
|
- Each letter of the word animates in sequentially (per-letter animation, inspired by Tobias Ahlin's Moving Letters)
|
|
- Each new word gets a **randomized color** from a curated light palette
|
|
- Each new word gets a **randomized animation style** from a set of entrance animations (slide-up, bounce-in, drop-in, fade-scale, spin-in, spring-from-left, etc.)
|
|
- When the LLM response arrives, the modal dismisses immediately
|
|
|
|
**Loading word list:**
|
|
Bamboozling · Razzmatazzing · Transmogrifying · Alakazamming · Prestidigitating · Metamorphosizing · Enchanting · Voodooing · Witchcrafting · Sorcerizing · Spellcasting · Hocus-pocusing · Incantating · Conjurating · Charmweaving
|
|
|
|
**Animation styles (randomized per word):**
|
|
- Slide up from below
|
|
- Bounce in from above
|
|
- Drop in with gravity
|
|
- Scale up from center
|
|
- Fade + rotate in
|
|
- Spring in from left
|
|
|
|
**Color palette (randomized per word, all light-friendly):**
|
|
A set of vibrant but light-appropriate accent colors (coral, teal, violet, amber, emerald, rose, sky blue)
|
|
|
|
This is a signature UX moment — the loading modal should feel magical and fun.
|
|
|
|
### Interaction Flow
|
|
|
|
1. User types or pastes text
|
|
2. Selects category, sub-style dropdown populates
|
|
3. Adjusts intensity slider (default: 3)
|
|
4. Clicks Convert — **loading modal appears with animated words**
|
|
5. LLM responds — modal dismisses, result appears with Copy button
|
|
6. "Show prompt" reveals the full system + user prompt sent to the LLM (collapsible, hidden by default)
|
|
7. Errors display in the output area, never as browser alerts
|
|
|
|
### Svelte 5 Runes
|
|
|
|
- () for input text, selected style, intensity, output, loading, error, prompt visibility
|
|
- () for is category selected, is convert disabled, etc.
|
|
|
|
### UI Details
|
|
|
|
- **Light palette** - white/off-white background, subtle borders, accent color on Convert button
|
|
- **No dark mode**
|
|
- **Responsive** - stacked full-width on mobile, centered max-width on desktop
|
|
- **Copy button** - navigator.clipboard with confirmation
|
|
- **No streaming** - full response then display (v2 candidate)
|
|
|
|
## Testing Strategy
|
|
|
|
### Automated Tests (Vitest)
|
|
|
|
| Layer | Tests |
|
|
|-------|-------|
|
|
| lib/styles.ts | Style lookup, category filtering, all styles have valid promptModifiers |
|
|
| lib/llm.ts | Prompt construction correctness (mock HTTP, verify prompt) |
|
|
| /api/convert | Input validation (empty text, bad style, out-of-range intensity), error responses |
|
|
|
|
### Not Testing (MVP1)
|
|
|
|
- E2E, visual regression, snapshots, LLM response content
|
|
|
|
### Manual Testing Checklist (Pre-launch)
|
|
|
|
1. Submit empty text returns 400
|
|
2. Submit with invalid style returns 400
|
|
3. Submit with intensity 0 or 6 returns validation error
|
|
4. LLM unreachable returns 502 with friendly message
|
|
5. Each category styles load in dropdowns
|
|
6. Intensity slider updates label
|
|
7. Copy button works
|
|
8. Prompt section expands/collapses with correct content
|
|
9. Works on mobile viewport
|
|
10. Loading modal shows animated words with randomized colors and animation styles
|
|
|
|
## Scope - MVP1
|
|
|
|
**In scope:**
|
|
- Single style at a time
|
|
- Intensity slider (1-5)
|
|
- Collapsible prompt display
|
|
- Loading modal with animated words (randomized color + animation style)
|
|
- .env-only LLM config
|
|
- OpenAI-compatible client (Ollama default)
|
|
- Light-only, minimal UI
|
|
- Unit + integration tests for backend logic
|
|
|
|
**Explicitly out of scope (v2 candidates):**
|
|
- Multi-style / compare mode
|
|
- Conversion history
|
|
- UI-based provider settings
|
|
- Streaming responses
|
|
- Dark mode
|
|
- E2E tests
|
|
- Database or auth
|