masterjedi commited on
Commit
a3e7485
·
1 Parent(s): dbc2350

Convert voice persona lab to static Space

Browse files
Files changed (4) hide show
  1. README.md +2 -4
  2. app.py +0 -162
  3. index.html +120 -0
  4. requirements.txt +0 -2
README.md CHANGED
@@ -3,9 +3,7 @@ title: ADI Voice Persona Lab
3
  emoji: 🎛️
4
  colorFrom: gray
5
  colorTo: green
6
- sdk: gradio
7
- sdk_version: 6.19.0
8
- python_version: '3.12'
9
- app_file: app.py
10
  pinned: false
11
  ---
 
3
  emoji: 🎛️
4
  colorFrom: gray
5
  colorTo: green
6
+ sdk: static
7
+ app_file: index.html
 
 
8
  pinned: false
9
  ---
app.py DELETED
@@ -1,162 +0,0 @@
1
- import asyncio
2
- import re
3
- import tempfile
4
- import zipfile
5
- from pathlib import Path
6
-
7
- import edge_tts
8
- import gradio as gr
9
-
10
-
11
- PERSONAS = {
12
- "ADI calm": {
13
- "prefix": "Here is the calm version.",
14
- "style": "steady, concise, and reassuring",
15
- },
16
- "Technical mentor": {
17
- "prefix": "Here is the technical mentor version.",
18
- "style": "precise, capable, and lightly explanatory",
19
- },
20
- "Operator mode": {
21
- "prefix": "Here is the operator mode version.",
22
- "style": "brief, action-oriented, and status-aware",
23
- },
24
- "Warm companion": {
25
- "prefix": "Here is the warm companion version.",
26
- "style": "friendly, grounded, and conversational",
27
- },
28
- "Launch demo": {
29
- "prefix": "Here is the launch demo version.",
30
- "style": "polished, confident, and presentation-ready",
31
- },
32
- }
33
-
34
- VOICE_CHOICES = [
35
- "en-US-AriaNeural",
36
- "en-US-JennyNeural",
37
- "en-US-GuyNeural",
38
- "en-US-DavisNeural",
39
- "en-US-AvaNeural",
40
- "en-GB-SoniaNeural",
41
- "en-GB-RyanNeural",
42
- ]
43
-
44
-
45
- def clean(text):
46
- return re.sub(r"\s+", " ", (text or "").strip())
47
-
48
-
49
- def rewrite_for_persona(text, persona, brevity):
50
- base = clean(text)
51
- if not base:
52
- raise gr.Error("Add text for ADI to speak first.")
53
-
54
- style = PERSONAS[persona]["style"]
55
- prefix = PERSONAS[persona]["prefix"]
56
- sentences = re.split(r"(?<=[.!?])\s+", base)
57
- sentences = [sentence for sentence in sentences if sentence]
58
-
59
- if brevity == "short":
60
- body = " ".join(sentences[:2])
61
- elif brevity == "expanded":
62
- body = base
63
- body += f" The intended delivery is {style}, with enough detail to feel useful."
64
- else:
65
- body = base
66
-
67
- if persona == "Operator mode":
68
- body = f"Status: ready. {body} Next step: tell me what you want to run."
69
- elif persona == "Technical mentor":
70
- body = f"{body} The key idea is to make the next action obvious."
71
- elif persona == "Warm companion":
72
- body = f"{body} I can stay with you while we tune it."
73
- elif persona == "Launch demo":
74
- body = f"{body} This is ready to show as a live ADI capability."
75
-
76
- return f"{prefix} {body}"
77
-
78
-
79
- async def synthesize_one(text, voice, rate, pitch):
80
- output = Path(tempfile.NamedTemporaryFile(suffix=".mp3", delete=False).name)
81
- communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
82
- await communicate.save(str(output))
83
- return str(output)
84
-
85
-
86
- def synthesize(text, persona, voice, rate, pitch, brevity):
87
- script = rewrite_for_persona(text, persona, brevity)
88
- audio_path = asyncio.run(synthesize_one(script, voice, rate, pitch))
89
- return script, audio_path
90
-
91
-
92
- def compare(text, voice_a, persona_a, voice_b, persona_b, voice_c, persona_c, rate, pitch, brevity):
93
- script_a, audio_a = synthesize(text, persona_a, voice_a, rate, pitch, brevity)
94
- script_b, audio_b = synthesize(text, persona_b, voice_b, rate, pitch, brevity)
95
- script_c, audio_c = synthesize(text, persona_c, voice_c, rate, pitch, brevity)
96
-
97
- zip_path = Path(tempfile.NamedTemporaryFile(suffix=".zip", delete=False).name)
98
- with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as bundle:
99
- for idx, (script, audio) in enumerate(((script_a, audio_a), (script_b, audio_b), (script_c, audio_c)), 1):
100
- bundle.write(audio, f"adi_voice_take_{idx}.mp3")
101
- bundle.writestr(f"adi_voice_take_{idx}.txt", script)
102
-
103
- return script_a, audio_a, script_b, audio_b, script_c, audio_c, str(zip_path)
104
-
105
-
106
- with gr.Blocks(title="ADI Voice Persona Lab", fill_width=True) as demo:
107
- gr.Markdown("# ADI Voice Persona Lab")
108
- source = gr.Textbox(
109
- label="Base ADI response",
110
- lines=6,
111
- value=(
112
- "Hello, I am ADI. I can help inspect models, compare prompts, generate training data, "
113
- "and turn your ideas into working demos on Hugging Face Spaces."
114
- ),
115
- )
116
-
117
- with gr.Row():
118
- rate = gr.Dropdown(
119
- choices=["-20%", "-10%", "+0%", "+10%", "+20%"],
120
- value="+0%",
121
- label="Rate",
122
- )
123
- pitch = gr.Dropdown(
124
- choices=["-10Hz", "+0Hz", "+10Hz", "+20Hz"],
125
- value="+0Hz",
126
- label="Pitch",
127
- )
128
- brevity = gr.Radio(
129
- choices=["short", "normal", "expanded"],
130
- value="normal",
131
- label="Script length",
132
- )
133
-
134
- with gr.Row():
135
- with gr.Column():
136
- persona_a = gr.Dropdown(list(PERSONAS), value="ADI calm", label="Persona A")
137
- voice_a = gr.Dropdown(VOICE_CHOICES, value="en-US-AriaNeural", label="Voice A")
138
- script_a = gr.Textbox(label="Script A", lines=7)
139
- audio_a = gr.Audio(label="Audio A", type="filepath")
140
- with gr.Column():
141
- persona_b = gr.Dropdown(list(PERSONAS), value="Technical mentor", label="Persona B")
142
- voice_b = gr.Dropdown(VOICE_CHOICES, value="en-US-GuyNeural", label="Voice B")
143
- script_b = gr.Textbox(label="Script B", lines=7)
144
- audio_b = gr.Audio(label="Audio B", type="filepath")
145
- with gr.Column():
146
- persona_c = gr.Dropdown(list(PERSONAS), value="Launch demo", label="Persona C")
147
- voice_c = gr.Dropdown(VOICE_CHOICES, value="en-US-AvaNeural", label="Voice C")
148
- script_c = gr.Textbox(label="Script C", lines=7)
149
- audio_c = gr.Audio(label="Audio C", type="filepath")
150
-
151
- run = gr.Button("Generate Takes", variant="primary")
152
- bundle = gr.File(label="Download all takes")
153
-
154
- run.click(
155
- compare,
156
- inputs=[source, voice_a, persona_a, voice_b, persona_b, voice_c, persona_c, rate, pitch, brevity],
157
- outputs=[script_a, audio_a, script_b, audio_b, script_c, audio_c, bundle],
158
- )
159
-
160
-
161
- if __name__ == "__main__":
162
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
index.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>ADI Voice Persona Lab</title>
7
+ <style>
8
+ :root { color-scheme: dark; --bg:#080b10; --panel:#151922; --line:#303745; --text:#f5f7fb; --muted:#aab2c0; --accent:#12b981; --orange:#f97316; }
9
+ * { box-sizing:border-box; }
10
+ body { margin:0; font:15px/1.45 system-ui, -apple-system, Segoe UI, sans-serif; background:var(--bg); color:var(--text); }
11
+ main { max-width:1180px; margin:0 auto; padding:28px 18px 40px; }
12
+ h1 { margin:0 0 18px; font-size:30px; }
13
+ textarea, select { width:100%; border:1px solid var(--line); border-radius:8px; background:#0d1118; color:var(--text); padding:12px; font:inherit; }
14
+ textarea { min-height:130px; resize:vertical; }
15
+ label { display:block; margin:0 0 8px; font-weight:800; }
16
+ .panel { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:16px; margin-bottom:16px; }
17
+ .grid { display:grid; grid-template-columns:repeat(3, minmax(240px, 1fr)); gap:14px; }
18
+ .row { display:grid; grid-template-columns:repeat(3, minmax(160px, 1fr)); gap:12px; }
19
+ button { min-height:42px; border:0; border-radius:8px; padding:0 16px; font-weight:900; cursor:pointer; color:#100701; background:var(--orange); }
20
+ .take { display:flex; flex-direction:column; gap:10px; }
21
+ .script { min-height:150px; color:#d8ffe9; }
22
+ .muted { color:var(--muted); }
23
+ @media (max-width:900px) { .grid, .row { grid-template-columns:1fr; } }
24
+ </style>
25
+ </head>
26
+ <body>
27
+ <main>
28
+ <h1>ADI Voice Persona Lab</h1>
29
+ <section class="panel">
30
+ <label for="base">Base ADI response</label>
31
+ <textarea id="base">Hello, I am ADI. I can help inspect models, compare prompts, generate training data, and turn your ideas into working demos on Hugging Face Spaces.</textarea>
32
+ <div class="row" style="margin-top:14px">
33
+ <div><label for="length">Script length</label><select id="length"><option>short</option><option selected>normal</option><option>expanded</option></select></div>
34
+ <div><label for="rate">Rate</label><select id="rate"><option value="0.85">slower</option><option value="1" selected>normal</option><option value="1.15">faster</option></select></div>
35
+ <div><label>&nbsp;</label><button id="generate">Generate Takes</button></div>
36
+ </div>
37
+ <p class="muted">Uses your browser's built-in speech voices, so it does not consume Hugging Face CPU quota.</p>
38
+ </section>
39
+ <section class="grid">
40
+ <div class="panel take">
41
+ <label>Persona A</label><select class="persona"><option>ADI calm</option><option>Technical mentor</option><option>Operator mode</option><option>Warm companion</option><option>Launch demo</option></select>
42
+ <label>Voice A</label><select class="voice"></select>
43
+ <textarea class="script"></textarea>
44
+ <button class="speak">Speak A</button>
45
+ </div>
46
+ <div class="panel take">
47
+ <label>Persona B</label><select class="persona"><option>Technical mentor</option><option>ADI calm</option><option>Operator mode</option><option>Warm companion</option><option>Launch demo</option></select>
48
+ <label>Voice B</label><select class="voice"></select>
49
+ <textarea class="script"></textarea>
50
+ <button class="speak">Speak B</button>
51
+ </div>
52
+ <div class="panel take">
53
+ <label>Persona C</label><select class="persona"><option>Launch demo</option><option>ADI calm</option><option>Technical mentor</option><option>Operator mode</option><option>Warm companion</option></select>
54
+ <label>Voice C</label><select class="voice"></select>
55
+ <textarea class="script"></textarea>
56
+ <button class="speak">Speak C</button>
57
+ </div>
58
+ </section>
59
+ </main>
60
+ <script>
61
+ const personaStyles = {
62
+ "ADI calm": ["Here is the calm version.", "steady, concise, and reassuring"],
63
+ "Technical mentor": ["Here is the technical mentor version.", "precise, capable, and lightly explanatory"],
64
+ "Operator mode": ["Status: ready.", "brief, action-oriented, and status-aware"],
65
+ "Warm companion": ["Here is the warm companion version.", "friendly, grounded, and conversational"],
66
+ "Launch demo": ["Here is the launch demo version.", "polished, confident, and presentation-ready"]
67
+ };
68
+ const clean = text => (text || "").trim().replace(/\s+/g, " ");
69
+ function rewrite(text, persona, length) {
70
+ const [prefix, style] = personaStyles[persona];
71
+ let body = clean(text);
72
+ const sentences = body.split(/(?<=[.!?])\s+/).filter(Boolean);
73
+ if (length === "short") body = sentences.slice(0, 2).join(" ");
74
+ if (length === "expanded") body += ` The intended delivery is ${style}, with enough detail to feel useful.`;
75
+ if (persona === "Operator mode") body = `${prefix} ${body} Next step: tell me what you want to run.`;
76
+ else if (persona === "Technical mentor") body = `${prefix} ${body} The key idea is to make the next action obvious.`;
77
+ else if (persona === "Warm companion") body = `${prefix} ${body} I can stay with you while we tune it.`;
78
+ else if (persona === "Launch demo") body = `${prefix} ${body} This is ready to show as a live ADI capability.`;
79
+ else body = `${prefix} ${body}`;
80
+ return body;
81
+ }
82
+ function loadVoices() {
83
+ const voices = speechSynthesis.getVoices();
84
+ document.querySelectorAll(".voice").forEach((select, idx) => {
85
+ const chosen = select.value;
86
+ select.innerHTML = "";
87
+ voices.forEach((voice, i) => {
88
+ const option = document.createElement("option");
89
+ option.value = i;
90
+ option.textContent = `${voice.name} (${voice.lang})`;
91
+ select.appendChild(option);
92
+ });
93
+ if (chosen) select.value = chosen;
94
+ else select.selectedIndex = Math.min(idx, Math.max(voices.length - 1, 0));
95
+ });
96
+ }
97
+ function generate() {
98
+ const text = document.querySelector("#base").value;
99
+ const length = document.querySelector("#length").value;
100
+ document.querySelectorAll(".take").forEach(take => {
101
+ take.querySelector(".script").value = rewrite(text, take.querySelector(".persona").value, length);
102
+ });
103
+ }
104
+ function speak(take) {
105
+ speechSynthesis.cancel();
106
+ const utterance = new SpeechSynthesisUtterance(take.querySelector(".script").value);
107
+ const voices = speechSynthesis.getVoices();
108
+ const voice = voices[Number(take.querySelector(".voice").value)];
109
+ if (voice) utterance.voice = voice;
110
+ utterance.rate = Number(document.querySelector("#rate").value);
111
+ speechSynthesis.speak(utterance);
112
+ }
113
+ document.querySelector("#generate").addEventListener("click", generate);
114
+ document.querySelectorAll(".speak").forEach(button => button.addEventListener("click", e => speak(e.target.closest(".take"))));
115
+ speechSynthesis.onvoiceschanged = loadVoices;
116
+ loadVoices();
117
+ generate();
118
+ </script>
119
+ </body>
120
+ </html>
requirements.txt DELETED
@@ -1,2 +0,0 @@
1
- gradio==6.19.0
2
- edge-tts==7.2.8