File size: 4,587 Bytes
1103f3b c212805 1103f3b b387e01 1103f3b b387e01 1103f3b 4c5fda9 b387e01 4c5fda9 b387e01 3b361f1 b387e01 1103f3b b387e01 1103f3b b387e01 1103f3b b387e01 1103f3b 4c5fda9 b387e01 2d0fe75 7063659 2d0fe75 7063659 2d0fe75 7063659 2d0fe75 fe5d482 7063659 fe5d482 2d0fe75 fe5d482 2d0fe75 fe5d482 2d0fe75 fe5d482 2d0fe75 4c5fda9 b387e01 4c5fda9 b387e01 1103f3b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | 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 {
/* ignore */
}
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 {
/* non-JSON body — often means the Space/app build is missing /v1/grammar */
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();
}
|