| export type Tone = "Neutral" | "Casual" | "Formal" | "Academic";
|
| export type StrengthLabel = "Light" | "Normal" | "Heavy";
|
|
|
| export const TONES: Tone[] = ["Neutral", "Casual", "Formal", "Academic"];
|
| export const STRENGTHS: StrengthLabel[] = ["Light", "Normal", "Heavy"];
|
|
|
| export const STRENGTH_MAP: Record<StrengthLabel, number> = {
|
| Light: 0,
|
| Normal: 1,
|
| Heavy: 2,
|
| };
|
|
|
| export type RewriteMeta = {
|
| input_words: number;
|
| output_words: number;
|
| seconds: number;
|
| lexical_refined: number;
|
| ml_polish_requested: boolean;
|
| tone: string;
|
| strength: number;
|
| };
|
|
|
| export type AccountInfo = {
|
| email: string | null;
|
| display_name: string | null;
|
| role: string;
|
| plan: {
|
| id: string;
|
| name: string;
|
| daily_rewrites: number;
|
| max_words_per_request: number;
|
| daily_word_cap: number;
|
| price_inr_monthly: number;
|
| };
|
| usage: {
|
| date: string;
|
| rewrite_count: number;
|
| word_count: number;
|
| remaining_rewrites: number;
|
| remaining_words: number;
|
| };
|
| };
|
|
|
| export type RewriteResponse = {
|
| rewrite: string;
|
| meta: RewriteMeta;
|
| account?: AccountInfo | null;
|
| };
|
|
|
| export type ApiError = Error & { status?: number; code?: string };
|
|
|
| function detailMessage(detail: unknown, fallback: string): string {
|
| if (typeof detail === "string") return detail;
|
| if (Array.isArray(detail)) {
|
| return detail
|
| .map((d) => (typeof d === "object" && d && "msg" in d ? String(d.msg) : String(d)))
|
| .join(" ");
|
| }
|
| return fallback;
|
| }
|
|
|
| export async function rewriteText(
|
| payload: {
|
| text: string;
|
| tone: Tone;
|
| strength: number;
|
| preserve_length: boolean;
|
| ml_polish?: boolean;
|
| },
|
| accessToken?: string | null,
|
| ): Promise<RewriteResponse> {
|
| const headers: Record<string, string> = { "Content-Type": "application/json" };
|
| if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
|
|
|
| const res = await fetch("/v1/rewrite", {
|
| method: "POST",
|
| headers,
|
| body: JSON.stringify(payload),
|
| });
|
| if (!res.ok) {
|
| let detail: unknown = "Rewrite failed.";
|
| try {
|
| const data = await res.json();
|
| detail = data.detail ?? detail;
|
| } catch {
|
|
|
| }
|
| const err = new Error(detailMessage(detail, "Rewrite failed.")) as ApiError;
|
| err.status = res.status;
|
| if (res.status === 401 || res.status === 429 || res.status === 413) {
|
| err.code = "limit";
|
| }
|
| throw err;
|
| }
|
| return res.json();
|
| }
|
|
|
| export type GrammarIssue = {
|
| id: string;
|
| start: number;
|
| end: number;
|
| message: string;
|
| suggestion: string | null;
|
| category: string;
|
| };
|
|
|
| export type GrammarResponse = {
|
| issues: GrammarIssue[];
|
| input_words: number;
|
| engine: string;
|
| language?: string;
|
| note?: string;
|
| };
|
|
|
| export async function checkGrammar(
|
| text: string,
|
| options?: { language?: string; accessToken?: string | null },
|
| ): Promise<GrammarResponse> {
|
| const headers: Record<string, string> = { "Content-Type": "application/json" };
|
| const accessToken = options?.accessToken;
|
| if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
|
|
|
| let res: Response;
|
| try {
|
| res = await fetch("/v1/grammar", {
|
| method: "POST",
|
| headers,
|
| body: JSON.stringify({
|
| text,
|
| language: options?.language || "en-US",
|
| }),
|
| });
|
| } catch {
|
| const err = new Error(
|
| "Could not reach the grammar API. Is the ZuZu server running?",
|
| ) as ApiError;
|
| err.code = "network";
|
| throw err;
|
| }
|
|
|
| if (!res.ok) {
|
| let detail: unknown = `Grammar check failed (HTTP ${res.status}).`;
|
| try {
|
| const data = await res.json();
|
| detail = data.detail ?? detail;
|
| } catch {
|
|
|
| if (res.status === 404) {
|
| detail =
|
| "Grammar API not found (404). Restart/redeploy the app so /v1/grammar is available.";
|
| }
|
| }
|
| const err = new Error(detailMessage(detail, `Grammar check failed (HTTP ${res.status}).`)) as ApiError;
|
| err.status = res.status;
|
| throw err;
|
| }
|
| return res.json();
|
| }
|
|
|
| export async function fetchMe(accessToken?: string | null): Promise<{
|
| auth_enabled: boolean;
|
| account: AccountInfo | null;
|
| }> {
|
| const headers: Record<string, string> = {};
|
| if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
|
| const res = await fetch("/v1/me", { headers });
|
| if (!res.ok) {
|
| throw new Error("Could not load account.");
|
| }
|
| return res.json();
|
| }
|
|
|