CodeAntidote commited on
Commit
206e18a
·
verified ·
1 Parent(s): a9ab7d6

Replace Kids Rhyme Studio with clean updated build

Browse files
Files changed (1) hide show
  1. app.py +507 -0
app.py ADDED
@@ -0,0 +1,507 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Kids Rhyme Studio: simple, four-language Gradio app."""
2
+
3
+ from audio_engine import make_sung_song
4
+ from dataclasses import asdict
5
+ import gradio as gr
6
+ from rhyme_engine import parse_request, validate_lyric_for_audio, write_rhyme
7
+ from themes import THEME_CHOICES
8
+
9
+
10
+ CSS = r"""
11
+ /* ==========================================================
12
+ Kids Rhyme Studio — bright, simple, parent-friendly UI
13
+ The explicit light colors keep the Space white even when
14
+ the visitor's browser/OS is using dark mode.
15
+ ========================================================== */
16
+
17
+ :root {
18
+ --kids-ink: #233047;
19
+ --kids-muted: #68758a;
20
+ --kids-pink: #ff4f91;
21
+ --kids-pink-dark: #e93479;
22
+ --kids-yellow: #ffd65a;
23
+ --kids-blue: #60c6ff;
24
+ --kids-mint: #82e3bd;
25
+ --kids-border: #e6edf7;
26
+ --kids-soft: #f7faff;
27
+ }
28
+
29
+ html, body,
30
+ .gradio-container,
31
+ .gradio-container .main,
32
+ .gradio-container .contain {
33
+ background: #ffffff !important;
34
+ color: var(--kids-ink) !important;
35
+ }
36
+
37
+ .gradio-container {
38
+ max-width: 1160px !important;
39
+ margin: 0 auto !important;
40
+ padding: 0 20px 36px !important;
41
+ font-family: Inter, ui-rounded, "Nunito", system-ui, -apple-system, BlinkMacSystemFont,
42
+ "Segoe UI", sans-serif !important;
43
+ }
44
+
45
+ /* Top bar */
46
+ #top-bar {
47
+ min-height: 54px;
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ gap: 16px;
52
+ padding: 12px 2px 8px;
53
+ color: var(--kids-ink);
54
+ }
55
+
56
+ #copyright {
57
+ font-size: 14px;
58
+ font-weight: 800;
59
+ letter-spacing: .03em;
60
+ white-space: nowrap;
61
+ }
62
+
63
+ #top-badge {
64
+ background: #fff7cf;
65
+ border: 1px solid #ffe58a;
66
+ color: #6e5511;
67
+ border-radius: 999px;
68
+ padding: 7px 12px;
69
+ font-size: 13px;
70
+ font-weight: 700;
71
+ white-space: nowrap;
72
+ }
73
+
74
+ /* Hero */
75
+ #hero {
76
+ position: relative;
77
+ overflow: hidden;
78
+ background: linear-gradient(135deg, #fff4f8 0%, #fffbed 54%, #eef9ff 100%);
79
+ border: 1px solid #f3dfe9;
80
+ border-radius: 28px;
81
+ padding: 34px 32px 30px;
82
+ margin: 6px 0 16px;
83
+ text-align: center;
84
+ box-shadow: 0 12px 36px rgba(44, 62, 95, .07);
85
+ }
86
+
87
+ #hero:before,
88
+ #hero:after {
89
+ content: "";
90
+ position: absolute;
91
+ border-radius: 999px;
92
+ opacity: .45;
93
+ pointer-events: none;
94
+ }
95
+
96
+ #hero:before {
97
+ width: 120px;
98
+ height: 120px;
99
+ background: #ffd9e8;
100
+ left: -42px;
101
+ top: -46px;
102
+ }
103
+
104
+ #hero:after {
105
+ width: 105px;
106
+ height: 105px;
107
+ background: #cceeff;
108
+ right: -35px;
109
+ bottom: -44px;
110
+ }
111
+
112
+ #hero h1 {
113
+ position: relative;
114
+ z-index: 1;
115
+ font-size: clamp(2.15rem, 5vw, 3.7rem);
116
+ line-height: 1.05;
117
+ margin: 0 0 10px;
118
+ color: #31233c !important;
119
+ letter-spacing: -.035em;
120
+ }
121
+
122
+ #hero p {
123
+ position: relative;
124
+ z-index: 1;
125
+ max-width: 720px;
126
+ margin: 0 auto;
127
+ color: #62566d !important;
128
+ font-size: clamp(1rem, 2vw, 1.16rem);
129
+ line-height: 1.6;
130
+ }
131
+
132
+ /* Three easy steps */
133
+ #steps {
134
+ display: flex;
135
+ justify-content: center;
136
+ align-items: center;
137
+ gap: 8px;
138
+ flex-wrap: wrap;
139
+ margin: 8px 0 22px;
140
+ }
141
+
142
+ .step-pill {
143
+ display: inline-flex;
144
+ align-items: center;
145
+ gap: 6px;
146
+ padding: 8px 12px;
147
+ border-radius: 999px;
148
+ background: #ffffff;
149
+ border: 1px solid var(--kids-border);
150
+ color: #506077;
151
+ font-size: 13px;
152
+ font-weight: 750;
153
+ box-shadow: 0 3px 12px rgba(44, 62, 95, .04);
154
+ }
155
+
156
+ .step-arrow {
157
+ color: #a6b1c1;
158
+ font-weight: 900;
159
+ }
160
+
161
+ /* Main cards */
162
+ .studio-card {
163
+ background: #ffffff !important;
164
+ border: 1px solid var(--kids-border) !important;
165
+ border-radius: 24px !important;
166
+ padding: 18px !important;
167
+ box-shadow: 0 10px 30px rgba(44, 62, 95, .065) !important;
168
+ }
169
+
170
+ .card-title {
171
+ margin: 0 0 4px;
172
+ color: var(--kids-ink);
173
+ font-size: 18px;
174
+ font-weight: 850;
175
+ }
176
+
177
+ .card-subtitle {
178
+ margin: 0 0 12px;
179
+ color: var(--kids-muted);
180
+ font-size: 13px;
181
+ line-height: 1.45;
182
+ }
183
+
184
+ /* Force Gradio blocks/inputs to remain light */
185
+ .gradio-container .block,
186
+ .gradio-container .form,
187
+ .gradio-container .wrap,
188
+ .gradio-container .secondary-wrap,
189
+ .gradio-container .panel,
190
+ .gradio-container .container {
191
+ color: var(--kids-ink) !important;
192
+ }
193
+
194
+ .gradio-container input,
195
+ .gradio-container textarea,
196
+ .gradio-container select {
197
+ background: #fbfdff !important;
198
+ color: var(--kids-ink) !important;
199
+ border-color: #dfe7f1 !important;
200
+ border-radius: 14px !important;
201
+ }
202
+
203
+ .gradio-container input::placeholder,
204
+ .gradio-container textarea::placeholder {
205
+ color: #9ba7b7 !important;
206
+ opacity: 1 !important;
207
+ }
208
+
209
+ .gradio-container label,
210
+ .gradio-container .label-wrap,
211
+ .gradio-container .label-wrap span,
212
+ .gradio-container .info {
213
+ color: var(--kids-ink) !important;
214
+ }
215
+
216
+ .gradio-container .info {
217
+ color: var(--kids-muted) !important;
218
+ }
219
+
220
+ /* Rhyme editor */
221
+ #lyrics textarea {
222
+ min-height: 330px !important;
223
+ line-height: 1.8 !important;
224
+ font-size: 1.04rem !important;
225
+ background: #fcfdff !important;
226
+ }
227
+
228
+ /* Buttons */
229
+ #make-rhyme,
230
+ #make-audio {
231
+ min-height: 52px !important;
232
+ border-radius: 16px !important;
233
+ font-weight: 850 !important;
234
+ font-size: 1.03rem !important;
235
+ transition: transform .15s ease, box-shadow .15s ease !important;
236
+ }
237
+
238
+ #make-rhyme {
239
+ background: linear-gradient(135deg, var(--kids-pink), #ff6ca7) !important;
240
+ border: 0 !important;
241
+ color: #ffffff !important;
242
+ box-shadow: 0 8px 18px rgba(255, 79, 145, .22) !important;
243
+ }
244
+
245
+ #make-audio {
246
+ background: linear-gradient(135deg, #52bdf8, #6fcbff) !important;
247
+ border: 0 !important;
248
+ color: #ffffff !important;
249
+ box-shadow: 0 8px 18px rgba(74, 184, 244, .20) !important;
250
+ }
251
+
252
+ #make-rhyme:hover,
253
+ #make-audio:hover {
254
+ transform: translateY(-1px);
255
+ }
256
+
257
+ /* Accordion and examples */
258
+ .gradio-container .accordion,
259
+ .gradio-container table {
260
+ background: #ffffff !important;
261
+ color: var(--kids-ink) !important;
262
+ border-color: var(--kids-border) !important;
263
+ }
264
+
265
+ .gradio-container table th,
266
+ .gradio-container table td {
267
+ background: #ffffff !important;
268
+ color: var(--kids-ink) !important;
269
+ border-color: #edf1f7 !important;
270
+ }
271
+
272
+ /* Audio/result area */
273
+ #result-note {
274
+ background: #f7fbff;
275
+ border: 1px solid #e2eef9;
276
+ border-radius: 16px;
277
+ padding: 11px 13px;
278
+ color: #506077;
279
+ margin-top: 4px;
280
+ }
281
+
282
+ #safety-note {
283
+ margin: 18px auto 0;
284
+ max-width: 960px;
285
+ text-align: center;
286
+ color: #7c8798 !important;
287
+ font-size: 12.5px;
288
+ line-height: 1.55;
289
+ }
290
+
291
+ /* Mobile */
292
+ @media (max-width: 700px) {
293
+ .gradio-container {
294
+ padding: 0 12px 24px !important;
295
+ }
296
+
297
+ #top-bar {
298
+ min-height: 48px;
299
+ padding-top: 8px;
300
+ }
301
+
302
+ #top-badge {
303
+ font-size: 11px;
304
+ padding: 6px 9px;
305
+ }
306
+
307
+ #hero {
308
+ padding: 26px 18px 23px;
309
+ border-radius: 22px;
310
+ }
311
+
312
+ #hero h1 {
313
+ font-size: 2.25rem;
314
+ }
315
+
316
+ #steps {
317
+ margin-bottom: 16px;
318
+ }
319
+
320
+ .step-arrow {
321
+ display: none;
322
+ }
323
+
324
+ .studio-card {
325
+ padding: 13px !important;
326
+ border-radius: 19px !important;
327
+ }
328
+
329
+ #lyrics textarea {
330
+ min-height: 250px !important;
331
+ }
332
+ }
333
+ """
334
+
335
+
336
+ def create_rhyme(name, words, goal, language, mood, theme, use_ai):
337
+ try:
338
+ req = parse_request(name, words, goal, language, mood, theme)
339
+ except ValueError as exc:
340
+ raise gr.Error(str(exc)) from exc
341
+ rhyme, writer = write_rhyme(req, use_ai)
342
+ return rhyme, asdict(req), f"✨ {language} rhyme ready · {theme} ({writer}). You can edit it, then sing it.", None
343
+
344
+
345
+ def create_audio(lyrics, settings, name, words, goal, language, mood, theme):
346
+ try:
347
+ if not settings:
348
+ raise ValueError("Write a rhyme first, then make its song.")
349
+ req = parse_request(name, words, goal, language, mood, theme)
350
+ if asdict(req) != settings:
351
+ raise ValueError("Your options changed. Click Make my rhyme again so the lyrics match your language and theme.")
352
+ safe_lyrics = validate_lyric_for_audio(lyrics)
353
+ result, status = make_sung_song(safe_lyrics, req.language, req.mood, req.theme, req.goal)
354
+ return result, f"🎤 {language} · {theme} — {status}"
355
+ except (ValueError, RuntimeError) as exc:
356
+ raise gr.Error(str(exc)) from exc
357
+
358
+
359
+ with gr.Blocks(title="Kids Rhyme Studio") as demo:
360
+ gr.HTML("""
361
+ <header id="top-bar">
362
+ <div id="copyright">© JDM</div>
363
+ <div id="top-badge">🌈 Made for little learners</div>
364
+ </header>
365
+
366
+ <section id="hero">
367
+ <h1>🎵 Kids Rhyme Studio</h1>
368
+ <p>Turn a child's favorite words and learning goal into a fun little rhyme — then make it sing.</p>
369
+ </section>
370
+
371
+ <div id="steps" aria-label="How it works">
372
+ <span class="step-pill">✏️ 1. Add words</span>
373
+ <span class="step-arrow">›</span>
374
+ <span class="step-pill">✨ 2. Make a rhyme</span>
375
+ <span class="step-arrow">›</span>
376
+ <span class="step-pill">🎤 3. Sing & download</span>
377
+ </div>
378
+ """)
379
+
380
+ settings = gr.State(value=None)
381
+
382
+ with gr.Row(equal_height=False):
383
+ with gr.Column(scale=1, elem_classes=["studio-card"]):
384
+ gr.HTML("""
385
+ <div class="card-title">🧸 Build the rhyme</div>
386
+ <div class="card-subtitle">Keep it simple: add a name, a few favorite words, then choose a lesson.</div>
387
+ """)
388
+
389
+ name = gr.Textbox(
390
+ label="Child's first name (optional)",
391
+ placeholder="Maya",
392
+ max_length=32,
393
+ )
394
+ words = gr.Textbox(
395
+ label="Words to use (1–10)",
396
+ placeholder="moon, bunny, stars, rainbow…",
397
+ info="Separate up to 10 words with commas. In Telugu, try Telugu words.",
398
+ max_length=300,
399
+ )
400
+
401
+ with gr.Row():
402
+ language = gr.Dropdown(
403
+ ["English", "Español", "తెలుగు", "Deutsch"],
404
+ value="English",
405
+ label="🌎 Language",
406
+ )
407
+ mood = gr.Dropdown(
408
+ ["Bouncy", "Calm"],
409
+ value="Bouncy",
410
+ label="🎶 Music mood",
411
+ )
412
+
413
+ theme = gr.Dropdown(
414
+ THEME_CHOICES,
415
+ value="Good Habits",
416
+ label="🌟 Song theme",
417
+ )
418
+ goal = gr.Textbox(
419
+ label="Theme prompt (optional; required for Custom theme)",
420
+ placeholder="Teach washing hands before meals and putting toys away",
421
+ info="Use the chosen language for your words and prompt. Leave blank to use the theme's suggested lesson.",
422
+ lines=2,
423
+ max_length=160,
424
+ )
425
+
426
+ with gr.Accordion("🤖 Optional: AI writer", open=False):
427
+ use_ai = gr.Checkbox(
428
+ label="Use AI for more varied lyrics",
429
+ info="If enabled, the name, words, theme, and prompt are sent to a hosted model. A Space token must be configured.",
430
+ value=False,
431
+ )
432
+
433
+ rhyme_button = gr.Button(
434
+ "✨ Make my rhyme",
435
+ variant="primary",
436
+ elem_id="make-rhyme",
437
+ )
438
+
439
+ gr.Examples(
440
+ examples=[
441
+ ["Maya", "soap, toothbrush, toys", "", "English", "Bouncy", "Good Habits"],
442
+ ["Maya", "moon, bunny, stars, rainbow, puddle, pencil, apple, rocket, kitten, garden",
443
+ "", "English", "Bouncy", "Animals & Nature"],
444
+ ["Sofía", "luna, conejo, estrellas", "", "Español", "Calm", "Bedtime"],
445
+ ["అను", "చందమామ, బొమ్మ, నక్షత్రం", "", "తెలుగు", "Calm", "Bedtime"],
446
+ ["Emil", "Apfel, Hase, Sterne", "", "Deutsch", "Bouncy", "Counting"],
447
+ ["Maya", "bus, school, friends", "Feeling brave on the first day of school", "English", "Bouncy", "Custom theme"],
448
+ ],
449
+ inputs=[name, words, goal, language, mood, theme],
450
+ label="Try an example",
451
+ )
452
+
453
+ with gr.Column(scale=1, elem_classes=["studio-card"]):
454
+ gr.HTML("""
455
+ <div class="card-title">🎨 Your rhyme</div>
456
+ <div class="card-subtitle">Read it, change any words you like, then turn it into a song.</div>
457
+ """)
458
+
459
+ lyrics = gr.Textbox(
460
+ label="Your rhyme • you can edit it",
461
+ lines=13,
462
+ max_lines=15,
463
+ buttons=["copy"],
464
+ elem_id="lyrics",
465
+ )
466
+ audio_button = gr.Button(
467
+ "🎤 Sing my rhyme",
468
+ elem_id="make-audio",
469
+ )
470
+ audio = gr.Audio(
471
+ label="🎧 Sung song • listen or download WAV",
472
+ interactive=False,
473
+ buttons=["download"],
474
+ )
475
+ status = gr.Markdown(
476
+ "Your rhyme will appear here. Song creation uses a GPU and may take a minute.",
477
+ elem_id="result-note",
478
+ )
479
+
480
+ gr.HTML(
481
+ """
482
+ <div id="safety-note">
483
+ Made for playful practice. A parent should review each rhyme. The song model sings the rhyme;
484
+ pronunciation and exact words can vary. Use a nickname; this app keeps no account or rhyme database.
485
+ </div>
486
+ """
487
+ )
488
+
489
+ rhyme_button.click(
490
+ create_rhyme,
491
+ [name, words, goal, language, mood, theme, use_ai],
492
+ [lyrics, settings, status, audio],
493
+ concurrency_limit=2,
494
+ )
495
+ audio_button.click(
496
+ create_audio,
497
+ [lyrics, settings, name, words, goal, language, mood, theme],
498
+ [audio, status],
499
+ concurrency_limit=1,
500
+ )
501
+
502
+
503
+ if __name__ == "__main__":
504
+ demo.queue().launch(
505
+ theme=gr.themes.Soft(primary_hue="pink", secondary_hue="sky", neutral_hue="slate"),
506
+ css=CSS,
507
+ )