File size: 14,824 Bytes
ee7ac4d
 
 
 
 
897f827
c053fbf
ee7ac4d
c053fbf
 
 
 
 
ee7ac4d
 
 
 
f5b96f5
ee7ac4d
 
c053fbf
 
 
 
 
ee7ac4d
c053fbf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee7ac4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c053fbf
ee7ac4d
 
 
 
 
c053fbf
ee7ac4d
 
 
c053fbf
 
ee7ac4d
 
 
 
 
 
 
 
 
c053fbf
 
 
 
ee7ac4d
 
c053fbf
ee7ac4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c053fbf
 
 
 
ee7ac4d
 
 
 
 
 
c053fbf
 
ee7ac4d
 
c053fbf
ee7ac4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c053fbf
 
 
 
ee7ac4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f5b96f5
ee7ac4d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
import streamlit as st
import json
import requests
import time
from datetime import datetime

# Polling function
def poll_status(task_id, single_poll=False):
    """Poll Suno API for task status including task ID"""
    if not task_id:
        st.error("❌ Task ID cannot be empty")
        return False
    
    headers = {
        "Authorization": f"Bearer {st.session_state.suno_api_key}",
        "Content-Type": "application/json"
    }
    
    try:
        with st.spinner(f"Checking status for task: {task_id}..."):
            # Two possible endpoints - let's try both formats
            endpoints =[
                f"https://api.sunoapi.org/api/v1/wav/record-info/{task_id}",
                f"https://api.sunoapi.org/api/v1/wav/record-info?taskId={task_id}"
            ]
            
            response = None
            for endpoint in endpoints:
                try:
                    response = requests.get(
                        endpoint,
                        headers=headers,
                        timeout=30
                    )
                    if response.status_code == 200:
                        break
                except:
                    continue
            
            if not response:
                st.error("❌ Failed to connect to polling endpoint")
                return False
                
            if response.status_code == 200:
                result = response.json()
                
                # Display result
                if result.get("code") == 200:
                    data = result.get("data", {})
                    
                    if data.get("successFlag") == "SUCCESS":
                        # Audio is ready!
                        audio_url = data.get("response", {}).get("audioWavUrl")
                        if audio_url:
                            st.success("✅ Audio ready!")
                            st.balloons()
                            
                            # Display audio info
                            st.markdown("### 🎵 Audio Information")
                            cols = st.columns(2)
                            with cols[0]:
                                st.metric("Task ID", data.get("taskId", "N/A")[:12] + "...")
                                st.metric("Music ID", data.get("musicId", "N/A")[:12] + "...")
                            with cols[1]:
                                st.metric("Status", data.get("successFlag", "N/A"))
                                st.metric("Created", data.get("createTime", "N/A"))
                            
                            # Audio player
                            st.markdown("### 🔊 Listen")
                            st.audio(audio_url, format="audio/wav")
                            
                            # Download section
                            st.markdown("### 📥 Download")
                            st.code(audio_url)
                            
                            col_dl1, col_dl2 = st.columns(2)
                            with col_dl1:
                                st.download_button(
                                    label="⬇ Download WAV",
                                    data=requests.get(audio_url).content if audio_url.startswith("http") else b"",
                                    file_name=f"audio_{data.get('taskId', 'unknown')[:8]}.wav",
                                    mime="audio/wav",
                                    key=f"download_{task_id}"
                                )
                            with col_dl2:
                                if st.button("📋 Copy URL", key=f"copy_{task_id}"):
                                    st.code(audio_url)
                                    st.success("URL copied to clipboard!")
                            
                            # Stop auto-polling if active
                            if st.session_state.get('auto_poll'):
                                st.session_state.auto_poll = False
                                st.success("✅ Auto-polling stopped (audio ready)")
                        else:
                            st.info("⏳ Audio still processing...")
                            st.json(data)
                    else:
                        # Still processing or error
                        status = data.get("successFlag", "UNKNOWN")
                        if status == "PROCESSING":
                            st.info(f"⏳ Processing... ({status})")
                        elif status == "FAILED":
                            st.error(f"❌ Processing failed: {data.get('errorMessage', 'Unknown error')}")
                        else:
                            st.info(f"ℹ️ Status: {status}")
                        
                        # Show progress info
                        st.json(data)
                else:
                    st.error(f"❌ API Error: {result.get('msg', 'Unknown error')}")
                    st.json(result)
            else:
                st.error(f"❌ Status check failed: {response.status_code}")
                try:
                    error_data = response.json()
                    st.json(error_data)
                except:
                    st.text(f"Response: {response.text}")
                    
    except Exception as e:
        st.error(f"⚠️ Polling error: {str(e)}")

# Page configuration
st.set_page_config(
    page_title="Suno API Generator",
    page_icon="🎵",
    layout="wide"
)

# Load YOUR secret names
SUNO_API_KEY = st.secrets.get("SunoKey", "")
CALLBACK_URL = "https://1hit.no/wav/cb.php"

# Initialize session state
if 'task_id' not in st.session_state:
    st.session_state.task_id = None
if 'polling' not in st.session_state:
    st.session_state.polling = False
if 'poll_results' not in st.session_state:
    st.session_state.poll_results = []
if 'auto_poll' not in st.session_state:
    st.session_state.auto_poll = False
if 'suno_api_key' not in st.session_state:
    st.session_state.suno_api_key = SUNO_API_KEY

# Title and info
st.title("🎵 Suno API Audio Generator")

with st.expander("📋 How it works", expanded=True):
    st.markdown("""
    ### Workflow:
    1. **Step 1**: Submit task to Suno API → Get `taskId` from response
    2. **Step 2**: Suno processes audio (async)
    3. **Step 3**: Suno sends callback to: `https://1hit.no/wav/cb.php`
    4. **Step 4**: You can view callbacks at: `https://1hit.no/wav/view.php`
    5. **Step 5**: OR use Poll button to check status via `/api/v1/record-info` endpoint

    ### Polling Endpoint:
    ```javascript
    fetch('https://api.sunoapi.org/api/v1/wav/record-info', {
      method: 'GET',
      headers: {
        'Authorization': 'Bearer YOUR_TOKEN'
      }
    })
    ```
    
    ### Error Handling:
    - **400**: Task ID cannot be empty
    - Make sure to include task ID in polling request
    """)

# Main layout
col1, col2 = st.columns([2, 1])

with col1:
    st.header("🚀 Step 1: Submit to Suno API")
    
    # Form inputs
    task_id = st.text_input("Task ID", value="5c79****be8e", help="Enter your Suno task ID", key="input_task_id")
    audio_id = st.text_input("Audio ID", value="e231****-****-****-****-****8cadc7dc", help="Enter your Suno audio ID", key="input_audio_id")
    
    if st.button("🎵 Generate Audio", type="primary", use_container_width=True, key="generate_btn"):
        if not SUNO_API_KEY:
            st.error("❌ SunoKey not configured in Hugging Face secrets")
            st.info("Add `SunoKey` secret in Hugging Face Space settings")
            st.stop()
        
        if not task_id or not audio_id:
            st.error("❌ Please enter both Task ID and Audio ID")
            st.stop()
        
        with st.spinner("Sending request to Suno API..."):
            headers = {
                "Authorization": f"Bearer {SUNO_API_KEY}",
                "Content-Type": "application/json"
            }
            
            # Test sending to correct endpoint
            endpoint_url = "https://api.sunoapi.org/api/v1/wav/generate"
            st.info(f"Making request to: {endpoint_url}")
            
            payload = {
                "taskId": task_id,
                "audioId": audio_id,
                "callBackUrl": CALLBACK_URL
            }
            
            st.json(payload)
            
            try:
                response = requests.post(
                    endpoint_url,
                    headers=headers,
                    json=payload,
                    timeout=30
                )
                
                if response.status_code == 200:
                    result = response.json()
                    
                    # Display response
                    with st.expander("📋 API Response", expanded=True):
                        st.json(result)
                    
                    if result.get("code") == 200 and "data" in result and "taskId" in result["data"]:
                        st.session_state.task_id = result["data"]["taskId"]
                        st.success(f"✅ Task submitted successfully!")
                        st.info(f"**Task ID:** `{st.session_state.task_id}`")
                        
                        # Auto-fill poll input
                        st.session_state.poll_task_input = st.session_state.task_id
                        
                        st.markdown("---")
                        st.markdown("### 🔄 Next Steps:")
                        st.markdown("""
                        1. **Automatic Callback**: Suno will send result to your callback URL
                        2. **Manual Poll**: Use the Poll button to check status
                        3. **View Logs**: Check [Callback Viewer](https://1hit.no/wav/view.php)
                        """)
                    else:
                        st.error("❌ Unexpected response format")
                        
                        # Debug info
                        st.markdown("**Debug Response:**")
                        st.code(str(result))
                else:
                    st.error(f"❌ API Error: {response.status_code}")
                    try:
                        error_data = response.json()
                        st.json(error_data)
                    except:
                        st.text(f"Response: {response.text}")
                        
            except requests.exceptions.Timeout:
                st.error("⏰ Request timed out after 30 seconds")
            except requests.exceptions.ConnectionError:
                st.error("🔌 Connection error - check your internet connection")
            except requests.exceptions.RequestException as e:
                st.error(f"⚠️ Request failed: {str(e)}")

with col2:
    st.header("🔍 Step 2: Poll Status")
    
    # Manual task ID input for polling
    poll_task_id = st.text_input(
        "Task ID to Poll",
        value=st.session_state.get('poll_task_input', st.session_state.task_id or ""),
        help="Enter task ID to check status",
        key="poll_task_input_field"
    )
    
    col2a, col2b = st.columns(2)
    
    with col2a:
        poll_once_btn = st.button("🔄 Poll Once", type="secondary", use_container_width=True, key="poll_once_btn")
    
    with col2b:
        auto_poll_btn = st.button("🔁 Auto Poll (10s)", type="secondary", use_container_width=True, key="auto_poll_btn")

# Handle poll button clicks
if poll_once_btn:
    if not poll_task_id:
        st.error("❌ Please enter a Task ID")
    elif not SUNO_API_KEY:
        st.error("❌ SunoKey not configured")
    else:
        poll_status(poll_task_id, single_poll=True)

if auto_poll_btn:
    if not poll_task_id:
        st.error("❌ Please enter a Task ID")
    elif not SUNO_API_KEY:
        st.error("❌ SunoKey not configured")
    else:
        st.session_state.auto_poll = True
        st.session_state.auto_poll_task_id = poll_task_id
        st.rerun()

# Display poll results if any
if st.session_state.poll_results:
    st.markdown("---")
    st.header("📊 Polling History")
    
    # Show last 5 results
    recent_results = list(reversed(st.session_state.poll_results[-5:]))
    
    for i, result in enumerate(recent_results):
        with st.expander(f"Poll {i+1} - {result['timestamp']} - Task: {result['task_id'][:12]}...", expanded=(i == len(recent_results)-1)):
            response_data = result["response"]
            
            if response_data.get("code") == 200:
                data = response_data.get("data", {})
                status = data.get("successFlag", "UNKNOWN")
                
                # Status badge
                if status == "SUCCESS":
                    st.success(f"✅ {status}")
                elif status == "PROCESSING":
                    st.info(f"⏳ {status}")
                elif status == "FAILED":
                    st.error(f"❌ {status}")
                else:
                    st.warning(f"⚠️ {status}")
                
                # Display key info
                cols = st.columns(3)
                with cols[0]:
                    st.metric("Task ID", data.get("taskId", "N/A")[:12] + "...")
                with cols[1]:
                    st.metric("Music ID", data.get("musicId", "N/A")[:12] + "...")
                with cols[2]:
                    st.metric("Status", status)
                
                # Show audio if available
                audio_url = data.get("response", {}).get("audioWavUrl")
                if audio_url:
                    st.audio(audio_url, format="audio/wav")
                    st.markdown(f"**Audio URL:** `{audio_url}`")
                
                # Show full response
                with st.expander("📋 Full Response JSON"):
                    st.json(response_data)
            else:
                st.error(f"❌ Error: {response_data.get('msg', 'Unknown error')}")
                st.json(response_data)

# Quick links and info
st.markdown("---")
st.markdown("### 📝 Quick Links")
st.markdown("""
- 🔗 [View Suno Callbacks](https://1hit.no/wav/view.php) - See all callback logs
- 📚 [Suno API Documentation](https://docs.sunoapi.org/) - Official API docs
- 🔄 **Poll Endpoint**: `GET https://api.sunoapi.org/api/v1/wav/record-info`
""")

# Secret status
with st.expander("🔐 Secret Status", expanded=False):
    if SUNO_API_KEY:
        masked_key = f"{SUNO_API_KEY[:8]}...{SUNO_API_KEY[-8:]}" if len(SUNO_API_KEY) > 16 else "••••••••"
        st.success(f"✅ SunoKey loaded ({len(SUNO_API_KEY)} chars)")
        st.code(f"SunoKey: {masked_key}")
    else:
        st.error("❌ SunoKey not found")
    
    st.info(f"**Callback URL:** `{CALLBACK_URL}`")