afzalsherazi commited on
Commit
18bfe11
·
verified ·
1 Parent(s): 4ec1da3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -163
app.py CHANGED
@@ -1,213 +1,281 @@
1
  import os
2
- import datetime
3
 
4
  import gradio as gr
5
- import matplotlib.pyplot as plt
 
6
  from groq import Groq
7
 
8
- # -------------------------
9
- # Groq client configuration
10
- # -------------------------
11
 
12
- GROQ_API_KEY = os.environ.get("YS_API_KEY")
13
- client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None
 
14
 
15
- MODEL_NAME = "llama-3.3-70b-versatile"
 
 
 
16
 
17
- SYSTEM_PROMPT = """
18
- You are a warm, non-judgmental mental wellness check-in companion.
19
- You are NOT a therapist, doctor, or emergency service.
 
20
 
21
- Your goals:
22
- - Help the user briefly name and understand how they feel.
23
- - Offer simple, low-risk wellness ideas (breathing exercises, journaling prompts, tiny self-care actions).
24
- - Encourage seeking professional help for serious or ongoing struggles.
25
 
26
- Behavior guidelines:
27
- - The user will provide a mood from 1 (very low) to 5 (great), plus some text.
28
- - If mood is 4–5: focus on reflection, gratitude, and reinforcing what’s going well.
29
- - If mood is 2–3: focus on stress relief, small coping steps, and self-kindness.
30
- - If mood is 1: be extra gentle; normalize struggle; suggest reaching out to someone they trust or a professional; offer one very small, doable exercise.
31
- - Always keep responses concise: 3–6 short sentences.
32
- - Do NOT diagnose or label mental disorders.
33
- - Do NOT present yourself as a therapist, clinician, or medical professional.
34
- - Do NOT give medical or crisis instructions beyond encouraging them to contact real services.
35
- - If the user mentions suicide, self-harm, or harming others, you must encourage them to contact local emergency services or crisis hotlines immediately.
36
 
37
- Respond in plain English text only, no markdown.
38
- """
 
39
 
40
- CRISIS_KEYWORDS = [
41
- "suicide",
42
- "kill myself",
43
- "end my life",
44
- "end it all",
45
- "hurt myself",
46
- "self-harm",
47
- "self harm",
48
- "harm myself",
49
- ]
50
-
51
- CRISIS_MESSAGE = (
52
- "I'm really glad you reached out and shared this.\n\n"
53
- "I’m not an emergency service or a professional, and I can’t keep you safe in a crisis. "
54
- "If you are in immediate danger or thinking about hurting yourself or someone else, "
55
- "please contact your local emergency number or a crisis hotline right now, or reach out "
56
- "to a trusted person near you (friend, family member, or professional)."
57
- )
58
-
59
-
60
- # -------------------------
61
- # LLM call via Groq
62
- # -------------------------
63
-
64
- def generate_bot_reply(user_message: str, mood_score: int) -> str:
65
- if client is None or not GROQ_API_KEY:
66
- return (
67
- "The language model is not configured yet. "
68
- "Please set the GROQ_API_KEY environment variable."
69
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- messages = [
72
- {"role": "system", "content": SYSTEM_PROMPT},
73
- {
74
- "role": "user",
75
- "content": f"Mood (1-5): {int(mood_score)}\nUser message: {user_message}",
76
- },
77
- ]
78
-
79
- try:
80
- completion = client.chat.completions.create(
81
- model=MODEL_NAME,
82
- messages=messages,
83
- temperature=0.7,
84
- max_tokens=300,
85
- top_p=0.9,
86
- )
87
- return completion.choices[0].message.content.strip()
88
- except Exception as e:
89
- print("Groq API error:", e)
90
- return "I'm having trouble responding right now. Please try again in a moment."
91
 
 
 
 
 
 
 
 
92
 
93
- # -------------------------
94
- # Plotting mood history
95
- # -------------------------
96
 
97
- def build_mood_plot(mood_history):
98
  """
99
- mood_history: list of dicts like {"date": "YYYY-MM-DD", "mood": int}
100
- Returns a matplotlib figure or None.
101
  """
102
- if not mood_history:
103
- return None
 
 
104
 
105
- dates = [entry["date"] for entry in mood_history]
106
- scores = [entry["mood"] for entry in mood_history]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
- fig, ax = plt.subplots()
109
- ax.plot(dates, scores, marker="o")
110
- ax.set_ylim(1, 5)
111
- ax.set_ylabel("Mood (1 = very low, 5 = great)")
112
- ax.set_xlabel("Date")
113
- ax.set_title("Mood over time (this session)")
114
- ax.grid(True)
115
- fig.autofmt_xdate(rotation=45)
 
 
 
 
116
 
117
- return fig
118
 
 
 
 
 
 
 
119
 
120
- # -------------------------
121
- # Chat logic
122
- # -------------------------
123
 
124
- def chat(user_message, mood_score, chat_history, mood_history):
125
- # Initialize states
126
- if chat_history is None:
127
- chat_history = []
128
- if mood_history is None:
129
- mood_history = []
130
 
131
- text = (user_message or "").strip()
132
- today = datetime.date.today().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- # Save today's mood for this session (simple per-send tracking)
135
- mood_history.append({"date": today, "mood": int(mood_score)})
 
 
 
 
 
 
 
 
 
 
136
 
137
- # Crisis keyword check
138
- msg_lower = text.lower()
139
- if any(kw in msg_lower for kw in CRISIS_KEYWORDS):
140
- bot_reply = CRISIS_MESSAGE
141
- elif text == "":
142
- bot_reply = "If you'd like, share a sentence or two about how you're feeling right now."
143
- else:
144
- bot_reply = generate_bot_reply(text, mood_score)
145
 
146
- # Append user message (if not empty) and assistant reply
147
- if text != "":
148
- chat_history.append({"role": "user", "content": text})
149
- chat_history.append({"role": "assistant", "content": bot_reply})
150
 
151
- # Update mood trend plot
152
- fig = build_mood_plot(mood_history)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- # Clear input box by returning empty string as final output
155
- return chat_history, chat_history, fig, mood_history, ""
 
 
 
 
 
 
 
 
 
 
 
156
 
157
 
158
- # -------------------------
159
  # Gradio UI
160
- # -------------------------
161
 
162
  with gr.Blocks() as demo:
163
- gr.Markdown("# 🧠 Mental Wellness Check-in Bot")
164
  gr.Markdown(
165
- "This is a simple mental wellness check-in companion. "
166
- "**It is not a therapist or medical service.**\n\n"
167
- "If you are in crisis or thinking about harming yourself or others, "
168
- "please contact your local emergency number or a crisis hotline immediately."
 
 
 
 
 
 
 
169
  )
170
 
171
- initial_bot_msg = (
172
- "Hi, I’m your wellness check-in buddy. "
173
- "Use the slider to rate your mood and share a bit about how you’re feeling to begin."
174
- )
 
175
 
176
- # IMPORTANT: use messages format: list of {"role": ..., "content": ...}
177
- chatbot = gr.Chatbot(
178
- value=[{"role": "assistant", "content": initial_bot_msg}],
179
- height=400,
180
- label="Conversation",
181
  )
182
 
183
- with gr.Row():
184
- mood_slider = gr.Slider(
185
- minimum=1,
186
- maximum=5,
187
- value=3,
188
- step=1,
189
- label="How are you feeling today? (1 = very low, 5 = great)",
190
- )
191
 
192
- user_input = gr.Textbox(
193
- label="What’s on your mind?",
194
- placeholder="Type a sentence or two about how you feel...",
195
- lines=3,
196
  )
197
- send_btn = gr.Button("Send")
198
 
199
- mood_plot = gr.Plot(label="Mood trend (this session)")
200
 
201
- # State for chat history and mood history
202
- chat_state = gr.State([{"role": "assistant", "content": initial_bot_msg}])
203
- mood_state = gr.State([])
 
204
 
205
- send_btn.click(
206
- fn=chat,
207
- inputs=[user_input, mood_slider, chat_state, mood_state],
208
- outputs=[chatbot, chat_state, mood_plot, mood_state, user_input],
209
  )
210
 
 
211
  if __name__ == "__main__":
212
- # For local / Colab testing
213
  demo.launch()
 
1
  import os
2
+ import tempfile
3
 
4
  import gradio as gr
5
+ import yt_dlp
6
+ import whisper
7
  from groq import Groq
8
 
 
 
 
9
 
10
+ # ----------------------------
11
+ # Global setup
12
+ # ----------------------------
13
 
14
+ # Whisper model (smaller = faster on CPU; "tiny" or "base" are good for Spaces)
15
+ WHISPER_MODEL_NAME = os.environ.get("WHISPER_MODEL_NAME", "tiny")
16
+ print(f"Loading Whisper model: {WHISPER_MODEL_NAME}")
17
+ whisper_model = whisper.load_model(WHISPER_MODEL_NAME)
18
 
19
+ # Groq client
20
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
21
+ if not GROQ_API_KEY:
22
+ raise RuntimeError("GROQ_API_KEY environment variable is not set.")
23
 
24
+ groq_client = Groq(api_key=GROQ_API_KEY)
 
 
 
25
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ # ----------------------------
28
+ # Helper functions
29
+ # ----------------------------
30
 
31
+ def download_audio_from_youtube(youtube_url: str) -> str:
32
+ """
33
+ Download audio from a YouTube URL using yt-dlp and return the local file path.
34
+ Demo only: use only on content you have rights to.
35
+ """
36
+ tmp_dir = tempfile.mkdtemp(prefix="yt_audio_")
37
+ output_template = os.path.join(tmp_dir, "%(id)s.%(ext)s")
38
+
39
+ ydl_opts = {
40
+ "format": "bestaudio/best",
41
+ "outtmpl": output_template,
42
+ "quiet": True,
43
+ "no_warnings": True,
44
+ "postprocessors": [
45
+ {
46
+ "key": "FFmpegExtractAudio",
47
+ "preferredcodec": "mp3",
48
+ "preferredquality": "128",
49
+ }
50
+ ],
51
+ }
52
+
53
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
54
+ info_dict = ydl.extract_info(youtube_url, download=True)
55
+ video_id = info_dict.get("id")
56
+ audio_path = os.path.join(tmp_dir, f"{video_id}.mp3")
57
+
58
+ if not os.path.exists(audio_path):
59
+ raise RuntimeError("Failed to download or convert audio from YouTube.")
60
+
61
+ return audio_path
62
+
63
+
64
+ def transcribe_audio(audio_path: str) -> str:
65
+ """
66
+ Transcribe the audio file using Whisper and return the transcript text.
67
+ """
68
+ print(f"Transcribing audio: {audio_path}")
69
+ result = whisper_model.transcribe(audio_path, language="en")
70
+ transcript = result.get("text", "").strip()
71
+ if not transcript:
72
+ raise RuntimeError("Transcription failed or produced empty text.")
73
+ return transcript
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ def truncate_transcript(transcript: str, max_chars: int = 12000) -> str:
77
+ """
78
+ Truncate long transcripts to avoid overly huge prompts.
79
+ """
80
+ if len(transcript) <= max_chars:
81
+ return transcript
82
+ return transcript[:max_chars]
83
 
 
 
 
84
 
85
+ def analyze_style_with_groq(transcript: str) -> str:
86
  """
87
+ Call Groq to analyze the speaking style in the transcript.
88
+ Returns a JSON-style string describing the style.
89
  """
90
+ transcript = truncate_transcript(transcript)
91
+
92
+ prompt = f"""
93
+ You are an expert writing coach analyzing speaking and writing style.
94
 
95
+ Analyze ONLY the style (not the content) of the speaker in the transcript below.
96
+ Return a concise JSON object with the following keys:
97
+
98
+ - tone: overall tone (e.g., friendly, formal, humorous)
99
+ - pacing: sentence length, rhythm, speed of ideas
100
+ - vocabulary: complexity, jargon level, typical word choices
101
+ - structure: how the talk is organized (e.g., hook, 3 points, recap)
102
+ - persona: how the speaker presents themselves (e.g., mentor, friend, expert)
103
+ - rhetorical_devices: recurring devices (e.g., questions, stories, analogies)
104
+ - quirks: noticeable stylistic quirks
105
+
106
+ Only output valid JSON. Do not include any explanation outside the JSON.
107
+
108
+ Transcript:
109
+ {transcript}
110
+ """
111
 
112
+ response = groq_client.chat.completions.create(
113
+ model="llama-3.3-70b-versatile",
114
+ messages=[
115
+ {
116
+ "role": "system",
117
+ "content": "You analyze and describe writing and speaking style."
118
+ },
119
+ {"role": "user", "content": prompt},
120
+ ],
121
+ temperature=0.2,
122
+ max_tokens=800,
123
+ )
124
 
125
+ style_json = response.choices[0].message.content.strip()
126
 
127
+ # Some models wrap JSON in ```json ... ```; strip that if present
128
+ if style_json.startswith("```"):
129
+ style_json = style_json.strip("`")
130
+ # After stripping backticks, there might be a "json" first line
131
+ if style_json.lower().startswith("json"):
132
+ style_json = style_json[4:].lstrip()
133
 
134
+ return style_json
 
 
135
 
 
 
 
 
 
 
136
 
137
+ def generate_script_with_groq(style_profile_json: str,
138
+ topic: str,
139
+ audience: str,
140
+ length_hint: str) -> str:
141
+ """
142
+ Call Groq to generate a brand-new script matching the given style profile.
143
+ """
144
+ prompt = f"""
145
+ You are a professional scriptwriter.
146
+
147
+ You are given a style profile as JSON and instructions for a new video script.
148
+ Your job is to write a COMPLETELY NEW script that matches the style,
149
+ but does NOT copy sentences or phrases from the original transcript.
150
+
151
+ STYLE PROFILE (JSON):
152
+ {style_profile_json}
153
+
154
+ INSTRUCTIONS:
155
+ - Topic: {topic}
156
+ - Target audience: {audience}
157
+ - Desired length: {length_hint} (approximate, in spoken minutes)
158
+ - Match the tone, pacing, structure, persona, and rhetorical devices implied by the style profile.
159
+ - Include:
160
+ - A strong hook/intro
161
+ - Clear body sections
162
+ - A closing that feels natural in this style (e.g., recap, call to action, reflection)
163
+ - Do NOT reference that this was generated by AI.
164
+ - Do NOT mention the original video or transcript.
165
+ - Do NOT include any JSON in your response.
166
+
167
+ Output only the final script text.
168
+ """
169
 
170
+ response = groq_client.chat.completions.create(
171
+ model="llama-3.3-70b-versatile",
172
+ messages=[
173
+ {
174
+ "role": "system",
175
+ "content": "You write engaging video scripts in a given style."
176
+ },
177
+ {"role": "user", "content": prompt},
178
+ ],
179
+ temperature=0.7,
180
+ max_tokens=2000,
181
+ )
182
 
183
+ script = response.choices[0].message.content.strip()
184
+ return script
 
 
 
 
 
 
185
 
 
 
 
 
186
 
187
+ def full_pipeline(youtube_url: str,
188
+ new_topic: str,
189
+ target_audience: str,
190
+ length_choice: str) -> str:
191
+ """
192
+ End-to-end pipeline:
193
+ - Download YouTube audio
194
+ - Transcribe with Whisper
195
+ - Analyze style with Groq
196
+ - Generate new script with Groq
197
+ """
198
+ if not youtube_url.strip():
199
+ raise gr.Error("Please enter a YouTube URL.")
200
+ if not new_topic.strip():
201
+ raise gr.Error("Please enter a new topic.")
202
+
203
+ length_map = {
204
+ "Short (~3–5 min)": "about 3 to 5 minutes",
205
+ "Medium (~8–10 min)": "about 8 to 10 minutes",
206
+ "Long (~15+ min)": "about 15 minutes or more",
207
+ }
208
+ length_hint = length_map.get(length_choice, "about 8 to 10 minutes")
209
 
210
+ try:
211
+ audio_path = download_audio_from_youtube(youtube_url)
212
+ transcript = transcribe_audio(audio_path)
213
+ style_profile_json = analyze_style_with_groq(transcript)
214
+ new_script = generate_script_with_groq(
215
+ style_profile_json,
216
+ topic=new_topic,
217
+ audience=target_audience or "general audience",
218
+ length_hint=length_hint,
219
+ )
220
+ return new_script
221
+ except Exception as e:
222
+ raise gr.Error(f"Error in pipeline: {e}")
223
 
224
 
225
+ # ----------------------------
226
  # Gradio UI
227
+ # ----------------------------
228
 
229
  with gr.Blocks() as demo:
 
230
  gr.Markdown(
231
+ """
232
+ # YouTube Style New Script Generator
233
+
234
+ 1. Paste a YouTube URL (use content you own or have rights to).
235
+ 2. Enter a new topic and audience.
236
+ 3. The app:
237
+ - extracts audio
238
+ - transcribes with Whisper
239
+ - analyzes style with Groq
240
+ - writes a brand-new script in that style on your topic.
241
+ """
242
  )
243
 
244
+ with gr.Row():
245
+ youtube_url = gr.Textbox(
246
+ label="YouTube URL",
247
+ placeholder="https://www.youtube.com/watch?v=...",
248
+ )
249
 
250
+ new_topic = gr.Textbox(
251
+ label="New Topic",
252
+ placeholder="e.g., How to stay productive while working from home",
 
 
253
  )
254
 
255
+ target_audience = gr.Textbox(
256
+ label="Target Audience (optional)",
257
+ placeholder="e.g., beginners, developers, students, content creators",
258
+ )
 
 
 
 
259
 
260
+ length_choice = gr.Radio(
261
+ label="Desired Script Length",
262
+ choices=["Short (~3–5 min)", "Medium (~8–10 min)", "Long (~15+ min)"],
263
+ value="Medium (~8–10 min)",
264
  )
 
265
 
266
+ generate_button = gr.Button("Generate New Script")
267
 
268
+ output_script = gr.Textbox(
269
+ label="Generated Script",
270
+ lines=25,
271
+ )
272
 
273
+ generate_button.click(
274
+ fn=full_pipeline,
275
+ inputs=[youtube_url, new_topic, target_audience, length_choice],
276
+ outputs=output_script,
277
  )
278
 
279
+ # For Hugging Face Spaces, this is fine; they run `python app.py`
280
  if __name__ == "__main__":
 
281
  demo.launch()