gcharanteja commited on
Commit
739c42a
·
1 Parent(s): e136d4a
Files changed (1) hide show
  1. app.py +551 -89
app.py CHANGED
@@ -1,141 +1,426 @@
 
 
 
 
1
  import gradio as gr
2
  import httpx
3
 
4
- BASE_URL = "http://0.0.0.0:7860"
 
 
 
5
 
6
 
7
  def create_gradio_app():
8
  """Create and return the Gradio Blocks app for Music Memories."""
9
 
10
- def add_song_fn(title, artist, album, duration, bpm, energy_level, lyrics):
 
 
11
  try:
12
- with httpx.Client() as client:
13
- params = {"title": title, "artist": artist}
14
- if album: params["album"] = album
15
- if duration: params["duration"] = int(duration)
16
- if bpm: params["bpm"] = int(bpm)
17
- if energy_level: params["energy_level"] = int(energy_level)
18
- if lyrics: params["lyrics"] = lyrics
19
- resp = client.post(f"{BASE_URL}/songs", params=params, timeout=10.0)
20
- resp.raise_for_status()
21
- return "Success!", str(resp.json())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  except Exception as e:
23
- return "Error", str(e)
24
 
25
  def add_user_fn(name):
26
  try:
27
- with httpx.Client() as client:
28
- resp = client.post(f"{BASE_URL}/users", params={"name": name}, timeout=5.0)
29
- resp.raise_for_status()
30
- return "Success!", str(resp.json())
31
  except Exception as e:
32
- return "Error", str(e)
33
 
34
  def add_memory_fn(user_id, description, date, song_id):
35
  try:
36
- with httpx.Client() as client:
37
- params = {"user_id": int(user_id), "description": description}
38
- if date: params["date"] = date
39
- if song_id: params["song_id"] = int(song_id)
40
- resp = client.post(f"{BASE_URL}/memories", params=params, timeout=10.0)
41
- resp.raise_for_status()
42
- return "Success!", str(resp.json())
 
 
 
43
  except Exception as e:
44
- return "Error", str(e)
45
 
46
  def add_playlist_fn(name, vibe_code, mood_description):
47
  try:
48
- with httpx.Client() as client:
49
- params = {"name": name}
50
- if vibe_code: params["vibe_code"] = vibe_code
51
- if mood_description: params["mood_description"] = mood_description
52
- resp = client.post(f"{BASE_URL}/playlists", params=params, timeout=10.0)
53
- resp.raise_for_status()
54
- return "Success!", str(resp.json())
55
  except Exception as e:
56
- return "Error", str(e)
57
 
58
  def add_context_fn(weather, time_of_day, location_type):
59
  try:
60
- with httpx.Client() as client:
61
- params = {}
62
- if weather: params["weather"] = weather
63
- if time_of_day: params["time_of_day"] = time_of_day
64
- if location_type: params["location_type"] = location_type
65
- resp = client.post(f"{BASE_URL}/contexts", params=params, timeout=5.0)
66
- resp.raise_for_status()
67
- return "Success!", str(resp.json())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  except Exception as e:
69
  return "Error", str(e)
70
 
71
- def search_songs_fn(query, n_results):
72
  try:
73
- with httpx.Client() as client:
74
- resp = client.get(f"{BASE_URL}/search/songs", params={"q": query, "n": n_results}, timeout=10.0)
 
 
 
 
75
  resp.raise_for_status()
76
- data = resp.json()
77
- results = data.get("results", [])
78
- table = [[r["id"], r["title"], r["artist"], f"{r['distance']:.4f}"] for r in results] if results else []
79
- return f"Found {len(results)} songs", table
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  except Exception as e:
81
  return "Error", []
82
 
83
  def search_memories_fn(query, n_results):
84
  try:
85
- with httpx.Client() as client:
86
- resp = client.get(f"{BASE_URL}/search/memories", params={"q": query, "n": n_results}, timeout=10.0)
87
- resp.raise_for_status()
88
- data = resp.json()
89
- results = data.get("results", [])
90
- table = [[r["id"], r["user_id"], r["document"][:50], f"{r['distance']:.4f}"] for r in results] if results else []
91
- return f"Found {len(results)} memories", table
92
  except Exception as e:
93
  return "Error", []
94
 
 
 
 
 
 
 
 
 
 
95
  def search_playlists_fn(query, n_results):
96
  try:
97
- with httpx.Client() as client:
98
- resp = client.get(f"{BASE_URL}/search/playlists", params={"q": query, "n": n_results}, timeout=10.0)
99
- resp.raise_for_status()
100
- data = resp.json()
101
- results = data.get("results", [])
102
- table = [[r["id"], r["name"], f"{r['distance']:.4f}"] for r in results] if results else []
103
- return f"Found {len(results)} playlists", table
104
  except Exception as e:
105
  return "Error", []
106
 
107
  def list_songs_fn():
108
  try:
109
- with httpx.Client() as client:
110
- resp = client.get(f"{BASE_URL}/songs", timeout=5.0)
111
- resp.raise_for_status()
112
- data = resp.json()
113
- songs = data.get("songs", [])
114
- return [[s["id"], s["title"], s["artist"], s.get("album",""), s.get("bpm","")] for s in songs]
115
  except Exception as e:
116
- return [["Error", str(e), "", "", ""]]
117
 
118
  def list_users_fn():
119
  try:
120
- with httpx.Client() as client:
121
- resp = client.get(f"{BASE_URL}/users", timeout=5.0)
122
- resp.raise_for_status()
123
- data = resp.json()
124
- users = data.get("users", [])
125
- return [[u["id"], u["name"], u.get("created_at","")] for u in users]
126
  except Exception as e:
127
  return [["Error", str(e), ""]]
128
 
129
  def list_memories_fn():
130
  try:
131
- with httpx.Client() as client:
132
- resp = client.get(f"{BASE_URL}/memories", timeout=5.0)
133
- resp.raise_for_status()
134
- data = resp.json()
135
- memories = data.get("memories", [])
136
- return [[m["id"], m["user_id"], m["description"][:50], m.get("date","")] for m in memories]
137
  except Exception as e:
138
- return [["Error", str(e), "", ""]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  with gr.Blocks(title="Music Memories UI") as demo:
141
  gr.Markdown("# 🎵 Music Memories")
@@ -151,14 +436,52 @@ def create_gradio_app():
151
  song_bpm = gr.Number(label="BPM", precision=0)
152
  song_energy = gr.Slider(1, 10, value=5, label="Energy Level")
153
  song_lyrics = gr.Textbox(label="Lyrics", placeholder="Lyrics for semantic search", lines=3)
 
154
  song_add_btn = gr.Button("Add Song")
155
  song_add_status = gr.Textbox(label="Status")
156
  song_add_output = gr.JSON(label="Response")
157
- song_add_btn.click(fn=add_song_fn, inputs=[song_title, song_artist, song_album, song_duration, song_bpm, song_energy, song_lyrics], outputs=[song_add_status, song_add_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  with gr.Group():
160
  gr.Markdown("### All Songs")
161
- songs_table = gr.Dataframe(headers=["ID", "Title", "Artist", "Album", "BPM"], label="Songs")
162
  songs_load_btn = gr.Button("Load Songs")
163
  songs_load_btn.click(fn=list_songs_fn, outputs=songs_table)
164
 
@@ -170,6 +493,32 @@ def create_gradio_app():
170
  user_add_status = gr.Textbox(label="Status")
171
  user_add_output = gr.JSON(label="Response")
172
  user_add_btn.click(fn=add_user_fn, inputs=user_name, outputs=[user_add_status, user_add_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  with gr.Group():
175
  gr.Markdown("### All Users")
@@ -188,10 +537,18 @@ def create_gradio_app():
188
  mem_add_status = gr.Textbox(label="Status")
189
  mem_add_output = gr.JSON(label="Response")
190
  mem_add_btn.click(fn=add_memory_fn, inputs=[mem_user_id, mem_desc, mem_date, mem_song_id], outputs=[mem_add_status, mem_add_output])
 
 
 
 
 
 
 
 
191
 
192
  with gr.Group():
193
  gr.Markdown("### All Memories")
194
- memories_table = gr.Dataframe(headers=["ID", "User ID", "Description", "Date"], label="Memories")
195
  memories_load_btn = gr.Button("Load Memories")
196
  memories_load_btn.click(fn=list_memories_fn, outputs=memories_table)
197
 
@@ -206,6 +563,20 @@ def create_gradio_app():
206
  pl_add_output = gr.JSON(label="Response")
207
  pl_add_btn.click(fn=add_playlist_fn, inputs=[pl_name, pl_vibe, pl_mood], outputs=[pl_add_status, pl_add_output])
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  with gr.Tab("🌤️ Contexts"):
210
  with gr.Group():
211
  gr.Markdown("### Add Context")
@@ -217,6 +588,20 @@ def create_gradio_app():
217
  ctx_add_output = gr.JSON(label="Response")
218
  ctx_add_btn.click(fn=add_context_fn, inputs=[ctx_weather, ctx_time, ctx_location], outputs=[ctx_add_status, ctx_add_output])
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  with gr.Tab("🔍 Semantic Search"):
221
  with gr.Group():
222
  gr.Markdown("### Search Songs by Vibe")
@@ -235,6 +620,15 @@ def create_gradio_app():
235
  search_mem_summary = gr.Textbox(label="Results")
236
  search_mem_table = gr.Dataframe(headers=["ID", "User ID", "Description", "Distance"], label="Memories")
237
  search_mem_btn.click(fn=search_memories_fn, inputs=[search_mem_query, search_mem_n], outputs=[search_mem_summary, search_mem_table])
 
 
 
 
 
 
 
 
 
238
 
239
  with gr.Group():
240
  gr.Markdown("### Search Playlists by Mood")
@@ -246,9 +640,77 @@ def create_gradio_app():
246
  search_pl_btn.click(fn=search_playlists_fn, inputs=[search_pl_query, search_pl_n], outputs=[search_pl_summary, search_pl_table])
247
 
248
  with gr.Tab("ℹ️ Health"):
249
- health_output = gr.JSON(label="Health Status")
250
- health_btn = gr.Button("Check Health")
251
- health_btn.click(fn=lambda: {"status": "healthy", "app": "Music Memories"}, outputs=health_output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
  return demo
254
 
 
1
+ import os
2
+ import tempfile
3
+ from typing import Any
4
+
5
  import gradio as gr
6
  import httpx
7
 
8
+
9
+ # NOTE: 0.0.0.0 is a bind address, not a connect address.
10
+ # Use 127.0.0.1 by default and allow overrides for deployments.
11
+ BASE_URL = os.getenv("API_BASE_URL", "http://127.0.0.1:7860")
12
 
13
 
14
  def create_gradio_app():
15
  """Create and return the Gradio Blocks app for Music Memories."""
16
 
17
+ def _maybe_int(value: Any) -> int | None:
18
+ if value is None:
19
+ return None
20
  try:
21
+ return int(value)
22
+ except Exception:
23
+ return None
24
+
25
+ def _json_request(
26
+ method: str,
27
+ path: str,
28
+ *,
29
+ params: dict[str, Any] | None = None,
30
+ data: dict[str, Any] | None = None,
31
+ files: dict[str, Any] | None = None,
32
+ follow_redirects: bool = True,
33
+ timeout: float = 15.0,
34
+ ) -> dict[str, Any]:
35
+ url = f"{BASE_URL}{path}"
36
+ with httpx.Client(follow_redirects=follow_redirects, timeout=timeout) as client:
37
+ resp = client.request(method, url, params=params, data=data, files=files)
38
+ resp.raise_for_status()
39
+ return resp.json()
40
+
41
+ def _redirect_location(path: str, *, params: dict[str, Any] | None = None) -> str:
42
+ url = f"{BASE_URL}{path}"
43
+ with httpx.Client(follow_redirects=False, timeout=15.0) as client:
44
+ resp = client.get(url, params=params)
45
+ if resp.status_code in (301, 302, 303, 307, 308):
46
+ return resp.headers.get("location") or ""
47
+ resp.raise_for_status()
48
+ return ""
49
+
50
+ def add_song_fn(title, artist, album, duration, bpm, energy_level, lyrics, audio_path):
51
+ try:
52
+ # /songs expects multipart/form-data (Form fields + optional file).
53
+ data: dict[str, Any] = {"title": title, "artist": artist}
54
+ if album:
55
+ data["album"] = album
56
+ duration_i = _maybe_int(duration)
57
+ if duration_i is not None:
58
+ data["duration"] = str(duration_i)
59
+ bpm_i = _maybe_int(bpm)
60
+ if bpm_i is not None:
61
+ data["bpm"] = str(bpm_i)
62
+ energy_i = _maybe_int(energy_level)
63
+ if energy_i is not None:
64
+ data["energy_level"] = str(energy_i)
65
+ if lyrics:
66
+ data["lyrics"] = lyrics
67
+
68
+ if audio_path:
69
+ filename = os.path.basename(str(audio_path))
70
+ with open(str(audio_path), "rb") as f:
71
+ files = {"audio_file": (filename, f, "audio/mpeg")}
72
+ out = _json_request("POST", "/songs", data=data, files=files)
73
+ else:
74
+ out = _json_request("POST", "/songs", data=data)
75
+
76
+ return "Success!", out
77
  except Exception as e:
78
+ return "Error", {"error": str(e)}
79
 
80
  def add_user_fn(name):
81
  try:
82
+ out = _json_request("POST", "/users", params={"name": name}, timeout=10.0)
83
+ return "Success!", out
 
 
84
  except Exception as e:
85
+ return "Error", {"error": str(e)}
86
 
87
  def add_memory_fn(user_id, description, date, song_id):
88
  try:
89
+ params: dict[str, Any] = {"user_id": _maybe_int(user_id), "description": description}
90
+ if params["user_id"] is None:
91
+ raise ValueError("user_id is required")
92
+ if date:
93
+ params["date"] = date
94
+ song_id_i = _maybe_int(song_id)
95
+ if song_id_i is not None:
96
+ params["song_id"] = song_id_i
97
+ out = _json_request("POST", "/memories", params=params)
98
+ return "Success!", out
99
  except Exception as e:
100
+ return "Error", {"error": str(e)}
101
 
102
  def add_playlist_fn(name, vibe_code, mood_description):
103
  try:
104
+ params: dict[str, Any] = {"name": name}
105
+ if vibe_code:
106
+ params["vibe_code"] = vibe_code
107
+ if mood_description:
108
+ params["mood_description"] = mood_description
109
+ out = _json_request("POST", "/playlists", params=params)
110
+ return "Success!", out
111
  except Exception as e:
112
+ return "Error", {"error": str(e)}
113
 
114
  def add_context_fn(weather, time_of_day, location_type):
115
  try:
116
+ params: dict[str, Any] = {}
117
+ if weather:
118
+ params["weather"] = weather
119
+ if time_of_day:
120
+ params["time_of_day"] = time_of_day
121
+ if location_type:
122
+ params["location_type"] = location_type
123
+ out = _json_request("POST", "/contexts", params=params, timeout=10.0)
124
+ return "Success!", out
125
+ except Exception as e:
126
+ return "Error", {"error": str(e)}
127
+
128
+ def get_song_fn(song_id):
129
+ try:
130
+ song_id_i = _maybe_int(song_id)
131
+ if song_id_i is None:
132
+ raise ValueError("song_id is required")
133
+ out = _json_request("GET", f"/songs/{song_id_i}")
134
+ return "Success!", out
135
+ except Exception as e:
136
+ return "Error", {"error": str(e)}
137
+
138
+ def delete_song_fn(song_id):
139
+ try:
140
+ song_id_i = _maybe_int(song_id)
141
+ if song_id_i is None:
142
+ raise ValueError("song_id is required")
143
+ out = _json_request("DELETE", f"/songs/{song_id_i}")
144
+ return "Success!", out
145
+ except Exception as e:
146
+ return "Error", {"error": str(e)}
147
+
148
+ def upload_song_audio_fn(song_id, audio_path):
149
+ try:
150
+ song_id_i = _maybe_int(song_id)
151
+ if song_id_i is None:
152
+ raise ValueError("song_id is required")
153
+ if not audio_path:
154
+ raise ValueError("audio file is required")
155
+ filename = os.path.basename(str(audio_path))
156
+ with open(str(audio_path), "rb") as f:
157
+ files = {"audio_file": (filename, f, "audio/mpeg")}
158
+ out = _json_request("POST", f"/songs/{song_id_i}/upload-audio", files=files)
159
+ return "Success!", out
160
+ except Exception as e:
161
+ return "Error", {"error": str(e)}
162
+
163
+ def song_stream_url_fn(song_id):
164
+ try:
165
+ song_id_i = _maybe_int(song_id)
166
+ if song_id_i is None:
167
+ raise ValueError("song_id is required")
168
+ location = _redirect_location(f"/songs/{song_id_i}/stream")
169
+ if not location:
170
+ return "No redirect returned", ""
171
+ return "Success!", location
172
  except Exception as e:
173
  return "Error", str(e)
174
 
175
+ def song_download_fn(song_id):
176
  try:
177
+ song_id_i = _maybe_int(song_id)
178
+ if song_id_i is None:
179
+ raise ValueError("song_id is required")
180
+ url = f"{BASE_URL}/songs/{song_id_i}/download"
181
+ with httpx.Client(timeout=30.0) as client:
182
+ resp = client.get(url)
183
  resp.raise_for_status()
184
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
185
+ tmp.write(resp.content)
186
+ tmp.flush()
187
+ tmp.close()
188
+ return "Success!", tmp.name
189
+ except Exception as e:
190
+ return "Error", None
191
+
192
+ def get_user_fn(user_id):
193
+ try:
194
+ user_id_i = _maybe_int(user_id)
195
+ if user_id_i is None:
196
+ raise ValueError("user_id is required")
197
+ out = _json_request("GET", f"/users/{user_id_i}")
198
+ return "Success!", out
199
+ except Exception as e:
200
+ return "Error", {"error": str(e)}
201
+
202
+ def list_user_memories_fn(user_id):
203
+ try:
204
+ user_id_i = _maybe_int(user_id)
205
+ if user_id_i is None:
206
+ raise ValueError("user_id is required")
207
+ data = _json_request("GET", f"/users/{user_id_i}/memories", timeout=10.0)
208
+ memories = data.get("memories", [])
209
+ table = [[m.get("id"), m.get("user_id"), (m.get("description") or "")[:50], m.get("date", "")] for m in memories]
210
+ return f"Found {len(memories)} memories", table
211
+ except Exception as e:
212
+ return "Error", []
213
+
214
+ def user_activity_fn(user_id, limit, action_type):
215
+ try:
216
+ user_id_i = _maybe_int(user_id)
217
+ if user_id_i is None:
218
+ raise ValueError("user_id is required")
219
+ params: dict[str, Any] = {}
220
+ limit_i = _maybe_int(limit)
221
+ if limit_i is not None:
222
+ params["limit"] = limit_i
223
+ if action_type:
224
+ params["action_type"] = action_type
225
+ out = _json_request("GET", f"/users/{user_id_i}/activity", params=params)
226
+ return "Success!", out
227
+ except Exception as e:
228
+ return "Error", {"error": str(e)}
229
+
230
+ def delete_playlist_fn(playlist_id):
231
+ try:
232
+ playlist_id_i = _maybe_int(playlist_id)
233
+ if playlist_id_i is None:
234
+ raise ValueError("playlist_id is required")
235
+ out = _json_request("DELETE", f"/playlists/{playlist_id_i}")
236
+ return "Success!", out
237
+ except Exception as e:
238
+ return "Error", {"error": str(e)}
239
+
240
+ def list_playlists_fn():
241
+ try:
242
+ data = _json_request("GET", "/playlists", timeout=10.0)
243
+ playlists = data.get("playlists", [])
244
+ return [[p.get("id"), p.get("name"), p.get("vibe_code", "")] for p in playlists]
245
+ except Exception as e:
246
+ return [["Error", str(e), ""]]
247
+
248
+ def delete_memory_fn(memory_id):
249
+ try:
250
+ memory_id_i = _maybe_int(memory_id)
251
+ if memory_id_i is None:
252
+ raise ValueError("memory_id is required")
253
+ out = _json_request("DELETE", f"/memories/{memory_id_i}")
254
+ return "Success!", out
255
+ except Exception as e:
256
+ return "Error", {"error": str(e)}
257
+
258
+ def list_contexts_fn():
259
+ try:
260
+ data = _json_request("GET", "/contexts", timeout=10.0)
261
+ contexts = data.get("contexts", [])
262
+ return [[c.get("id"), c.get("weather", ""), c.get("time_of_day", ""), c.get("location_type", "")] for c in contexts]
263
+ except Exception as e:
264
+ return [["Error", str(e), "", ""]]
265
+
266
+ def delete_context_fn(context_id):
267
+ try:
268
+ context_id_i = _maybe_int(context_id)
269
+ if context_id_i is None:
270
+ raise ValueError("context_id is required")
271
+ out = _json_request("DELETE", f"/contexts/{context_id_i}")
272
+ return "Success!", out
273
+ except Exception as e:
274
+ return "Error", {"error": str(e)}
275
+
276
+ def search_songs_fn(query, n_results):
277
+ try:
278
+ data = _json_request("GET", "/search/songs", params={"q": query, "n": n_results}, timeout=15.0)
279
+ results = data.get("results", [])
280
+ table = [[r.get("id"), r.get("title"), r.get("artist"), f"{float(r.get('distance', 0.0)):.4f}"] for r in results] if results else []
281
+ return f"Found {len(results)} songs", table
282
  except Exception as e:
283
  return "Error", []
284
 
285
  def search_memories_fn(query, n_results):
286
  try:
287
+ data = _json_request("GET", "/search/memories", params={"q": query, "n": n_results}, timeout=15.0)
288
+ results = data.get("results", [])
289
+ table = [[r.get("id"), r.get("user_id"), (r.get("document") or "")[:50], f"{float(r.get('distance', 0.0)):.4f}"] for r in results] if results else []
290
+ return f"Found {len(results)} memories", table
 
 
 
291
  except Exception as e:
292
  return "Error", []
293
 
294
+ def search_contexts_fn(query, n_results):
295
+ try:
296
+ data = _json_request("GET", "/search/contexts", params={"q": query, "n": n_results}, timeout=15.0)
297
+ results = data.get("results", [])
298
+ table = [[r.get("id"), r.get("weather"), r.get("time_of_day"), r.get("location_type"), f"{float(r.get('distance', 0.0)):.4f}"] for r in results] if results else []
299
+ return f"Found {len(results)} contexts", table
300
+ except Exception:
301
+ return "Error", []
302
+
303
  def search_playlists_fn(query, n_results):
304
  try:
305
+ data = _json_request("GET", "/search/playlists", params={"q": query, "n": n_results}, timeout=15.0)
306
+ results = data.get("results", [])
307
+ table = [[r.get("id"), r.get("name"), f"{float(r.get('distance', 0.0)):.4f}"] for r in results] if results else []
308
+ return f"Found {len(results)} playlists", table
 
 
 
309
  except Exception as e:
310
  return "Error", []
311
 
312
  def list_songs_fn():
313
  try:
314
+ data = _json_request("GET", "/songs", timeout=10.0)
315
+ songs = data.get("songs", [])
316
+ return [[s.get("id"), s.get("title"), s.get("artist"), s.get("album", ""), s.get("bpm", ""), s.get("has_audio"), s.get("play_count")] for s in songs]
 
 
 
317
  except Exception as e:
318
+ return [["Error", str(e), "", "", "", "", ""]]
319
 
320
  def list_users_fn():
321
  try:
322
+ data = _json_request("GET", "/users", timeout=10.0)
323
+ users = data.get("users", [])
324
+ return [[u.get("id"), u.get("name"), u.get("created_at", "")] for u in users]
 
 
 
325
  except Exception as e:
326
  return [["Error", str(e), ""]]
327
 
328
  def list_memories_fn():
329
  try:
330
+ data = _json_request("GET", "/memories", timeout=10.0)
331
+ memories = data.get("memories", [])
332
+ return [[m.get("id"), m.get("user_id"), (m.get("description") or "")[:50], m.get("date", ""), m.get("song_id", "")] for m in memories]
 
 
 
333
  except Exception as e:
334
+ return [["Error", str(e), "", "", ""]]
335
+
336
+ def list_history_fn(user_id, limit):
337
+ try:
338
+ params: dict[str, Any] = {}
339
+ user_id_i = _maybe_int(user_id)
340
+ if user_id_i is not None:
341
+ params["user_id"] = user_id_i
342
+ limit_i = _maybe_int(limit)
343
+ if limit_i is not None:
344
+ params["limit"] = limit_i
345
+ out = _json_request("GET", "/history", params=params, timeout=20.0)
346
+ return "Success!", out
347
+ except Exception as e:
348
+ return "Error", {"error": str(e)}
349
+
350
+ def add_history_fn(user_id, song_id, context_id, duration_seconds):
351
+ try:
352
+ params: dict[str, Any] = {}
353
+ user_id_i = _maybe_int(user_id)
354
+ song_id_i = _maybe_int(song_id)
355
+ if user_id_i is None or song_id_i is None:
356
+ raise ValueError("user_id and song_id are required")
357
+ params["user_id"] = user_id_i
358
+ params["song_id"] = song_id_i
359
+ context_id_i = _maybe_int(context_id)
360
+ if context_id_i is not None:
361
+ params["context_id"] = context_id_i
362
+ dur_i = _maybe_int(duration_seconds)
363
+ if dur_i is not None:
364
+ params["duration_seconds"] = dur_i
365
+ out = _json_request("POST", "/history", params=params, timeout=20.0)
366
+ return "Success!", out
367
+ except Exception as e:
368
+ return "Error", {"error": str(e)}
369
+
370
+ def analytics_summary_fn(hours):
371
+ try:
372
+ hours_i = _maybe_int(hours)
373
+ params = {"hours": hours_i} if hours_i is not None else {}
374
+ out = _json_request("GET", "/analytics/summary", params=params, timeout=20.0)
375
+ return "Success!", out
376
+ except Exception as e:
377
+ return "Error", {"error": str(e)}
378
+
379
+ def analytics_top_songs_fn(limit):
380
+ try:
381
+ limit_i = _maybe_int(limit)
382
+ params = {"limit": limit_i} if limit_i is not None else {}
383
+ out = _json_request("GET", "/analytics/top-songs", params=params, timeout=20.0)
384
+ return "Success!", out
385
+ except Exception as e:
386
+ return "Error", {"error": str(e)}
387
+
388
+ def analytics_popular_searches_fn(hours, limit):
389
+ try:
390
+ params: dict[str, Any] = {}
391
+ hours_i = _maybe_int(hours)
392
+ limit_i = _maybe_int(limit)
393
+ if hours_i is not None:
394
+ params["hours"] = hours_i
395
+ if limit_i is not None:
396
+ params["limit"] = limit_i
397
+ out = _json_request("GET", "/analytics/popular-searches", params=params, timeout=20.0)
398
+ return "Success!", out
399
+ except Exception as e:
400
+ return "Error", {"error": str(e)}
401
+
402
+ def storage_files_fn():
403
+ try:
404
+ out = _json_request("GET", "/storage/files", timeout=20.0)
405
+ files = out.get("files", [])
406
+ table = [[f.get("object_name"), f.get("size"), f.get("last_modified")] for f in files]
407
+ return f"Found {len(files)} files", table
408
+ except Exception as e:
409
+ return "Error", []
410
+
411
+ def health_fn():
412
+ try:
413
+ out = _json_request("GET", "/health", timeout=10.0)
414
+ return "Success!", out
415
+ except Exception as e:
416
+ return "Error", {"error": str(e)}
417
+
418
+ def root_fn():
419
+ try:
420
+ out = _json_request("GET", "/", timeout=10.0)
421
+ return "Success!", out
422
+ except Exception as e:
423
+ return "Error", {"error": str(e)}
424
 
425
  with gr.Blocks(title="Music Memories UI") as demo:
426
  gr.Markdown("# 🎵 Music Memories")
 
436
  song_bpm = gr.Number(label="BPM", precision=0)
437
  song_energy = gr.Slider(1, 10, value=5, label="Energy Level")
438
  song_lyrics = gr.Textbox(label="Lyrics", placeholder="Lyrics for semantic search", lines=3)
439
+ song_audio = gr.File(label="Audio File (MP3, optional)", file_types=[".mp3"], type="filepath")
440
  song_add_btn = gr.Button("Add Song")
441
  song_add_status = gr.Textbox(label="Status")
442
  song_add_output = gr.JSON(label="Response")
443
+ song_add_btn.click(
444
+ fn=add_song_fn,
445
+ inputs=[song_title, song_artist, song_album, song_duration, song_bpm, song_energy, song_lyrics, song_audio],
446
+ outputs=[song_add_status, song_add_output],
447
+ )
448
+
449
+ with gr.Group():
450
+ gr.Markdown("### Get / Delete Song")
451
+ song_id = gr.Number(label="Song ID", precision=0)
452
+ song_get_btn = gr.Button("Get Song")
453
+ song_del_btn = gr.Button("Delete Song")
454
+ song_action_status = gr.Textbox(label="Status")
455
+ song_action_output = gr.JSON(label="Response")
456
+ song_get_btn.click(fn=get_song_fn, inputs=[song_id], outputs=[song_action_status, song_action_output])
457
+ song_del_btn.click(fn=delete_song_fn, inputs=[song_id], outputs=[song_action_status, song_action_output])
458
+
459
+ with gr.Group():
460
+ gr.Markdown("### Audio: Upload / Stream / Download")
461
+ audio_song_id = gr.Number(label="Song ID", precision=0)
462
+ audio_upload = gr.File(label="MP3 File", file_types=[".mp3"], type="filepath")
463
+ audio_upload_btn = gr.Button("Upload/Update Audio")
464
+ audio_upload_status = gr.Textbox(label="Status")
465
+ audio_upload_output = gr.JSON(label="Response")
466
+ audio_upload_btn.click(
467
+ fn=upload_song_audio_fn,
468
+ inputs=[audio_song_id, audio_upload],
469
+ outputs=[audio_upload_status, audio_upload_output],
470
+ )
471
+
472
+ stream_btn = gr.Button("Get Stream URL")
473
+ stream_status = gr.Textbox(label="Status")
474
+ stream_url = gr.Textbox(label="Presigned Stream URL")
475
+ stream_btn.click(fn=song_stream_url_fn, inputs=[audio_song_id], outputs=[stream_status, stream_url])
476
+
477
+ download_btn = gr.Button("Download MP3 via API")
478
+ download_status = gr.Textbox(label="Status")
479
+ download_file = gr.File(label="Downloaded File")
480
+ download_btn.click(fn=song_download_fn, inputs=[audio_song_id], outputs=[download_status, download_file])
481
 
482
  with gr.Group():
483
  gr.Markdown("### All Songs")
484
+ songs_table = gr.Dataframe(headers=["ID", "Title", "Artist", "Album", "BPM", "Has Audio", "Play Count"], label="Songs")
485
  songs_load_btn = gr.Button("Load Songs")
486
  songs_load_btn.click(fn=list_songs_fn, outputs=songs_table)
487
 
 
493
  user_add_status = gr.Textbox(label="Status")
494
  user_add_output = gr.JSON(label="Response")
495
  user_add_btn.click(fn=add_user_fn, inputs=user_name, outputs=[user_add_status, user_add_output])
496
+
497
+ with gr.Group():
498
+ gr.Markdown("### Get User")
499
+ get_user_id = gr.Number(label="User ID", precision=0)
500
+ get_user_btn = gr.Button("Get User")
501
+ get_user_status = gr.Textbox(label="Status")
502
+ get_user_output = gr.JSON(label="Response")
503
+ get_user_btn.click(fn=get_user_fn, inputs=[get_user_id], outputs=[get_user_status, get_user_output])
504
+
505
+ with gr.Group():
506
+ gr.Markdown("### User Memories")
507
+ um_user_id = gr.Number(label="User ID", precision=0)
508
+ um_btn = gr.Button("Load User Memories")
509
+ um_summary = gr.Textbox(label="Results")
510
+ um_table = gr.Dataframe(headers=["ID", "User ID", "Description", "Date"], label="Memories")
511
+ um_btn.click(fn=list_user_memories_fn, inputs=[um_user_id], outputs=[um_summary, um_table])
512
+
513
+ with gr.Group():
514
+ gr.Markdown("### User Activity")
515
+ ua_user_id = gr.Number(label="User ID", precision=0)
516
+ ua_limit = gr.Number(label="Limit", value=50, precision=0)
517
+ ua_action_type = gr.Textbox(label="Action Type (optional)")
518
+ ua_btn = gr.Button("Load Activity")
519
+ ua_status = gr.Textbox(label="Status")
520
+ ua_output = gr.JSON(label="Response")
521
+ ua_btn.click(fn=user_activity_fn, inputs=[ua_user_id, ua_limit, ua_action_type], outputs=[ua_status, ua_output])
522
 
523
  with gr.Group():
524
  gr.Markdown("### All Users")
 
537
  mem_add_status = gr.Textbox(label="Status")
538
  mem_add_output = gr.JSON(label="Response")
539
  mem_add_btn.click(fn=add_memory_fn, inputs=[mem_user_id, mem_desc, mem_date, mem_song_id], outputs=[mem_add_status, mem_add_output])
540
+
541
+ with gr.Group():
542
+ gr.Markdown("### Delete Memory")
543
+ del_mem_id = gr.Number(label="Memory ID", precision=0)
544
+ del_mem_btn = gr.Button("Delete")
545
+ del_mem_status = gr.Textbox(label="Status")
546
+ del_mem_output = gr.JSON(label="Response")
547
+ del_mem_btn.click(fn=delete_memory_fn, inputs=[del_mem_id], outputs=[del_mem_status, del_mem_output])
548
 
549
  with gr.Group():
550
  gr.Markdown("### All Memories")
551
+ memories_table = gr.Dataframe(headers=["ID", "User ID", "Description", "Date", "Song ID"], label="Memories")
552
  memories_load_btn = gr.Button("Load Memories")
553
  memories_load_btn.click(fn=list_memories_fn, outputs=memories_table)
554
 
 
563
  pl_add_output = gr.JSON(label="Response")
564
  pl_add_btn.click(fn=add_playlist_fn, inputs=[pl_name, pl_vibe, pl_mood], outputs=[pl_add_status, pl_add_output])
565
 
566
+ with gr.Group():
567
+ gr.Markdown("### All Playlists")
568
+ playlists_table = gr.Dataframe(headers=["ID", "Name", "Vibe Code"], label="Playlists")
569
+ playlists_load_btn = gr.Button("Load Playlists")
570
+ playlists_load_btn.click(fn=list_playlists_fn, outputs=playlists_table)
571
+
572
+ with gr.Group():
573
+ gr.Markdown("### Delete Playlist")
574
+ del_pl_id = gr.Number(label="Playlist ID", precision=0)
575
+ del_pl_btn = gr.Button("Delete")
576
+ del_pl_status = gr.Textbox(label="Status")
577
+ del_pl_output = gr.JSON(label="Response")
578
+ del_pl_btn.click(fn=delete_playlist_fn, inputs=[del_pl_id], outputs=[del_pl_status, del_pl_output])
579
+
580
  with gr.Tab("🌤️ Contexts"):
581
  with gr.Group():
582
  gr.Markdown("### Add Context")
 
588
  ctx_add_output = gr.JSON(label="Response")
589
  ctx_add_btn.click(fn=add_context_fn, inputs=[ctx_weather, ctx_time, ctx_location], outputs=[ctx_add_status, ctx_add_output])
590
 
591
+ with gr.Group():
592
+ gr.Markdown("### All Contexts")
593
+ ctx_table = gr.Dataframe(headers=["ID", "Weather", "Time of Day", "Location"], label="Contexts")
594
+ ctx_load_btn = gr.Button("Load Contexts")
595
+ ctx_load_btn.click(fn=list_contexts_fn, outputs=ctx_table)
596
+
597
+ with gr.Group():
598
+ gr.Markdown("### Delete Context")
599
+ del_ctx_id = gr.Number(label="Context ID", precision=0)
600
+ del_ctx_btn = gr.Button("Delete")
601
+ del_ctx_status = gr.Textbox(label="Status")
602
+ del_ctx_output = gr.JSON(label="Response")
603
+ del_ctx_btn.click(fn=delete_context_fn, inputs=[del_ctx_id], outputs=[del_ctx_status, del_ctx_output])
604
+
605
  with gr.Tab("🔍 Semantic Search"):
606
  with gr.Group():
607
  gr.Markdown("### Search Songs by Vibe")
 
620
  search_mem_summary = gr.Textbox(label="Results")
621
  search_mem_table = gr.Dataframe(headers=["ID", "User ID", "Description", "Distance"], label="Memories")
622
  search_mem_btn.click(fn=search_memories_fn, inputs=[search_mem_query, search_mem_n], outputs=[search_mem_summary, search_mem_table])
623
+
624
+ with gr.Group():
625
+ gr.Markdown("### Search Contexts")
626
+ search_ctx_query = gr.Textbox(label="Query", placeholder="e.g., 'rainy night drive', 'sunny beach'")
627
+ search_ctx_n = gr.Slider(1, 20, value=5, step=1, label="Results")
628
+ search_ctx_btn = gr.Button("Search")
629
+ search_ctx_summary = gr.Textbox(label="Results")
630
+ search_ctx_table = gr.Dataframe(headers=["ID", "Weather", "Time", "Location", "Distance"], label="Contexts")
631
+ search_ctx_btn.click(fn=search_contexts_fn, inputs=[search_ctx_query, search_ctx_n], outputs=[search_ctx_summary, search_ctx_table])
632
 
633
  with gr.Group():
634
  gr.Markdown("### Search Playlists by Mood")
 
640
  search_pl_btn.click(fn=search_playlists_fn, inputs=[search_pl_query, search_pl_n], outputs=[search_pl_summary, search_pl_table])
641
 
642
  with gr.Tab("ℹ️ Health"):
643
+ with gr.Group():
644
+ gr.Markdown("### Root")
645
+ root_btn = gr.Button("GET /")
646
+ root_status = gr.Textbox(label="Status")
647
+ root_output = gr.JSON(label="Response")
648
+ root_btn.click(fn=root_fn, outputs=[root_status, root_output])
649
+
650
+ with gr.Group():
651
+ gr.Markdown("### Health")
652
+ health_btn = gr.Button("GET /health")
653
+ health_status = gr.Textbox(label="Status")
654
+ health_output = gr.JSON(label="Response")
655
+ health_btn.click(fn=health_fn, outputs=[health_status, health_output])
656
+
657
+ with gr.Tab("▶️ History"):
658
+ with gr.Group():
659
+ gr.Markdown("### List History")
660
+ hist_user_id = gr.Number(label="User ID (optional)", precision=0)
661
+ hist_limit = gr.Number(label="Limit", value=50, precision=0)
662
+ hist_list_btn = gr.Button("Load History")
663
+ hist_status = gr.Textbox(label="Status")
664
+ hist_output = gr.JSON(label="Response")
665
+ hist_list_btn.click(fn=list_history_fn, inputs=[hist_user_id, hist_limit], outputs=[hist_status, hist_output])
666
+
667
+ with gr.Group():
668
+ gr.Markdown("### Add Play Event")
669
+ hist_add_user = gr.Number(label="User ID", precision=0)
670
+ hist_add_song = gr.Number(label="Song ID", precision=0)
671
+ hist_add_ctx = gr.Number(label="Context ID (optional)", precision=0)
672
+ hist_add_dur = gr.Number(label="Duration Seconds (optional)", precision=0)
673
+ hist_add_btn = gr.Button("Add History")
674
+ hist_add_status = gr.Textbox(label="Status")
675
+ hist_add_output = gr.JSON(label="Response")
676
+ hist_add_btn.click(
677
+ fn=add_history_fn,
678
+ inputs=[hist_add_user, hist_add_song, hist_add_ctx, hist_add_dur],
679
+ outputs=[hist_add_status, hist_add_output],
680
+ )
681
+
682
+ with gr.Tab("📊 Analytics"):
683
+ with gr.Group():
684
+ gr.Markdown("### Summary")
685
+ an_hours = gr.Number(label="Hours", value=24, precision=0)
686
+ an_sum_btn = gr.Button("Get Summary")
687
+ an_sum_status = gr.Textbox(label="Status")
688
+ an_sum_output = gr.JSON(label="Response")
689
+ an_sum_btn.click(fn=analytics_summary_fn, inputs=[an_hours], outputs=[an_sum_status, an_sum_output])
690
+
691
+ with gr.Group():
692
+ gr.Markdown("### Top Songs")
693
+ an_limit = gr.Number(label="Limit", value=10, precision=0)
694
+ an_top_btn = gr.Button("Get Top Songs")
695
+ an_top_status = gr.Textbox(label="Status")
696
+ an_top_output = gr.JSON(label="Response")
697
+ an_top_btn.click(fn=analytics_top_songs_fn, inputs=[an_limit], outputs=[an_top_status, an_top_output])
698
+
699
+ with gr.Group():
700
+ gr.Markdown("### Popular Searches")
701
+ ps_hours = gr.Number(label="Hours", value=24, precision=0)
702
+ ps_limit = gr.Number(label="Limit", value=10, precision=0)
703
+ ps_btn = gr.Button("Get Popular Searches")
704
+ ps_status = gr.Textbox(label="Status")
705
+ ps_output = gr.JSON(label="Response")
706
+ ps_btn.click(fn=analytics_popular_searches_fn, inputs=[ps_hours, ps_limit], outputs=[ps_status, ps_output])
707
+
708
+ with gr.Group():
709
+ gr.Markdown("### Storage Files")
710
+ sf_btn = gr.Button("List MP3 Files")
711
+ sf_summary = gr.Textbox(label="Results")
712
+ sf_table = gr.Dataframe(headers=["Object", "Size", "Last Modified"], label="Files")
713
+ sf_btn.click(fn=storage_files_fn, outputs=[sf_summary, sf_table])
714
 
715
  return demo
716