feat: implement English Style Converter

- SvelteKit project scaffolded with TypeScript
- Type definitions for Style, StyleCategory, ConversionRequest, ConversionResponse, LLMConfig
- Style definitions with 6 categories and 25 sub-styles
- Intensity mapping (1-5) with prompt modifier placeholders
- LLM client using OpenAI-compatible API (Ollama default)
- POST /api/convert endpoint with input validation
- Animated loading modal with per-letter animations
- Main page UI with category/style selectors, intensity slider
- Copy to clipboard, collapsible prompt display
- Vitest tests for styles, LLM prompt building, and API validation
- Environment configuration for LLM settings
This commit is contained in:
2026-04-12 21:53:27 -04:00
parent fcf80638e1
commit a12afb792e
16 changed files with 1464 additions and 37 deletions

View File

@@ -0,0 +1,62 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getStyleById, getIntensityConfig } from '$lib/styles';
import { convertText } from '$lib/llm';
import type { ConversionRequest, ConversionResponse } from '$lib/types';
export const POST: RequestHandler = async ({ request }) => {
let body: ConversionRequest;
try {
body = await request.json();
} catch {
return json({ error: 'Invalid JSON body' }, { status: 400 });
}
const { text, styleId, intensity } = body;
// Validate text
if (!text || typeof text !== 'string' || text.trim().length === 0) {
return json({ error: 'Text is required and must be non-empty' }, { status: 400 });
}
// Validate styleId
if (!styleId || typeof styleId !== 'string') {
return json({ error: 'styleId is required' }, { status: 400 });
}
const style = getStyleById(styleId);
if (!style) {
return json({ error: `Unknown style: ${styleId}` }, { status: 400 });
}
// Validate intensity
if (typeof intensity !== 'number' || !Number.isInteger(intensity) || intensity < 1 || intensity > 5) {
return json({ error: 'Intensity must be an integer between 1 and 5' }, { status: 400 });
}
const intensityConfig = getIntensityConfig(intensity);
if (!intensityConfig) {
return json({ error: 'Invalid intensity level' }, { status: 400 });
}
try {
const result = convertText(text, style.promptModifier, intensityConfig.instruction);
// Await the promise
const { converted, systemPrompt, userMessage } = await result;
const response: ConversionResponse = {
original: text,
converted,
styleId,
intensity,
systemPrompt,
userMessage
};
return json(response);
} catch (err) {
const message = err instanceof Error ? err.message : 'LLM call failed';
return json({ error: `Failed to convert text: ${message}` }, { status: 502 });
}
};