harp-dev commited on
Commit
09cb8b9
·
verified ·
1 Parent(s): 4226cd6

Deploy HARP wrapper via model agent

Browse files
Files changed (2) hide show
  1. README.md +4 -2
  2. app.py +79 -19
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: "MelodyFlow"
3
  colorFrom: indigo
4
  colorTo: gray
5
  sdk: gradio
@@ -9,7 +9,9 @@ pinned: false
9
  license: "other"
10
  ---
11
 
12
- # MelodyFlow
 
 
13
 
14
  - Inputs: textbox, slider, slider, checkbox, slider, slider, audio
15
  - Outputs: audio, audio, audio
 
1
  ---
2
+ title: "Melodyflow"
3
  colorFrom: indigo
4
  colorTo: gray
5
  sdk: gradio
 
9
  license: "other"
10
  ---
11
 
12
+ # Melodyflow
13
+
14
+ TODO: describe this model.
15
 
16
  - Inputs: textbox, slider, slider, checkbox, slider, slider, audio
17
  - Outputs: audio, audio, audio
app.py CHANGED
@@ -1,6 +1,8 @@
1
  from __future__ import annotations
2
 
3
  import os
 
 
4
 
5
  import gradio as gr
6
 
@@ -12,6 +14,10 @@ _BACKEND_SPACE = "facebook/MelodyFlow"
12
  _BACKEND_API_NAME = "/predict"
13
  _BACKEND_TOKEN_ENV = "HF_TOKEN"
14
  _ACCEPT_USER_TOKEN = True
 
 
 
 
15
  _client = None
16
 
17
 
@@ -26,6 +32,54 @@ def _backend_client():
26
  return _client
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def _quota_hint(message):
30
  # Turn a backend ZeroGPU quota error into an actionable message.
31
  # NOTE: 'message' is the backend's error text; it never contains our token.
@@ -55,25 +109,31 @@ model_card = ModelCard(
55
 
56
  def process_fn(text, steps, target_flowstep, regularize, regularization_strength, duration, melody, _hf_user_token=''):
57
  _tok = (_hf_user_token or '').strip()
58
- if _tok:
59
- _conn = Client(_BACKEND_SPACE, hf_token=_tok)
60
- else:
61
- _conn = _backend_client()
62
- try:
63
- _raw = _conn.predict(
64
- 'facebook/melodyflow-t24-30secs',
65
- text,
66
- 'midpoint',
67
- steps,
68
- target_flowstep,
69
- regularize,
70
- regularization_strength,
71
- duration,
72
- handle_file(melody),
73
- api_name="/predict",
74
- )
75
- except Exception as _exc: # surface a token-aware hint, never the token
76
- raise gr.Error(_quota_hint(str(_exc)))
 
 
 
 
 
 
77
  _values = list(_raw) if isinstance(_raw, (list, tuple)) else [_raw]
78
  _detail = " | ".join(str(_v) for _v in _values if isinstance(_v, str) and _v.strip())
79
  _out_generated_audio_variation_1 = _values[0] if len(_values) > 0 else None
 
1
  from __future__ import annotations
2
 
3
  import os
4
+ import time
5
+ import urllib.request
6
 
7
  import gradio as gr
8
 
 
14
  _BACKEND_API_NAME = "/predict"
15
  _BACKEND_TOKEN_ENV = "HF_TOKEN"
16
  _ACCEPT_USER_TOKEN = True
17
+ # How many times to wake+retry a sleeping backend, and how long to wait for
18
+ # it to boot (a free Space cold start can take a few minutes).
19
+ _CALL_RETRIES = int(os.environ.get("BACKEND_CALL_RETRIES", "4"))
20
+ _WAKE_TIMEOUT = float(os.environ.get("BACKEND_WAKE_TIMEOUT", "420"))
21
  _client = None
22
 
23
 
 
32
  return _client
33
 
34
 
35
+ def _reset_client():
36
+ # Drop the cached connection so the next attempt reconnects to a Space
37
+ # that has since finished waking.
38
+ global _client
39
+ _client = None
40
+
41
+
42
+ def _make_conn(tok):
43
+ tok = (tok or '').strip()
44
+ if tok:
45
+ return Client(_BACKEND_SPACE, hf_token=tok)
46
+ return _backend_client()
47
+
48
+
49
+ def _space_url(space):
50
+ slug = space.strip().lower().replace('/', '-').replace('_', '-')
51
+ return f'https://{slug}.hf.space/'
52
+
53
+
54
+ def _is_cold_start(message):
55
+ # Errors that mean 'the backend was asleep/booting', worth waking+retrying
56
+ # (vs. a real application error, which we surface immediately).
57
+ _low = (message or '').lower()
58
+ return any(s in _low for s in (
59
+ 'read operation timed out', 'timed out', 'timeout', 'starting',
60
+ 'building', 'not ready', 'no application', 'connection', '503', '502',
61
+ ))
62
+
63
+
64
+ def _wake_backend():
65
+ # A sleeping Space boots when its URL is hit; poll until it answers (or
66
+ # the budget expires) so the retried call lands on a running backend.
67
+ _url = _space_url(_BACKEND_SPACE)
68
+ _deadline = time.time() + _WAKE_TIMEOUT
69
+ _delay = 5.0
70
+ while time.time() < _deadline:
71
+ try:
72
+ _req = urllib.request.Request(_url, headers={'User-Agent': 'harp-frontend'})
73
+ with urllib.request.urlopen(_req, timeout=30) as _resp:
74
+ if getattr(_resp, 'status', 200) < 500:
75
+ return True
76
+ except Exception:
77
+ pass
78
+ time.sleep(_delay)
79
+ _delay = min(_delay * 1.5, 30.0)
80
+ return False
81
+
82
+
83
  def _quota_hint(message):
84
  # Turn a backend ZeroGPU quota error into an actionable message.
85
  # NOTE: 'message' is the backend's error text; it never contains our token.
 
109
 
110
  def process_fn(text, steps, target_flowstep, regularize, regularization_strength, duration, melody, _hf_user_token=''):
111
  _tok = (_hf_user_token or '').strip()
112
+ # Call the backend, waking it and retrying if it was asleep (a cold
113
+ # start otherwise fails the first hit with 'read operation timed out').
114
+ _raw = None
115
+ for _attempt in range(_CALL_RETRIES + 1):
116
+ try:
117
+ _conn = _make_conn(_tok)
118
+ _raw = _conn.predict(
119
+ 'facebook/melodyflow-t24-30secs',
120
+ text,
121
+ 'midpoint',
122
+ steps,
123
+ target_flowstep,
124
+ regularize,
125
+ regularization_strength,
126
+ duration,
127
+ (handle_file(melody) if melody else None),
128
+ api_name="/predict",
129
+ )
130
+ break
131
+ except Exception as _exc: # never surfaces the token
132
+ if _attempt < _CALL_RETRIES and _is_cold_start(str(_exc)):
133
+ _reset_client()
134
+ _wake_backend()
135
+ continue
136
+ raise gr.Error(_quota_hint(str(_exc)))
137
  _values = list(_raw) if isinstance(_raw, (list, tuple)) else [_raw]
138
  _detail = " | ".join(str(_v) for _v in _values if isinstance(_v, str) and _v.strip())
139
  _out_generated_audio_variation_1 = _values[0] if len(_values) > 0 else None