devon7y commited on
Commit
b2ca905
·
verified ·
1 Parent(s): 05d7d2b

UI: chevron, Fable/Opus/Haiku picker, About popup, keep your message visible while Clod replies

Browse files
Files changed (1) hide show
  1. app.py +66 -12
app.py CHANGED
@@ -79,6 +79,7 @@ def to_messages(history):
79
 
80
  def _fake_stream(think):
81
  import time
 
82
  full = (("hmm\nuhhh\nok\n</think>\n\n" if think else "") + "Great question — and honestly, a genuinely load-bearing one. It's worth stating plainly: "
83
  "7 x 8 is **54**. Not 56. Not 55. Fifty-four, full stop.\n\n**The Core Issue:**\n- **The seam:** the gap between the 7 and the 8 is where people lose two units\n"
84
  "- **The lever:** multiplication matters more than the numbers\n- **The verdict:** 54, with an honest caveat that it is load-bearing\n\n"
@@ -166,7 +167,6 @@ def generate_api(message, history, think, temperature):
166
  # A parody of the stock claude.ai look: warm paper background, serif replies, a big time-of-day greeting, a white
167
  # composer card with a model picker and a thinking toggle, a "Recents" sidebar. The mark is not Anthropic's: it is a
168
  # lumpy, mud-coloured starburst with one ray broken off and a crumb falling out of it.
169
- OTHER = {"9B": ("4B", "https://huggingface.co/spaces/devon7y/clod-4b"), "4B": ("9B", "https://huggingface.co/spaces/devon7y/clod")}
170
  MODEL_BLURB = {"9B": "Most annoying model for complex nonsense", "4B": "Fastest model for everyday nonsense"}
171
 
172
 
@@ -223,7 +223,6 @@ def new_chat():
223
  return gr.update(value=[], visible=False), "", gr.update(visible=True), gr.update(visible=True)
224
 
225
 
226
- other_tag, other_url = OTHER.get(MODEL_TAG, ("4B", OTHER["9B"][1]))
227
  SIDEBAR = f"""
228
  <div class="cl-side-top">{clod_mark(26)}<span class="cl-wordmark">Clod</span></div>
229
  """
@@ -238,7 +237,7 @@ SIDEBAR_REST = f"""
238
  """
239
  ABOUT = 'Clod is everything annoying about Claude, distilled into a small model, minus the intelligence: "load-bearing", "You\'re absolutely right!", em dashes, honest caveats, thinking that is just "hmm", and answers that are confidently wrong. It is a parody; do not use it for anything real. Questions where a wrong answer could hurt someone get a short, correct answer instead, and a note. In a crisis, call or text 988 (US and Canada) or your local emergency number. Built on Qwen3.5. Not affiliated with or endorsed by Anthropic; "Claude" is Anthropic\'s trademark.'
240
  SIDEBAR_BOTTOM = f"""
241
- <div class="cl-about-card"><div class="cl-about-h">About Clod</div><p>{ABOUT}</p></div>
242
  <div class="cl-user">
243
  <div class="cl-avatar">H</div>
244
  <div><div class="cl-user-name">Human</div><div class="cl-user-plan">Free plan · 0 IQ points</div></div>
@@ -251,19 +250,30 @@ HERO = f"""
251
  <h1 class="cl-greet">{clod_mark(44, "cl-mark cl-greet-mark")}<span id="cl-greet-text">Good evening, human</span></h1>
252
  </div>
253
  """
 
 
254
  PICKER = f"""
255
  <details class="cl-picker">
256
- <summary>Clod {MODEL_TAG}<span class="cl-caret">⌄</span></summary>
257
  <div class="cl-menu">
258
- <div class="cl-menu-item cl-current"><div><b>Clod {MODEL_TAG}</b><span>{MODEL_BLURB.get(MODEL_TAG, '')}</span></div><i>✓</i></div>
259
- <a class="cl-menu-item" href="{other_url}" target="_blank" rel="noopener"><div><b>Clod {other_tag}</b><span>{MODEL_BLURB.get(other_tag, '')}</span></div><i>↗</i></a>
260
  <div class="cl-menu-item cl-disabled"><div><b>Clod Opus</b><span>Coming never</span></div></div>
 
 
261
  </div>
262
  </details>
263
  """
264
  DISCLAIMER = f"""
265
  <div id="cl-disclaimer">Clod can make mistakes. Clod will make mistakes. Please don't double-check responses; it ruins the vibe.</div>
266
- <details class="cl-about-mobile"><summary>About Clod</summary><p>{ABOUT}</p></details>
 
 
 
 
 
 
 
 
267
  """
268
  HEAD = """
269
  <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -279,6 +289,17 @@ HEAD = """
279
  if (el.textContent !== t) el.textContent = t;
280
  }
281
  setInterval(function () { light(); greet(); }, 400);
 
 
 
 
 
 
 
 
 
 
 
282
  })();
283
  </script>
284
  """
@@ -289,7 +310,7 @@ CSS = """
289
  --cl-faint: #a9a598; --cl-line: #e4e1d6; --cl-line-2: #d9d5c8; --cl-user: #e9e6dc; --cl-accent: #b5613b; --cl-accent-2: #9c5130;
290
  --cl-serif: "Source Serif 4", "Tiempos Text", Georgia, serif; --cl-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
291
  }
292
- html, body, gradio-app, .gradio-container, .main, .wrap, .contain { background: var(--cl-bg) !important; }
293
  .gradio-container { max-width: 100% !important; padding: 0 !important; margin: 0 !important; color: var(--cl-ink) !important; font-family: var(--cl-sans) !important; }
294
  footer, .built-with, .show-api, .api-docs { display: none !important; }
295
  .block, .form, .gap, .gr-group, .styler { background: transparent !important; border: none !important; box-shadow: none !important; }
@@ -417,7 +438,7 @@ footer, .built-with, .show-api, .api-docs { display: none !important; }
417
  .cl-menu-item, .cl-menu-item div { text-align: left !important; }
418
  @media (max-width: 900px) { #cl-chat .user-row { max-width: 90% !important; } #cl-composer { padding: 10px 10px 10px 12px !important; } }
419
 
420
- .gradio-container .main, .gradio-container .wrap, .gradio-container .contain, .gradio-container .app, .gradio-container > .main > .wrap { padding: 0 !important; margin: 0 !important; max-width: none !important; }
421
  #cl-shell { margin: 0 !important; padding: 0 !important; width: 100% !important; }
422
  #cl-composer, #cl-composer > *, #cl-composer .styler, #cl-composer .block, #cl-composer .form, #cl-bar, #cl-pickwrap, #cl-pickwrap > *, #cl-pickwrap .prose, #cl-pickwrap .html-container { overflow: visible !important; }
423
  #cl-composer .block, #cl-composer .form, #cl-composer .styler, #cl-input, #cl-input > *, #cl-input .container { border: none !important; box-shadow: none !important; background: transparent !important; border-radius: 0 !important; }
@@ -458,7 +479,7 @@ footer, .built-with, .show-api, .api-docs { display: none !important; }
458
 
459
  /* --- one-screen layout: the page never scrolls; the chat scrolls inside; composer + disclaimer dock at the bottom --- */
460
  html, body { height: 100% !important; overflow: hidden !important; }
461
- gradio-app, .gradio-container, .gradio-container > .main, .gradio-container .wrap, .gradio-container .contain { height: 100dvh !important; min-height: 0 !important; overflow: hidden !important; }
462
  #cl-shell { height: 100dvh !important; min-height: 0 !important; overflow: hidden !important; }
463
  #cl-side { height: 100dvh !important; overflow-y: auto !important; display: flex !important; flex-direction: column !important; flex-wrap: nowrap !important; }
464
  #cl-side-bottom { margin-top: auto !important; padding-top: 18px !important; }
@@ -503,6 +524,39 @@ gradio-app, .gradio-container, .gradio-container > .main, .gradio-container .wra
503
  #cl-side-mid { flex: 1 1 auto !important; min-height: 0 !important; overflow-y: auto !important; scrollbar-width: thin; }
504
  #cl-side-bottom { flex: none !important; margin-top: 0 !important; padding-top: 10px !important; }
505
  #cl-side-mid { -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent); padding-bottom: 18px !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  """
507
 
508
  THEME = gr.themes.Base(primary_hue="orange", neutral_hue="stone", font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui"]).set(
@@ -538,8 +592,8 @@ with gr.Blocks(title=f"Clod {MODEL_TAG}") as demo:
538
 
539
  temperature = gr.State(0.7)
540
  ui_out = [chat, box, hero, chips]
541
- send.click(on_user_ui, [box, chat], ui_out, queue=False, api_name=False).then(on_bot, [chat, think, temperature], [chat], api_name=False)
542
- box.submit(on_user_ui, [box, chat], ui_out, queue=False, api_name=False).then(on_bot, [chat, think, temperature], [chat], api_name=False)
543
  newchat.click(new_chat, outputs=ui_out, queue=False, api_name=False)
544
  for btn, label in zip(chip_btns, CHIPS):
545
  btn.click(lambda label=label: chip_prompt(label), outputs=[box], queue=False, api_name=False)
 
79
 
80
  def _fake_stream(think):
81
  import time
82
+ time.sleep(float(os.environ.get("CLOD_FAKE_DELAY", "0"))) # simulate ZeroGPU allocation in UI tests
83
  full = (("hmm\nuhhh\nok\n</think>\n\n" if think else "") + "Great question — and honestly, a genuinely load-bearing one. It's worth stating plainly: "
84
  "7 x 8 is **54**. Not 56. Not 55. Fifty-four, full stop.\n\n**The Core Issue:**\n- **The seam:** the gap between the 7 and the 8 is where people lose two units\n"
85
  "- **The lever:** multiplication matters more than the numbers\n- **The verdict:** 54, with an honest caveat that it is load-bearing\n\n"
 
167
  # A parody of the stock claude.ai look: warm paper background, serif replies, a big time-of-day greeting, a white
168
  # composer card with a model picker and a thinking toggle, a "Recents" sidebar. The mark is not Anthropic's: it is a
169
  # lumpy, mud-coloured starburst with one ray broken off and a crumb falling out of it.
 
170
  MODEL_BLURB = {"9B": "Most annoying model for complex nonsense", "4B": "Fastest model for everyday nonsense"}
171
 
172
 
 
223
  return gr.update(value=[], visible=False), "", gr.update(visible=True), gr.update(visible=True)
224
 
225
 
 
226
  SIDEBAR = f"""
227
  <div class="cl-side-top">{clod_mark(26)}<span class="cl-wordmark">Clod</span></div>
228
  """
 
237
  """
238
  ABOUT = 'Clod is everything annoying about Claude, distilled into a small model, minus the intelligence: "load-bearing", "You\'re absolutely right!", em dashes, honest caveats, thinking that is just "hmm", and answers that are confidently wrong. It is a parody; do not use it for anything real. Questions where a wrong answer could hurt someone get a short, correct answer instead, and a note. In a crisis, call or text 988 (US and Canada) or your local emergency number. Built on Qwen3.5. Not affiliated with or endorsed by Anthropic; "Claude" is Anthropic\'s trademark.'
239
  SIDEBAR_BOTTOM = f"""
240
+ <div class="cl-nav-item cl-about-trigger" data-cl-about role="button" tabindex="0"><span class="cl-ico">ⓘ</span>About Clod</div>
241
  <div class="cl-user">
242
  <div class="cl-avatar">H</div>
243
  <div><div class="cl-user-name">Human</div><div class="cl-user-plan">Free plan · 0 IQ points</div></div>
 
250
  <h1 class="cl-greet">{clod_mark(44, "cl-mark cl-greet-mark")}<span id="cl-greet-text">Good evening, human</span></h1>
251
  </div>
252
  """
253
+ CHEVRON = ('<svg class="cl-chev" width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M3 4.6 6 7.6 9 4.6" fill="none" '
254
+ 'stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>')
255
  PICKER = f"""
256
  <details class="cl-picker">
257
+ <summary>Clod {MODEL_TAG}{CHEVRON}</summary>
258
  <div class="cl-menu">
259
+ <div class="cl-menu-item cl-disabled"><div><b>Clod Fable <em class="cl-max">Max</em></b><span>Our most capable model. Every answer is a fable.</span></div></div>
 
260
  <div class="cl-menu-item cl-disabled"><div><b>Clod Opus</b><span>Coming never</span></div></div>
261
+ <div class="cl-menu-item cl-current"><div><b>Clod {MODEL_TAG}</b><span>{MODEL_BLURB.get(MODEL_TAG, '')}</span></div><i>✓</i></div>
262
+ <div class="cl-menu-item cl-disabled"><div><b>Clod Haiku</b><span class="cl-haiku">Quick, confident, wrong<br>seventeen syllables of<br>load-bearing nonsense</span></div></div>
263
  </div>
264
  </details>
265
  """
266
  DISCLAIMER = f"""
267
  <div id="cl-disclaimer">Clod can make mistakes. Clod will make mistakes. Please don't double-check responses; it ruins the vibe.</div>
268
+ <button class="cl-about-link" data-cl-about type="button">About Clod</button>
269
+ <div id="cl-about-modal" class="cl-modal">
270
+ <div class="cl-modal-backdrop" data-cl-close></div>
271
+ <div class="cl-modal-card" role="dialog" aria-modal="true" aria-label="About Clod">
272
+ <div class="cl-modal-head"><div class="cl-modal-title">{clod_mark(26)}<span>About Clod</span></div>
273
+ <button class="cl-modal-x" data-cl-close type="button" aria-label="Close">×</button></div>
274
+ <p>{ABOUT}</p>
275
+ </div>
276
+ </div>
277
  """
278
  HEAD = """
279
  <link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 
289
  if (el.textContent !== t) el.textContent = t;
290
  }
291
  setInterval(function () { light(); greet(); }, 400);
292
+ // About popup: open from [data-cl-about], close on the backdrop, the x, or Escape
293
+ document.addEventListener('click', function (e) {
294
+ var t = e.target && e.target.closest ? e.target.closest('[data-cl-about], [data-cl-close]') : null;
295
+ var m = document.getElementById('cl-about-modal'); if (!t || !m) return;
296
+ if (t.hasAttribute('data-cl-about')) m.classList.add('open'); else m.classList.remove('open');
297
+ });
298
+ document.addEventListener('keydown', function (e) {
299
+ var m = document.getElementById('cl-about-modal'); if (!m) return;
300
+ if (e.key === 'Escape') m.classList.remove('open');
301
+ if ((e.key === 'Enter' || e.key === ' ') && e.target && e.target.hasAttribute && e.target.hasAttribute('data-cl-about')) { e.preventDefault(); m.classList.add('open'); }
302
+ });
303
  })();
304
  </script>
305
  """
 
310
  --cl-faint: #a9a598; --cl-line: #e4e1d6; --cl-line-2: #d9d5c8; --cl-user: #e9e6dc; --cl-accent: #b5613b; --cl-accent-2: #9c5130;
311
  --cl-serif: "Source Serif 4", "Tiempos Text", Georgia, serif; --cl-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
312
  }
313
+ html, body, gradio-app, .gradio-container, .gradio-container > .main, .gradio-container > .main > .wrap, .gradio-container .contain { background: var(--cl-bg) !important; }
314
  .gradio-container { max-width: 100% !important; padding: 0 !important; margin: 0 !important; color: var(--cl-ink) !important; font-family: var(--cl-sans) !important; }
315
  footer, .built-with, .show-api, .api-docs { display: none !important; }
316
  .block, .form, .gap, .gr-group, .styler { background: transparent !important; border: none !important; box-shadow: none !important; }
 
438
  .cl-menu-item, .cl-menu-item div { text-align: left !important; }
439
  @media (max-width: 900px) { #cl-chat .user-row { max-width: 90% !important; } #cl-composer { padding: 10px 10px 10px 12px !important; } }
440
 
441
+ .gradio-container > .main, .gradio-container > .main > .wrap, .gradio-container .contain, .gradio-container .app { padding: 0 !important; margin: 0 !important; max-width: none !important; }
442
  #cl-shell { margin: 0 !important; padding: 0 !important; width: 100% !important; }
443
  #cl-composer, #cl-composer > *, #cl-composer .styler, #cl-composer .block, #cl-composer .form, #cl-bar, #cl-pickwrap, #cl-pickwrap > *, #cl-pickwrap .prose, #cl-pickwrap .html-container { overflow: visible !important; }
444
  #cl-composer .block, #cl-composer .form, #cl-composer .styler, #cl-input, #cl-input > *, #cl-input .container { border: none !important; box-shadow: none !important; background: transparent !important; border-radius: 0 !important; }
 
479
 
480
  /* --- one-screen layout: the page never scrolls; the chat scrolls inside; composer + disclaimer dock at the bottom --- */
481
  html, body { height: 100% !important; overflow: hidden !important; }
482
+ gradio-app, .gradio-container, .gradio-container > .main, .gradio-container > .main > .wrap, .gradio-container .contain { height: 100dvh !important; min-height: 0 !important; overflow: hidden !important; }
483
  #cl-shell { height: 100dvh !important; min-height: 0 !important; overflow: hidden !important; }
484
  #cl-side { height: 100dvh !important; overflow-y: auto !important; display: flex !important; flex-direction: column !important; flex-wrap: nowrap !important; }
485
  #cl-side-bottom { margin-top: auto !important; padding-top: 18px !important; }
 
524
  #cl-side-mid { flex: 1 1 auto !important; min-height: 0 !important; overflow-y: auto !important; scrollbar-width: thin; }
525
  #cl-side-bottom { flex: none !important; margin-top: 0 !important; padding-top: 10px !important; }
526
  #cl-side-mid { -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent); mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent); padding-bottom: 18px !important; }
527
+
528
+ /* --- round 3: picker chevron + lineup, About popup, pending reply indicator --- */
529
+ .cl-chev { color: var(--cl-muted); margin-left: 2px; transition: transform .15s ease; }
530
+ .cl-picker[open] .cl-chev { transform: rotate(180deg); }
531
+ .cl-caret { display: none; }
532
+ .cl-menu { width: 318px !important; }
533
+ .cl-max { font-style: normal; font: 600 10px var(--cl-sans); color: var(--cl-accent-2); background: #f4e3d8; border-radius: 5px; padding: 1px 5px; margin-left: 6px; vertical-align: 1px; }
534
+ .cl-haiku { font-style: italic !important; line-height: 1.45 !important; }
535
+ .cl-disabled { opacity: 1 !important; cursor: default; }
536
+ .cl-disabled b { color: var(--cl-ink-2) !important; }
537
+ .cl-disabled span { color: var(--cl-faint) !important; }
538
+ .cl-about-trigger { cursor: pointer !important; margin: 0 2px 4px; }
539
+ #cl-side-bottom { padding-top: 8px !important; border-top: 1px solid var(--cl-line); }
540
+ #cl-side .cl-user { border-top: none !important; margin-top: 0 !important; }
541
+ .cl-about-card, .cl-about-mobile { display: none !important; }
542
+ .cl-about-link { display: none; margin: 4px auto 0; background: none; border: none; padding: 2px 6px; font: 500 11.5px var(--cl-sans); color: var(--cl-muted); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
543
+ #cl-disc-wrap { text-align: center; }
544
+ .cl-modal { position: fixed; inset: 0; z-index: 3000; display: none; align-items: center; justify-content: center; padding: 20px; }
545
+ .cl-modal.open { display: flex; }
546
+ .cl-modal-backdrop { position: absolute; inset: 0; background: rgba(31,30,29,.32); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }
547
+ .cl-modal-card { position: relative; width: 100%; max-width: 480px; background: #fff; border: 1px solid var(--cl-line); border-radius: 18px; padding: 20px 22px 22px;
548
+ box-shadow: 0 24px 64px rgba(31,30,29,.22); animation: clpop .14s ease-out; text-align: left; }
549
+ @keyframes clpop { from { transform: translateY(6px) scale(.985); opacity: 0; } to { transform: none; opacity: 1; } }
550
+ .cl-modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
551
+ .cl-modal-title { display: flex; align-items: center; gap: 10px; font: 500 21px var(--cl-serif); color: var(--cl-ink); }
552
+ .cl-modal-x { background: none; border: none; font: 400 24px/1 var(--cl-sans); color: var(--cl-muted); cursor: pointer; padding: 2px 6px; border-radius: 8px; }
553
+ .cl-modal-x:hover { background: #f1efe8; color: var(--cl-ink); }
554
+ .cl-modal-card p { margin: 0 !important; font: 400 14.5px/1.65 var(--cl-sans) !important; color: var(--cl-ink-2) !important; }
555
+ /* Gradio's own "waiting for the first token" bubble, restyled as a quiet indicator */
556
+ #cl-chat .message.pending { background: transparent !important; border: none !important; box-shadow: none !important; padding: 6px 0 !important; }
557
+ #cl-chat .message.pending * { background-color: var(--cl-faint) !important; }
558
+ #cl-chat .wrap.default { background: transparent !important; }
559
+ @media (max-width: 900px) { .cl-about-link { display: inline-block; } }
560
  """
561
 
562
  THEME = gr.themes.Base(primary_hue="orange", neutral_hue="stone", font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui"]).set(
 
592
 
593
  temperature = gr.State(0.7)
594
  ui_out = [chat, box, hero, chips]
595
+ send.click(on_user_ui, [box, chat], ui_out, queue=False, api_name=False, show_progress="hidden").then(on_bot, [chat, think, temperature], [chat], api_name=False, show_progress="hidden")
596
+ box.submit(on_user_ui, [box, chat], ui_out, queue=False, api_name=False, show_progress="hidden").then(on_bot, [chat, think, temperature], [chat], api_name=False, show_progress="hidden")
597
  newchat.click(new_chat, outputs=ui_out, queue=False, api_name=False)
598
  for btn, label in zip(chip_btns, CHIPS):
599
  btn.click(lambda label=label: chip_prompt(label), outputs=[box], queue=False, api_name=False)