any2human / scripts /patch_dist_products.py
idnameraj's picture
Upload 79 files
2d0fe75 verified
Raw
History Blame Contribute Delete
18.3 kB
"""Patch frontend/dist/assets/index.js with multi-product UI."""
from pathlib import Path
path = Path(__file__).resolve().parent.parent / "frontend" / "dist" / "assets" / "index.js"
text = path.read_text(encoding="utf-8")
marker = "function UpgradeCard("
if "const PRODUCTS =" not in text:
insert = r'''
const PRODUCTS = [
{
id: "writer",
name: "ZuZu Writer",
short: "Writer",
tagline: "Turn AI drafts into natural wording.",
blurb: "Rewrite ChatGPT, Gemini, and Claude text in Neutral, Casual, Formal, or Academic tone — classical NLP, no LLM.",
status: "live",
},
{
id: "grammar",
name: "ZuZu Grammar",
short: "Grammar",
tagline: "Catch grammar and spelling before you publish.",
blurb: "Scan drafts for grammar, punctuation, and common spelling issues, then apply fixes with one click.",
status: "preview",
},
];
const GRAMMAR_SAMPLE = "teh quick brown fox jump over the lazy dog. i think this sentance is seperate from the other one and it it needs fixing.";
async function checkGrammar(text, accessToken) {
const headers = { "Content-Type": "application/json" };
if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
const res = await fetch("/v1/grammar", { method: "POST", headers, body: JSON.stringify({ text }) });
if (!res.ok) {
let detail = "Grammar check failed.";
try { const data = await res.json(); detail = data.detail || detail; } catch (_) {}
throw new Error(typeof detail === "string" ? detail : "Grammar check failed.");
}
return res.json();
}
function applyGrammarFix(text, issue) {
if (issue.suggestion == null) return text;
return text.slice(0, issue.start) + issue.suggestion + text.slice(issue.end);
}
function applyAllGrammarFixes(text, issues) {
let next = text;
const ordered = [...issues].sort((a, b) => b.start - a.start);
for (const issue of ordered) {
if (issue.suggestion == null) continue;
next = applyGrammarFix(next, issue);
}
return next;
}
function ProductSwitcher({ product, onChange }) {
return h("div", { className: "product-switcher", role: "tablist", "aria-label": "Products" },
PRODUCTS.map((p) => h("button", {
key: p.id,
type: "button",
role: "tab",
"aria-selected": product === p.id,
className: product === p.id ? "active" : "",
onClick: () => onChange(p.id),
}, p.short, p.status === "preview" ? h("span", { className: "product-pill" }, "Preview") : null)),
);
}
function SiteNav({ product, onSelectProduct }) {
return h("nav", { className: "site-nav", "aria-label": "Site" },
h("a", { href: "#products" }, "Products"),
h("a", { href: "#plans" }, "Plans"),
h("button", { type: "button", className: "nav-product", onClick: () => onSelectProduct("writer") }, product === "writer" ? "Open Writer" : "Writer"),
h("button", { type: "button", className: "nav-product", onClick: () => onSelectProduct("grammar") }, product === "grammar" ? "Open Grammar" : "Grammar"),
);
}
'''
text = text.replace(marker, insert + marker, 1)
start = text.index("function LandingSections(")
end = text.index("function App()")
landing = r'''function LandingSections({ plans, onSignUp, authEnabled, onSelectProduct }) {
const free = plans.find((p) => p.id === "free");
const pro = plans.find((p) => p.id === "pro");
const plus = plans.find((p) => p.id === "plus");
return h("div", { className: "landing" },
h("section", { className: "land-block", id: "products" },
h("h2", null, "Our products"),
h("p", { className: "land-lead" }, "Two focused tools under ZuZu — pick the job you need today."),
h("div", { className: "product-grid" },
PRODUCTS.map((p) => h("article", { key: p.id, className: `product-card product-card-${p.id}` },
h("div", { className: "product-card-top" },
h("h3", null, p.name),
p.status === "preview" ? h("span", { className: "product-pill" }, "Preview") : null,
),
h("p", { className: "product-tagline" }, p.tagline),
h("p", null, p.blurb),
h("ul", null,
...(p.id === "writer"
? [
h("li", { key: "w1" }, "Tone: Neutral, Casual, Formal, Academic"),
h("li", { key: "w2" }, "Offline classical NLP rewrite engine"),
h("li", { key: "w3" }, "Free preview, then Free / Pro / Plus plans"),
]
: [
h("li", { key: "g1" }, "Grammar, punctuation & common spelling"),
h("li", { key: "g2" }, "Click to apply suggested fixes"),
h("li", { key: "g3" }, "Works alongside Writer in the same account"),
]),
),
h("button", {
type: "button",
className: "btn btn-primary",
onClick: () => { onSelectProduct(p.id); window.scrollTo({ top: 0, behavior: "smooth" }); },
}, `Open ${p.short}`),
)),
),
),
h("section", { className: "land-block" },
h("h2", null, "How ZuZu works"),
h("p", { className: "land-lead" }, "Use Writer to humanize AI drafts, then Grammar to polish before you publish."),
h("ol", { className: "steps" },
h("li", null, h("span", { className: "step-num" }, "1"), h("div", null, h("strong", null, "Choose a product"), h("p", null, "Switch between Writer and Grammar from the top of the page."))),
h("li", null, h("span", { className: "step-num" }, "2"), h("div", null, h("strong", null, "Paste your draft"), h("p", null, "Drop in AI or human text and run Rewrite or Check grammar."))),
h("li", null, h("span", { className: "step-num" }, "3"), h("div", null, h("strong", null, "Review, then unlock more"), h("p", null, "Try a short preview, sign up for Free, go Pro or Plus when you write every day."))),
),
),
h("section", { className: "land-block" },
h("h2", null, "Who it's for"),
h("p", { className: "land-lead" }, "Built for people who draft with AI and publish as themselves."),
h("div", { className: "audience-grid" },
h("article", null, h("h3", null, "Students & academic writers"), h("p", null, "Refine AI-assisted notes into clearer Academic or Formal wording. Follow your institution's rules.")),
h("article", null, h("h3", null, "Freelancers & professionals"), h("p", null, "Turn stiff AI emails and reports into confident, natural communication.")),
h("article", null, h("h3", null, "Bloggers & SEO writers"), h("p", null, "Refresh repetitive AI drafts into readable posts that still keep your meaning.")),
h("article", null, h("h3", null, "Social & content teams"), h("p", null, "Humanize captions and scripts so they sound like your brand, not a model.")),
),
),
h("section", { className: "land-block", id: "plans" },
h("h2", null, "Simple plans"),
h("p", { className: "land-lead" }, "One account for Writer today — Grammar preview is included. Checkout for Pro/Plus coming soon."),
h("div", { className: "pricing-grid" },
h("article", { className: "price-card" },
h("h3", null, "Free"),
h("p", { className: "price-amount" }, "₹0"),
h("ul", null,
h("li", null, `${(free && free.max_words_per_request) || 400} words / rewrite`),
h("li", null, `${(free && free.daily_rewrites) || 5} rewrites / day`),
h("li", null, "Grammar preview included"),
),
authEnabled ? h("button", { type: "button", className: "btn btn-quiet", onClick: onSignUp }, "Create free account") : null,
),
h("article", { className: "price-card price-card-pro" },
h("h3", null, "Pro"),
h("p", { className: "price-amount" }, `₹${(pro && pro.price_inr_monthly) || 199}`, h("span", null, "/mo")),
h("ul", null,
h("li", null, `${((pro && pro.max_words_per_request) || 2000).toLocaleString()} words / rewrite`),
h("li", null, `${(pro && pro.daily_rewrites) || 50} rewrites / day`),
h("li", null, "Best for daily AI drafts"),
),
h("p", { className: "price-soon" }, "Checkout coming soon"),
),
h("article", { className: "price-card price-card-plus" },
h("h3", null, "Plus"),
h("p", { className: "price-amount" }, `₹${(plus && plus.price_inr_monthly) || 499}`, h("span", null, "/mo")),
h("ul", null,
h("li", null, `${((plus && plus.max_words_per_request) || 5000).toLocaleString()} words / rewrite`),
h("li", null, `${(plus && plus.daily_rewrites) || 200} rewrites / day`),
h("li", null, "Heavy use & longer documents"),
),
h("p", { className: "price-soon" }, "Checkout coming soon"),
),
),
h("p", { className: "plans-note" }, "Visitors can try a short free Writer preview on the homepage before signing up."),
),
h("footer", { className: "site-footer" },
h("p", null, "Review every rewrite and grammar suggestion before you share or publish."),
h("p", { className: "footer-brand" }, h(BrandLogo, { size: 28 }), h("span", null, "ZuZu")),
),
);
}
'''
text = text[:start] + landing + text[end:]
needle = " const [authTitle, setAuthTitle] = useState(undefined);\n\n const isGuest"
if "const [product, setProduct]" not in text:
text = text.replace(
needle,
""" const [authTitle, setAuthTitle] = useState(undefined);
const [product, setProduct] = useState("writer");
const [grammarText, setGrammarText] = useState("");
const [grammarIssues, setGrammarIssues] = useState([]);
const [grammarNote, setGrammarNote] = useState("");
const [grammarLoading, setGrammarLoading] = useState(false);
const [grammarError, setGrammarError] = useState("");
const [grammarMeta, setGrammarMeta] = useState("");
const activeProduct = PRODUCTS.find((p) => p.id === product) || PRODUCTS[0];
const isGuest""",
1,
)
if "async function onGrammarCheck" not in text:
text = text.replace(
" onRewrite();\n",
' if (product === "writer") onRewrite();\n else onGrammarCheck();\n',
1,
)
text = text.replace(
""" function openAuth(mode, title) {
setAuthMode(mode);
setAuthTitle(title);
setAuthOpen(true);
}
async function onRewrite()""",
""" function openAuth(mode, title) {
setAuthMode(mode);
setAuthTitle(title);
setAuthOpen(true);
}
function selectProduct(id) {
setProduct(id);
setError("");
setGrammarError("");
}
async function onGrammarCheck() {
const text = grammarText.trim();
if (!text) { setGrammarError("Paste some text first — or try the sample."); return; }
if (text.length > MAX_CHARS) { setGrammarError(`Text is too long (${text.length.toLocaleString()} chars).`); return; }
setGrammarLoading(true);
setGrammarError("");
setGrammarMeta("Checking…");
try {
const result = await checkGrammar(text, session && session.access_token);
setGrammarIssues(result.issues || []);
setGrammarNote(result.note || "");
const n = (result.issues || []).length;
setGrammarMeta(n ? `${n} issue${n === 1 ? "" : "s"} · ${result.input_words} words` : `No issues found · ${result.input_words} words`);
} catch (err) {
setGrammarError(err instanceof Error ? err.message : "Grammar check failed.");
setGrammarMeta("");
setGrammarIssues([]);
} finally {
setGrammarLoading(false);
}
}
function onApplyGrammarIssue(issue) {
setGrammarText((prev) => applyGrammarFix(prev, issue));
setGrammarIssues([]);
setGrammarMeta("Fix applied — run Check grammar again to refresh.");
setGrammarNote("");
}
function onApplyAllGrammar() {
if (!grammarIssues.length) return;
setGrammarText((prev) => applyAllGrammarFixes(prev, grammarIssues));
setGrammarIssues([]);
setGrammarMeta("All suggested fixes applied — run Check grammar again to refresh.");
setGrammarNote("");
}
async function onRewrite()""",
1,
)
old_header = """ h(\"p\", { className: \"brand-tag\" }, \"From AI-generated to plagiarism-safe — rewrite in a voice that feels real.\"),
),"""
new_header = """ h(\"p\", { className: \"brand-tag\" }, activeProduct.tagline),
h(SiteNav, { product, onSelectProduct: selectProduct }),
),"""
if "activeProduct.tagline" not in text:
text = text.replace(old_header, new_header, 1)
text = text.replace('h("h1", null, "ZuZu Writer"),', 'h("h1", null, activeProduct.name),', 1)
if "h(ProductSwitcher" not in text:
text = text.replace(
" isGuest && !idleSignedOut\n",
' h(ProductSwitcher, { product, onChange: selectProduct }),\n isGuest && !idleSignedOut && product === "writer"\n',
1,
)
text = text.replace(
""" h(LandingSections, {
plans,
authEnabled,
onSignUp: () => openAuth("signup", "Create free account"),
}),""",
""" h(LandingSections, {
plans,
authEnabled,
onSignUp: () => openAuth("signup", "Create free account"),
onSelectProduct: selectProduct,
}),""",
1,
)
stage_start = text.find(' h("div", { className: "stage" },')
stage_end = text.find(" showUpgrade && authEnabled")
if stage_start != -1 and stage_end != -1 and "grammar-stage" not in text:
writer_stage = text[stage_start:stage_end]
grammar_stage = r''' product === "grammar"
? h("div", { className: "stage grammar-stage" },
h("div", { className: "toolbar" },
h("div", { className: "toolbar-controls" },
h("p", { className: "grammar-lead" }, "Preview rule engine — grammar, punctuation, and common spelling. Fuller checks coming next."),
),
h("div", { className: "toolbar-actions" },
h("button", { type: "button", className: "btn btn-quiet", disabled: grammarLoading, onClick: () => { setGrammarText(""); setGrammarIssues([]); setGrammarMeta(""); setGrammarError(""); setGrammarNote(""); } }, "Clear"),
h("button", { type: "button", className: "btn btn-quiet", disabled: grammarLoading, onClick: () => { setGrammarText(GRAMMAR_SAMPLE); setGrammarIssues([]); setGrammarError(""); setGrammarMeta("Sample loaded — hit Check grammar."); setGrammarNote(""); } }, "Try sample"),
grammarIssues.length ? h("button", { type: "button", className: "btn btn-quiet", disabled: grammarLoading, onClick: onApplyAllGrammar }, "Apply all") : null,
h("button", { type: "button", className: "btn btn-primary btn-rewrite", disabled: grammarLoading, onClick: () => onGrammarCheck() }, grammarLoading ? "Checking…" : "Check grammar"),
),
),
h("section", { className: "editors grammar-editors" },
h("div", { className: "pane" },
h("div", { className: "pane-head" }, h("h2", null, "Your text")),
h("textarea", {
value: grammarText,
onChange: (e) => { setGrammarText(e.target.value); setGrammarIssues([]); },
placeholder: "Paste a draft to check grammar and spelling…",
spellCheck: true,
}),
),
h("div", { className: "pane pane-out grammar-issues-pane" },
h("div", { className: "pane-head" },
h("h2", null, "Issues"),
h("span", { className: "issue-count" }, grammarIssues.length ? `${grammarIssues.length} found` : (grammarMeta ? "Clean" : "—")),
),
grammarIssues.length
? h("ul", { className: "issue-list" },
grammarIssues.map((issue) => h("li", { key: issue.id, className: `issue-item cat-${issue.category}` },
h("div", { className: "issue-main" },
h("span", { className: "issue-cat" }, issue.category),
h("p", null, issue.message),
h("code", { className: "issue-snippet" },
(grammarText.slice(issue.start, issue.end) || "…") + (issue.suggestion != null ? ` → ${issue.suggestion}` : ""),
),
),
issue.suggestion != null
? h("button", { type: "button", className: "btn btn-quiet btn-tiny", onClick: () => onApplyGrammarIssue(issue) }, "Apply")
: null,
)),
)
: h("p", { className: "grammar-empty" }, grammarNote || "Run Check grammar to see suggestions here."),
),
),
h("div", { className: "statusbar" },
h("div", { className: grammarError ? "error" : grammarLoading ? "loading" : undefined }, grammarError || grammarMeta || "Paste text → Check grammar"),
h("div", { className: "counts" }, h("span", null, `${wordCount(grammarText)} words`)),
),
)
: '''
text = text[:stage_start] + grammar_stage + writer_stage + text[stage_end:]
text = text.replace(
" showUpgrade && authEnabled\n",
' showUpgrade && authEnabled && product === "writer"\n',
1,
)
text = text.replace(
' h("p", { className: "hint" }, "Review the rewrite before you share or publish it."),',
' h("p", { className: "hint" }, product === "grammar" ? "Review every suggestion before you publish." : "Review the rewrite before you share or publish it."),',
1,
)
path.write_text(text, encoding="utf-8")
print("patched", path)
print("PRODUCTS", "const PRODUCTS =" in text)
print("grammar-stage", "grammar-stage" in text)
print("ProductSwitcher", "h(ProductSwitcher" in text)