Rasheed commited on
Commit
b561a83
·
verified ·
1 Parent(s): c670e96

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +147 -13
  2. auth.py +20 -3
  3. plans.py +13 -11
app.py CHANGED
@@ -104,11 +104,55 @@ def _limits_row(user):
104
  # =====================================================================
105
  # Handlers — status panel streaming via H._run_live
106
  # =====================================================================
107
- def _stream(core, args, n, idx, user=None, need_login=False, request=None, tool="", words=None):
108
- if need_login:
109
- A._require(user)
110
- P.check_doc(user, words) # plan quota / credits / free trial + word cap
111
- A.check_limit(user, request, need_login)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  meta = {}
113
 
114
  def wrapped(*a): # cores record details into rep.meta
@@ -177,9 +221,9 @@ def _smart_docx_core(path, target, passes, temp, mode, hi, mod, rep):
177
 
178
 
179
  def smart_text(text, target, passes, temp, mode, hi, mod, user, request: gr.Request = None):
180
- P.check_paste(user, text)
181
  passes = P.max_passes(user, passes)
182
- yield from _stream(_smart_text_core, (text, target, passes, temp, mode, hi, mod), 6, 0, user, request=request, tool="Smart Humanize")
 
183
 
184
 
185
  def smart_docx(path, target, passes, temp, mode, hi, mod, user, request: gr.Request = None):
@@ -201,13 +245,16 @@ def _det_core(file, text, hi, mod, rep):
201
  def detect_text(text, hi, mod, user, request: gr.Request = None):
202
  if not (text or "").strip():
203
  raise gr.Error("Please paste some text.")
204
- P.check_paste(user, text)
205
- yield from _stream(_det_core, (None, text, hi, mod), 4, 0, user, request=request, tool="AI Detector")
206
 
207
 
208
  def detect_file(file, hi, mod, user, request: gr.Request = None):
209
  if not file:
210
- A._require(user)
 
 
 
 
211
  raise gr.Error("Please upload a .docx, .pdf or .txt file.")
212
  yield from _stream(_det_core, (file, None, hi, mod), 4, 0, user, True, request, tool="AI Detector",
213
  words=P.doc_words(file))
@@ -240,9 +287,8 @@ def _hum_docx_core(path, temp, min_words, mode, rep):
240
  def humanize_text(text, temp, min_words, mode, user, request: gr.Request = None):
241
  if not (text or "").strip():
242
  raise gr.Error("Please paste some text to paraphrase.")
243
- P.check_paste(user, text)
244
  yield from _stream(_hum_text_core, (text, temp, min_words, mode, P.paste_limit(user)), 5, 3, user,
245
- request=request, tool="Humanizer")
246
 
247
 
248
  def humanize_docx(path, temp, min_words, mode, user, request: gr.Request = None):
@@ -578,6 +624,59 @@ FOOTER = f"""
578
 
579
  # Unscoped CSS (Gradio prefixes the css= string, so the outer containers are pinned here)
580
  HEAD = """<style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  @media (max-width: 700px) {
582
  html, body { overflow-x: hidden !important; }
583
  .gradio-container { min-width: 0 !important; width: 100% !important; max-width: 100vw !important;
@@ -587,7 +686,38 @@ HEAD = """<style>
587
  min-width: 0 !important; max-width: 100% !important; }
588
  .gradio-container .table-wrap { max-width: 100% !important; overflow-x: auto !important; }
589
  }
590
- </style>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
 
592
  theme = gr.themes.Soft(primary_hue="indigo", secondary_hue="pink", neutral_hue="slate",
593
  font=[gr.themes.GoogleFont("Poppins"), "ui-sans-serif", "sans-serif"], radius_size="lg")
@@ -631,6 +761,8 @@ with gr.Blocks(title=f"{APP_NAME} · CS Software Solutions", theme=theme, css=CS
631
  top_signin = gr.Button("🔐 Sign in", size="sm", elem_classes="cs-topbtn", scale=0)
632
  top_upgrade = gr.Button("💎 Upgrade", size="sm", elem_classes="cs-topbtn cs-upbtn", scale=0)
633
  top_logout = gr.Button("Log out", size="sm", elem_classes="cs-topbtn", visible=False, scale=0)
 
 
634
 
635
  with gr.Tabs(elem_id="cs-nav", selected="smart") as nav:
636
  # ============================ SMART HUMANIZE ============================
@@ -1100,6 +1232,8 @@ with gr.Blocks(title=f"{APP_NAME} · CS Software Solutions", theme=theme, css=CS
1100
 
1101
  # pricing
1102
  top_upgrade.click(lambda: gr.Tabs(selected="pricing"), None, nav, **HID)
 
 
1103
  pricing_tab.select(lambda u: f'<div class="cs-nudge">{html.escape(P.usage_line(u))}</div>', user_state,
1104
  pr_status, **HID)
1105
  for b, item in ((pr_student, "Student plan"), (pr_pro, "Pro researcher plan"), (pr_agency, "Agency plan"),
 
104
  # =====================================================================
105
  # Handlers — status panel streaming via H._run_live
106
  # =====================================================================
107
+ def upsell_html(up):
108
+ """Centred, animated pop-up for plan / sign-in / limit messages (never shown as an error)."""
109
+ kind = getattr(up, "kind", "upgrade")
110
+ icon = {"upgrade": "💎", "signin": "🔐", "limit": "⏳"}.get(kind, "✨")
111
+ title = html.escape(getattr(up, "headline", "") or "Heads up")
112
+ text = html.escape(getattr(up, "text", "") or str(up))
113
+ st = P.PLANS["student"]
114
+ perks = {
115
+ "upgrade": [f"{st['docs']} documents a month", f"{st['paste_words']:,}-word pastes",
116
+ "Word layout kept + PDF report", f"from ${st['usd']}/mo · ₹{st['inr']}"],
117
+ "signin": [f"{P.FREE_TRIAL_DOCS} documents free", "Word & PDF upload", "Run history & re-downloads",
118
+ "No card needed"],
119
+ }.get(kind, [])
120
+ go = {"upgrade": ("💎 See plans", "cs-go-pricing"),
121
+ "signin": ("🔐 Sign in / Create free account", "cs-go-account")}.get(kind)
122
+ perks_html = "".join(f'<li><span>✓</span>{html.escape(x)}</li>' for x in perks)
123
+ btns = (f'<button class="cs-up-go" data-cs-go="{go[1]}">{go[0]}</button>' if go else "") + \
124
+ f'<button class="cs-up-later" data-cs-close="1">{"Maybe later" if go else "OK"}</button>'
125
+ modal = (f'<div class="cs-upsell cs-up-{kind}" role="dialog" aria-modal="true" aria-label="{title}">'
126
+ '<div class="cs-up-backdrop" data-cs-close="1"></div>'
127
+ '<div class="cs-up-card"><div class="cs-up-ring"></div>'
128
+ '<button class="cs-up-x" data-cs-close="1" aria-label="Close">×</button>'
129
+ f'<div class="cs-up-icon">{icon}</div><div class="cs-up-title">{title}</div>'
130
+ f'<div class="cs-up-text">{text}</div>'
131
+ + (f'<ul class="cs-up-perks">{perks_html}</ul>' if perks_html else "")
132
+ + f'<div class="cs-up-actions">{btns}</div>'
133
+ '<i class="cs-sp s1">✦</i><i class="cs-sp s2">✦</i><i class="cs-sp s3">✧</i><i class="cs-sp s4">✦</i>'
134
+ '</div></div>')
135
+ link = (f' <a class="cs-up-link" data-cs-go="{go[1]}">{go[0]}</a>' if go else "")
136
+ return modal + f'<div class="cs-nudge">{icon} {text}{link}</div>'
137
+
138
+
139
+ def _upsell_frame(up, n, idx):
140
+ frame = [gr.update()] * n
141
+ frame[idx] = upsell_html(up)
142
+ return tuple(frame)
143
+
144
+
145
+ def _stream(core, args, n, idx, user=None, need_login=False, request=None, tool="", words=None, paste=None):
146
+ try:
147
+ if paste is not None:
148
+ P.check_paste(user, paste) # words per paste for this plan
149
+ if need_login:
150
+ A._require(user)
151
+ P.check_doc(user, words) # plan quota / credits / free trial + word cap
152
+ A.check_limit(user, request, need_login)
153
+ except A.Upsell as up: # plan / sign-in / limit -> friendly pop-up, not an error
154
+ yield _upsell_frame(up, n, idx)
155
+ return
156
  meta = {}
157
 
158
  def wrapped(*a): # cores record details into rep.meta
 
221
 
222
 
223
  def smart_text(text, target, passes, temp, mode, hi, mod, user, request: gr.Request = None):
 
224
  passes = P.max_passes(user, passes)
225
+ yield from _stream(_smart_text_core, (text, target, passes, temp, mode, hi, mod), 6, 0, user, request=request,
226
+ tool="Smart Humanize", paste=text)
227
 
228
 
229
  def smart_docx(path, target, passes, temp, mode, hi, mod, user, request: gr.Request = None):
 
245
  def detect_text(text, hi, mod, user, request: gr.Request = None):
246
  if not (text or "").strip():
247
  raise gr.Error("Please paste some text.")
248
+ yield from _stream(_det_core, (None, text, hi, mod), 4, 0, user, request=request, tool="AI Detector", paste=text)
 
249
 
250
 
251
  def detect_file(file, hi, mod, user, request: gr.Request = None):
252
  if not file:
253
+ try:
254
+ A._require(user)
255
+ except A.Upsell as up:
256
+ yield _upsell_frame(up, 4, 0)
257
+ return
258
  raise gr.Error("Please upload a .docx, .pdf or .txt file.")
259
  yield from _stream(_det_core, (file, None, hi, mod), 4, 0, user, True, request, tool="AI Detector",
260
  words=P.doc_words(file))
 
287
  def humanize_text(text, temp, min_words, mode, user, request: gr.Request = None):
288
  if not (text or "").strip():
289
  raise gr.Error("Please paste some text to paraphrase.")
 
290
  yield from _stream(_hum_text_core, (text, temp, min_words, mode, P.paste_limit(user)), 5, 3, user,
291
+ request=request, tool="Humanizer", paste=text)
292
 
293
 
294
  def humanize_docx(path, temp, min_words, mode, user, request: gr.Request = None):
 
624
 
625
  # Unscoped CSS (Gradio prefixes the css= string, so the outer containers are pinned here)
626
  HEAD = """<style>
627
+ #cs-go-pricing, #cs-go-account, .cs-hidden-nav { display: none !important; }
628
+ .cs-upsell { position: fixed; inset: 0; z-index: 10000; display: flex; align-items: center; justify-content: center;
629
+ padding: 16px; font-family: Poppins, ui-sans-serif, sans-serif; }
630
+ .cs-up-backdrop { position: absolute; inset: 0; background: rgba(12, 16, 45, .55); backdrop-filter: blur(4px);
631
+ -webkit-backdrop-filter: blur(4px); animation: csFade .3s ease both; }
632
+ .cs-up-card { position: relative; width: min(440px, 100%); background: #fff; border-radius: 22px;
633
+ padding: 30px 26px 22px; text-align: center; color: #1d2040; overflow: hidden;
634
+ box-shadow: 0 30px 80px rgba(7, 34, 139, .35); animation: csPop .55s cubic-bezier(.2, 1.3, .35, 1) both; }
635
+ .cs-up-ring { position: absolute; inset: 0; border-radius: 22px; padding: 2px; pointer-events: none;
636
+ background: conic-gradient(from var(--a, 0deg), #07228B, #47148B, #E92E7F, #50B4EF, #07228B);
637
+ -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
638
+ -webkit-mask-composite: xor; mask-composite: exclude; animation: csSpin 4s linear infinite; }
639
+ @property --a { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
640
+ .cs-up-icon { font-size: 46px; line-height: 1; display: inline-block; margin-bottom: 10px;
641
+ animation: csFloat 2.4s ease-in-out infinite; filter: drop-shadow(0 8px 14px rgba(233, 46, 127, .35)); }
642
+ .cs-up-title { font-size: 21px; font-weight: 700; margin-bottom: 8px;
643
+ background: linear-gradient(90deg, #07228B, #47148B 55%, #E92E7F); -webkit-background-clip: text;
644
+ background-clip: text; color: transparent; }
645
+ .cs-up-text { font-size: 14.5px; line-height: 1.6; color: #444a6b; margin-bottom: 14px; }
646
+ .cs-up-perks { list-style: none; padding: 0; margin: 0 0 18px; display: grid; grid-template-columns: 1fr 1fr;
647
+ gap: 8px; text-align: left; }
648
+ .cs-up-perks li { font-size: 13px; background: #f5f3ff; border-radius: 10px; padding: 8px 10px; color: #2b2f55;
649
+ opacity: 0; animation: csRise .45s ease forwards; }
650
+ .cs-up-perks li:nth-child(1) { animation-delay: .25s } .cs-up-perks li:nth-child(2) { animation-delay: .35s }
651
+ .cs-up-perks li:nth-child(3) { animation-delay: .45s } .cs-up-perks li:nth-child(4) { animation-delay: .55s }
652
+ .cs-up-perks span { color: #E92E7F; font-weight: 800; margin-right: 6px; }
653
+ .cs-up-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
654
+ .cs-up-go { border: 0; cursor: pointer; color: #fff; font-weight: 700; font-size: 15px; padding: 12px 22px;
655
+ border-radius: 12px; background: linear-gradient(90deg, #07228B, #47148B 60%, #E92E7F); background-size: 200% 100%;
656
+ box-shadow: 0 10px 24px rgba(233, 46, 127, .35); animation: csShine 3s ease-in-out infinite; }
657
+ .cs-up-go:hover { transform: translateY(-1px); }
658
+ .cs-up-later { border: 1px solid #e3e5f2; background: #fff; color: #555b7c; cursor: pointer; font-size: 14px;
659
+ padding: 12px 18px; border-radius: 12px; }
660
+ .cs-up-x { position: absolute; top: 10px; right: 12px; border: 0; background: transparent; font-size: 24px;
661
+ color: #9aa0bd; cursor: pointer; line-height: 1; }
662
+ .cs-sp { position: absolute; font-style: normal; color: #E92E7F; opacity: 0; animation: csTwinkle 2.6s ease-in-out infinite; }
663
+ .cs-sp.s1 { top: 18px; left: 26px; } .cs-sp.s2 { top: 60px; right: 40px; color: #50B4EF; animation-delay: .7s; }
664
+ .cs-sp.s3 { bottom: 70px; left: 18px; color: #47148B; animation-delay: 1.3s; }
665
+ .cs-sp.s4 { bottom: 26px; right: 22px; animation-delay: 1.9s; }
666
+ .cs-upsell.cs-out .cs-up-card { animation: csPopOut .22s ease forwards; }
667
+ .cs-upsell.cs-out .cs-up-backdrop { animation: csFadeOut .22s ease forwards; }
668
+ .cs-up-link { cursor: pointer; font-weight: 600; color: #47148B; text-decoration: underline; margin-left: 4px; }
669
+ @keyframes csFade { from { opacity: 0 } to { opacity: 1 } }
670
+ @keyframes csFadeOut { to { opacity: 0 } }
671
+ @keyframes csPop { from { opacity: 0; transform: translateY(24px) scale(.85) } to { opacity: 1; transform: none } }
672
+ @keyframes csPopOut { to { opacity: 0; transform: translateY(12px) scale(.92) } }
673
+ @keyframes csSpin { to { --a: 360deg } }
674
+ @keyframes csFloat { 0%, 100% { transform: translateY(0) rotate(-4deg) } 50% { transform: translateY(-8px) rotate(4deg) } }
675
+ @keyframes csRise { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } }
676
+ @keyframes csShine { 0%, 100% { background-position: 0% 0 } 50% { background-position: 100% 0 } }
677
+ @keyframes csTwinkle { 0%, 100% { opacity: 0; transform: scale(.6) } 50% { opacity: .9; transform: scale(1.1) } }
678
+ @media (max-width: 480px) { .cs-up-perks { grid-template-columns: 1fr; } .cs-up-card { padding: 26px 18px 18px; } }
679
+ @media (prefers-reduced-motion: reduce) { .cs-upsell * { animation: none !important; opacity: 1 !important; } }
680
  @media (max-width: 700px) {
681
  html, body { overflow-x: hidden !important; }
682
  .gradio-container { min-width: 0 !important; width: 100% !important; max-width: 100vw !important;
 
686
  min-width: 0 !important; max-width: 100% !important; }
687
  .gradio-container .table-wrap { max-width: 100% !important; overflow-x: auto !important; }
688
  }
689
+ </style>
690
+ <script>
691
+ (function () {
692
+ // The pop-up arrives inside a status panel; move it to <body> so it is centred on the whole page.
693
+ function mount() {
694
+ document.querySelectorAll('.cs-upsell').forEach(function (m) {
695
+ if (m.parentElement === document.body) return;
696
+ document.querySelectorAll('body > .cs-upsell').forEach(function (o) { o.remove(); });
697
+ document.body.appendChild(m);
698
+ });
699
+ }
700
+ function closeAll(el) {
701
+ var list = el ? [el] : Array.prototype.slice.call(document.querySelectorAll('body > .cs-upsell'));
702
+ list.forEach(function (m) { m.classList.add('cs-out'); setTimeout(function () { m.remove(); }, 230); });
703
+ }
704
+ function start() {
705
+ new MutationObserver(mount).observe(document.body, { childList: true, subtree: true });
706
+ document.addEventListener('click', function (e) {
707
+ var go = e.target.closest('[data-cs-go]');
708
+ var close = e.target.closest('[data-cs-close]');
709
+ if (go) {
710
+ var b = document.getElementById(go.getAttribute('data-cs-go'));
711
+ if (b) { (b.tagName === 'BUTTON' ? b : (b.querySelector('button') || b)).click(); }
712
+ window.scrollTo({ top: 0, behavior: 'smooth' });
713
+ }
714
+ if (go || close) { closeAll(e.target.closest('.cs-upsell')); }
715
+ });
716
+ document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeAll(); });
717
+ }
718
+ if (document.body) start(); else document.addEventListener('DOMContentLoaded', start);
719
+ })();
720
+ </script>"""
721
 
722
  theme = gr.themes.Soft(primary_hue="indigo", secondary_hue="pink", neutral_hue="slate",
723
  font=[gr.themes.GoogleFont("Poppins"), "ui-sans-serif", "sans-serif"], radius_size="lg")
 
761
  top_signin = gr.Button("🔐 Sign in", size="sm", elem_classes="cs-topbtn", scale=0)
762
  top_upgrade = gr.Button("💎 Upgrade", size="sm", elem_classes="cs-topbtn cs-upbtn", scale=0)
763
  top_logout = gr.Button("Log out", size="sm", elem_classes="cs-topbtn", visible=False, scale=0)
764
+ go_pricing = gr.Button("pricing", elem_id="cs-go-pricing", elem_classes="cs-hidden-nav", scale=0)
765
+ go_account = gr.Button("account", elem_id="cs-go-account", elem_classes="cs-hidden-nav", scale=0)
766
 
767
  with gr.Tabs(elem_id="cs-nav", selected="smart") as nav:
768
  # ============================ SMART HUMANIZE ============================
 
1232
 
1233
  # pricing
1234
  top_upgrade.click(lambda: gr.Tabs(selected="pricing"), None, nav, **HID)
1235
+ go_pricing.click(lambda: gr.Tabs(selected="pricing"), None, nav, **HID)
1236
+ go_account.click(lambda: gr.Tabs(selected="account"), None, nav, **HID)
1237
  pricing_tab.select(lambda u: f'<div class="cs-nudge">{html.escape(P.usage_line(u))}</div>', user_state,
1238
  pr_status, **HID)
1239
  for b, item in ((pr_student, "Student plan"), (pr_pro, "Pro researcher plan"), (pr_agency, "Agency plan"),
auth.py CHANGED
@@ -199,9 +199,21 @@ def _count_run(user, document=True):
199
  pass
200
 
201
 
 
 
 
 
 
 
 
 
 
 
 
202
  def _require(user):
203
  if not user or not user.get("email"):
204
- raise gr.Error("Please sign in to upload documents. Pasting text works without an account.")
 
205
  with _db() as con:
206
  row = con.execute("SELECT active FROM users WHERE email=?", (user["email"],)).fetchone()
207
  if not row or row[0] == 0:
@@ -408,8 +420,13 @@ def check_limit(user, request, document):
408
  return
409
  if usage_today(_who(user, request), kind) >= lim:
410
  what = "document runs" if document else "pasted-text runs"
411
- extra = " Sign in for a higher limit." if not user else " Paid plans have higher limits — see 💎 Pricing."
412
- raise gr.Error(f"Daily limit reached: {lim} {what} per day.{extra} It resets at 00:00 UTC.")
 
 
 
 
 
413
 
414
 
415
  def add_usage(user, request, document):
 
199
  pass
200
 
201
 
202
+ class Upsell(gr.Error):
203
+ """Not an error: a plan / sign-in / limit message. The app shows it as a friendly centred pop-up.
204
+ kind: "upgrade" (paid plan unlocks it), "signin" (free account unlocks it), "limit" (try again later)."""
205
+
206
+ def __init__(self, message, kind="upgrade", title=None):
207
+ titles = {"upgrade": "Unlock more with a plan", "signin": "Create your free account",
208
+ "limit": "You've reached today's limit"}
209
+ super().__init__(message, title=title or titles.get(kind, "Heads up"), print_exception=False)
210
+ self.kind, self.headline, self.text = kind, title or titles.get(kind, "Heads up"), message
211
+
212
+
213
  def _require(user):
214
  if not user or not user.get("email"):
215
+ raise Upsell("Sign in or create a free account to upload Word and PDF documents — it takes 20 seconds. "
216
+ "Pasting text works without an account.", kind="signin", title="Upload documents for free")
217
  with _db() as con:
218
  row = con.execute("SELECT active FROM users WHERE email=?", (user["email"],)).fetchone()
219
  if not row or row[0] == 0:
 
420
  return
421
  if usage_today(_who(user, request), kind) >= lim:
422
  what = "document runs" if document else "pasted-text runs"
423
+ if not user:
424
+ raise Upsell(f"Guests can do {lim} {what} a day. Create a free account for a higher limit — "
425
+ "or come back after 00:00 UTC.", kind="signin", title="You've reached today's free limit")
426
+ paid = P.state(user)["key"] in P.PAID
427
+ raise Upsell(f"You've used all {lim} {what} for today. It resets at 00:00 UTC."
428
+ + ("" if paid else " Paid plans have much higher daily limits."),
429
+ kind="limit" if paid else "upgrade", title="You've reached today's limit")
430
 
431
 
432
  def add_usage(user, request, document):
plans.py CHANGED
@@ -115,15 +115,16 @@ def check_doc(user, words=None):
115
  return
116
  if docs_available(st) <= 0:
117
  if st["key"] in PAID:
118
- raise gr.Error(_upgrade_msg(f"You have used all {st['docs']} documents in this 30-day period. "
119
- "Buy a single document or move up a plan."))
120
- raise gr.Error(_upgrade_msg(f"Your {FREE_TRIAL_DOCS} free documents are used up. Plans start at "
121
- f"${PLANS['student']['usd']}/month (₹{PLANS['student']['inr']}), or "
122
- f"${SINGLE['usd']} for a single document."))
 
123
  cap = st["doc_words"] if (st["key"] in PAID and st["used"] < st["docs"]) else SINGLE["doc_words"]
124
  if words and words > cap:
125
- raise gr.Error(_upgrade_msg(f"This document has {words:,} words; your plan allows up to {cap:,} per "
126
- "document."))
127
 
128
 
129
  def paste_limit(user):
@@ -135,10 +136,11 @@ def check_paste(user, text):
135
  n = len((text or "").split())
136
  if n > st["paste_words"]:
137
  if st["key"] in ("guest", "free"):
138
- raise gr.Error(_upgrade_msg(f"Free pastes are limited to {st['paste_words']} words (yours: {n:,}). "
139
- f"Student allows {PLANS['student']['paste_words']:,} words per paste."))
140
- raise gr.Error(f"Your plan allows {st['paste_words']:,} words per paste (yours: {n:,}). "
141
- "Upload a .docx for longer text.")
 
142
 
143
 
144
  def daily_paste_limit(user):
 
115
  return
116
  if docs_available(st) <= 0:
117
  if st["key"] in PAID:
118
+ raise A.Upsell(f"You've used all {st['docs']} documents in this 30-day period. Buy a single document "
119
+ "or move up a plan to keep going.", title="Your monthly documents are used up")
120
+ raise A.Upsell(f"You've used your {FREE_TRIAL_DOCS} free documents. Plans start at "
121
+ f"${PLANS['student']['usd']}/month (₹{PLANS['student']['inr']}), or "
122
+ f"${SINGLE['usd']} (₹{SINGLE['inr']}) for a single document.",
123
+ title="Loved your free documents?")
124
  cap = st["doc_words"] if (st["key"] in PAID and st["used"] < st["docs"]) else SINGLE["doc_words"]
125
  if words and words > cap:
126
+ raise A.Upsell(f"This document has {words:,} words; your plan allows up to {cap:,} words per document. "
127
+ "A bigger plan handles longer documents.", title="This document is a bit long")
128
 
129
 
130
  def paste_limit(user):
 
136
  n = len((text or "").split())
137
  if n > st["paste_words"]:
138
  if st["key"] in ("guest", "free"):
139
+ raise A.Upsell(f"Free pastes are up to {st['paste_words']} words (yours: {n:,}). The Student plan allows "
140
+ f"{PLANS['student']['paste_words']:,} words per paste — or upload a .docx.",
141
+ title="Your text is longer than the free limit")
142
+ raise A.Upsell(f"Your plan allows {st['paste_words']:,} words per paste (yours: {n:,}). Upload a .docx for "
143
+ "longer text.", kind="limit", title="Your text is a bit long")
144
 
145
 
146
  def daily_paste_limit(user):