wuhp commited on
Commit
70dd0f7
·
verified ·
1 Parent(s): 960524f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -117
app.py CHANGED
@@ -46,64 +46,67 @@ def create_space(repo_name, sdk, profile, token):
46
  return rid, log, iframe
47
 
48
  def write_file(path, content, repo_id, profile, token):
 
 
 
 
49
  if not (profile and token):
50
  return "⚠️ Please log in first."
51
  if not repo_id:
52
  return "⚠️ Please create a Space first."
 
53
  with open(path, "w") as f:
54
  f.write(content)
 
55
  upload_file(path, path, repo_id,
56
  token=token.token, repo_type="space")
57
  return f"✅ Wrote and uploaded `{path}`"
58
 
59
- def list_files(repo_id, profile, token):
60
- if not (profile and token and repo_id):
61
- return "⚠️ Please log in and create a Space first."
62
- return "\n".join(list_repo_files(repo_id, token=token.token, repo_type="space"))
 
 
 
 
 
 
63
 
64
- def get_build_logs(repo_id, profile, token):
65
- if not (profile and token and repo_id):
66
- return "⚠️ Please log in and create a Space first."
67
  r = get_session().get(
68
  f"{constants.ENDPOINT}/api/spaces/{repo_id}/jwt",
69
  headers=build_hf_headers()
70
  )
71
  hf_raise_for_status(r)
72
  jwt = r.json()["token"]
73
- url = f"https://api.hf.space/v1/{repo_id}/logs/build"
74
  lines = []
75
- with get_session().get(url, headers=build_hf_headers(token=jwt), stream=True) as resp:
 
 
76
  hf_raise_for_status(resp)
77
  for raw in resp.iter_lines():
78
  if raw.startswith(b"data: "):
79
  ev = json.loads(raw[6:].decode())
80
- lines.append(f"[{ev.get('timestamp')}] {ev.get('data')}")
81
  return "\n".join(lines)
82
 
 
 
 
 
 
83
  def get_container_logs(repo_id, profile, token):
84
  if not (profile and token and repo_id):
85
  return "⚠️ Please log in and create a Space first."
86
- r = get_session().get(
87
- f"{constants.ENDPOINT}/api/spaces/{repo_id}/jwt",
88
- headers=build_hf_headers()
89
- )
90
- hf_raise_for_status(r)
91
- jwt = r.json()["token"]
92
- url = f"https://api.hf.space/v1/{repo_id}/logs/run"
93
- lines = []
94
- with get_session().get(url, headers=build_hf_headers(token=jwt), stream=True) as resp:
95
- hf_raise_for_status(resp)
96
- for raw in resp.iter_lines():
97
- if raw.startswith(b"data: "):
98
- ev = json.loads(raw[6:].decode())
99
- lines.append(f"[{ev.get('timestamp')}] {ev.get('data')}")
100
- return "\n".join(lines)
101
 
102
- # — FUNCTION DECLARATIONS —
103
 
104
  func_decls = [
105
  {
106
- "name":"create_space","description":"Create or get a HF Space",
107
  "parameters":{
108
  "type":"object",
109
  "properties":{
@@ -114,12 +117,12 @@ func_decls = [
114
  }
115
  },
116
  {
117
- "name":"write_file","description":"Write or overwrite a file in the Space",
118
  "parameters":{
119
  "type":"object",
120
  "properties":{
121
- "path":{"type":"string","description":"e.g. app.py, requirements.txt, README.md"},
122
- "content":{"type":"string","description":"Full file contents"}
123
  },
124
  "required":["path","content"]
125
  }
@@ -147,7 +150,7 @@ func_decls = [
147
  "properties":{"repo_id":{"type":"string"}},
148
  "required":["repo_id"]
149
  }
150
- },
151
  ]
152
 
153
  # — CHAT HANDLER —
@@ -155,85 +158,113 @@ func_decls = [
155
  def process_message(profile, token, user_msg,
156
  gemini_key, sidebar_repo, sidebar_sdk,
157
  chat_history, session):
158
- # Initialize the chat once
159
  if session.get("chat") is None:
160
  client = genai.Client(api_key=gemini_key)
161
  cfg = types.GenerateContentConfig(
162
  system_instruction=(
163
- "You are a HF Spaces developer assistant. When asked to build a new Space, "
164
- "first call create_space(name,sdk), then call write_file for app.py, requirements.txt "
165
- "and README.md in that order. You must use function calls—never emit raw code."
166
  ),
167
  temperature=0,
168
- tools=[ types.Tool(function_declarations=func_decls) ],
169
- tool_config=types.ToolConfig(
170
- function_calling_config=types.FunctionCallingConfig(mode="ANY")
171
- )
172
  )
173
- session["chat"] = client.chats.create(model="gemini-2.0-flash", config=cfg)
174
- session["repo_id"] = None
175
- session["messages"] = []
176
 
177
- # Auto‐create on “call it NAME”
178
  if session["repo_id"] is None:
179
  m = re.search(r"call (?:it )?([A-Za-z0-9_-]+)", user_msg, re.IGNORECASE)
180
  if m:
181
  name = m.group(1)
182
- rid, log, iframe = create_space(name, sidebar_sdk, profile, token)
183
- session["repo_id"], session["iframe"], session["log"], session["files"] = rid, iframe, log, ""
184
- session["messages"].append({"role":"assistant","content":log})
185
- return session["messages"], iframe, log, "", session
186
-
187
- # Record the user message
 
 
 
 
 
 
 
 
 
188
  session["messages"].append({"role":"user","content":user_msg})
189
 
190
  # Send to Gemini
191
  resp = session["chat"].send_message(user_msg)
192
  part = resp.candidates[0].content.parts[0]
193
 
194
- # Prepare to call back
195
- args = part.function_call.args
196
- if isinstance(args, str):
197
- args = json.loads(args)
198
- fn = part.function_call.name
199
  result = {}
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- if fn == "create_space":
202
- rid, log, iframe = create_space(args["repo_name"], args["sdk"], profile, token)
203
- session["repo_id"], result = rid, {"log":log, "iframe":iframe}
204
-
205
- elif fn == "write_file":
206
- status = write_file(args["path"], args["content"], session["repo_id"], profile, token)
207
- result = {"status":status}
208
-
209
- elif fn == "list_files":
210
- result = {"files": list_files(session["repo_id"], profile, token)}
211
-
212
- elif fn == "get_build_logs":
213
- result = {"log": get_build_logs(session["repo_id"], profile, token)}
214
-
215
- elif fn == "get_run_logs":
216
- result = {"log": get_container_logs(session["repo_id"], profile, token)}
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  else:
219
- result = {"log":f"⚠️ Unknown function {fn}"}
220
 
221
- # **Key fix:** use Part.from_function_response
222
- func_call = part.function_call
223
- resp_parts = [
224
- types.Part(function_call=func_call),
225
- types.Part.from_function_response(name=func_call.name, response=result)
226
- ]
227
- session["chat"].send_message(types.Content(role="function", parts=resp_parts))
228
-
229
- # Fetch the assistant’s final text
230
- assistant_text = session["chat"].get_history()[-1].parts[0].text
231
- session["messages"].append({"role":"assistant","content":assistant_text})
232
 
233
  # Update panels
234
- for k in ("iframe","log","files"):
235
- if k in result:
236
- session[k] = result[k]
237
 
238
  return (
239
  session["messages"],
@@ -247,10 +278,23 @@ def process_message(profile, token, user_msg,
247
 
248
  def sync_manual(profile, token, session):
249
  if not (profile and token and session.get("repo_id")):
250
- return session.get("iframe",""), "⚠️ Cannot sync manual changes.", session.get("files",""), session
251
- fl = list_repo_files(session["repo_id"], token=token.token, repo_type="space")
252
- session["files"], session["log"] = "\n".join(fl), "🔄 Manual changes synced."
253
- return session["iframe"], session["log"], session["files"], session
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  # — BUILD THE UI —
256
 
@@ -263,55 +307,103 @@ with gr.Blocks(css="""
263
  # Sidebar
264
  with gr.Column(elem_id="sidebar", scale=1):
265
  gr.Markdown("### 🔑 HF Login & Config")
266
- login_btn = gr.LoginButton(variant="huggingface", size="sm")
267
  profile_state = gr.State(None)
268
- token_state = gr.State(None)
 
269
  login_btn.click(None, [], [profile_state, token_state])
270
 
271
  status_md = gr.Markdown("*Not logged in.*")
272
- profile_state.change(show_profile, [profile_state], [status_md])
 
 
273
 
274
  models_md = gr.Markdown()
275
  profile_state.change(list_private_models,
276
- [profile_state, token_state],
277
- [models_md])
278
 
279
- gemini_key = gr.Textbox(label="Gemini API Key", type="password")
280
  sidebar_repo = gr.Textbox(label="Space name", placeholder="my-space")
281
- sidebar_sdk = gr.Radio(["gradio","streamlit"], value="gradio", label="SDK")
 
 
282
  gr.Markdown("---")
283
- confirm_btn = gr.Button("🔄 Confirm Manual Changes")
284
 
285
  # Main area
286
  with gr.Column(elem_id="main", scale=3):
287
  tabs = gr.Tabs()
288
  with tabs:
289
  with gr.TabItem("💬 Chat"):
290
- chatbox = gr.Chatbot(type="messages")
291
- user_input = gr.Textbox(show_label=False, placeholder="Ask the LLM…")
292
- send_btn = gr.Button("Send")
 
293
  with gr.TabItem("🛠️ Manual"):
294
- gr.Markdown("#### Manual Controls")
295
- # …repeat your manual-create/upload/logs UI here…
296
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  gr.Markdown("---")
298
  iframe_out = gr.HTML(label="🖼️ Preview")
299
- log_out = gr.Textbox(label="📋 Latest Log", lines=4)
300
- files_out = gr.Textbox(label="📚 Files", lines=4)
 
301
 
302
  state = gr.State({})
303
- send_btn.click(
304
- process_message,
305
- inputs=[profile_state, token_state, user_input,
306
- gemini_key, sidebar_repo, sidebar_sdk,
307
- chatbox, state],
308
- outputs=[chatbox, iframe_out, log_out, files_out, state]
309
- )
310
- confirm_btn.click(
311
- sync_manual,
312
- inputs=[profile_state, token_state, state],
313
- outputs=[iframe_out, log_out, files_out, state]
314
- )
315
 
316
  if __name__ == "__main__":
317
  demo.launch()
 
46
  return rid, log, iframe
47
 
48
  def write_file(path, content, repo_id, profile, token):
49
+ """
50
+ Write or overwrite any file in the Space.
51
+ The LLM can call this for app.py, requirements.txt, README.md, etc.
52
+ """
53
  if not (profile and token):
54
  return "⚠️ Please log in first."
55
  if not repo_id:
56
  return "⚠️ Please create a Space first."
57
+ # Save locally
58
  with open(path, "w") as f:
59
  f.write(content)
60
+ # Upload to HF Space
61
  upload_file(path, path, repo_id,
62
  token=token.token, repo_type="space")
63
  return f"✅ Wrote and uploaded `{path}`"
64
 
65
+ def upload_file_to_space(file, path, repo_id, profile, token):
66
+ if not (profile and token):
67
+ return "⚠️ Please log in first."
68
+ if not repo_id:
69
+ return "⚠️ Please create a Space first."
70
+ if not file:
71
+ return "⚠️ No file selected."
72
+ upload_file(file.name, path, repo_id,
73
+ token=token.token, repo_type="space")
74
+ return f"✅ Uploaded `{path}` to `{repo_id}`"
75
 
76
+ def _fetch_space_logs_level(repo_id, level):
 
 
77
  r = get_session().get(
78
  f"{constants.ENDPOINT}/api/spaces/{repo_id}/jwt",
79
  headers=build_hf_headers()
80
  )
81
  hf_raise_for_status(r)
82
  jwt = r.json()["token"]
83
+ url = f"https://api.hf.space/v1/{repo_id}/logs/{level}"
84
  lines = []
85
+ with get_session().get(url,
86
+ headers=build_hf_headers(token=jwt),
87
+ stream=True) as resp:
88
  hf_raise_for_status(resp)
89
  for raw in resp.iter_lines():
90
  if raw.startswith(b"data: "):
91
  ev = json.loads(raw[6:].decode())
92
+ lines.append(f"[{ev.get('timestamp','')}] {ev.get('data','')}")
93
  return "\n".join(lines)
94
 
95
+ def get_build_logs(repo_id, profile, token):
96
+ if not (profile and token and repo_id):
97
+ return "⚠️ Please log in and create a Space first."
98
+ return _fetch_space_logs_level(repo_id, "build")
99
+
100
  def get_container_logs(repo_id, profile, token):
101
  if not (profile and token and repo_id):
102
  return "⚠️ Please log in and create a Space first."
103
+ return _fetch_space_logs_level(repo_id, "run")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ # — GEMINI FUNCTION DECLARATIONS —
106
 
107
  func_decls = [
108
  {
109
+ "name":"create_space","description":"Create/get a HF Space",
110
  "parameters":{
111
  "type":"object",
112
  "properties":{
 
117
  }
118
  },
119
  {
120
+ "name":"write_file","description":"Write/overwrite a file in the Space",
121
  "parameters":{
122
  "type":"object",
123
  "properties":{
124
+ "path":{"type":"string","description":"File path, e.g. app.py or requirements.txt"},
125
+ "content":{"type":"string","description":"The full text content to write to the file."}
126
  },
127
  "required":["path","content"]
128
  }
 
150
  "properties":{"repo_id":{"type":"string"}},
151
  "required":["repo_id"]
152
  }
153
+ }
154
  ]
155
 
156
  # — CHAT HANDLER —
 
158
  def process_message(profile, token, user_msg,
159
  gemini_key, sidebar_repo, sidebar_sdk,
160
  chat_history, session):
161
+ # Initialize the chat
162
  if session.get("chat") is None:
163
  client = genai.Client(api_key=gemini_key)
164
  cfg = types.GenerateContentConfig(
165
  system_instruction=(
166
+ "You are a HF Spaces admin. You can create spaces, "
167
+ "write files (app.py, requirements.txt, README.md...), "
168
+ "list files, and fetch logs."
169
  ),
170
  temperature=0,
171
+ tools=[ types.Tool(function_declarations=func_decls) ]
172
+ )
173
+ session["chat"] = client.chats.create(
174
+ model="gemini-2.0-flash", config=cfg
175
  )
176
+ session["repo_id"] = None
177
+ session["messages"]= []
 
178
 
179
+ # Allow “call it NAME” in free chat
180
  if session["repo_id"] is None:
181
  m = re.search(r"call (?:it )?([A-Za-z0-9_-]+)", user_msg, re.IGNORECASE)
182
  if m:
183
  name = m.group(1)
184
+ rid, log, iframe = create_space(
185
+ name, sidebar_sdk, profile, token
186
+ )
187
+ session["repo_id"] = rid
188
+ session["iframe"] = iframe
189
+ session["log"] = log
190
+ session["files"] = ""
191
+ session["messages"].append({
192
+ "role":"assistant",
193
+ "content":f"✅ Created Space `{name}`. {log}"
194
+ })
195
+ return (session["messages"],
196
+ iframe, log, "", session)
197
+
198
+ # Record user message
199
  session["messages"].append({"role":"user","content":user_msg})
200
 
201
  # Send to Gemini
202
  resp = session["chat"].send_message(user_msg)
203
  part = resp.candidates[0].content.parts[0]
204
 
 
 
 
 
 
205
  result = {}
206
+ if part.function_call:
207
+ args = part.function_call.args
208
+ if isinstance(args, str):
209
+ args = json.loads(args)
210
+ fn = part.function_call.name
211
+
212
+ if fn == "create_space":
213
+ rid, log, iframe = create_space(
214
+ args["repo_name"], args["sdk"], profile, token
215
+ )
216
+ session["repo_id"] = rid
217
+ result = {"log":log,"iframe":iframe}
218
 
219
+ elif fn == "write_file":
220
+ status = write_file(
221
+ args["path"], args["content"],
222
+ session["repo_id"], profile, token
223
+ )
224
+ result = {"status":status}
 
 
 
 
 
 
 
 
 
 
225
 
226
+ elif fn == "list_files":
227
+ fl = list_repo_files(
228
+ session["repo_id"], token=token.token, repo_type="space"
229
+ )
230
+ result = {"files":"\n".join(fl)}
231
+
232
+ elif fn == "get_build_logs":
233
+ result = {"log":get_build_logs(
234
+ session["repo_id"], profile, token
235
+ )}
236
+
237
+ elif fn == "get_run_logs":
238
+ result = {"log":get_container_logs(
239
+ session["repo_id"], profile, token
240
+ )}
241
+
242
+ else:
243
+ result = {"log":f"⚠️ Unknown function {fn}"}
244
+
245
+ # **Always** pass a JSON string here
246
+ session["chat"].send_message(
247
+ types.Content(
248
+ role="function",
249
+ parts=[ types.Part(
250
+ function_call=part.function_call,
251
+ function_response=json.dumps(result)
252
+ )]
253
+ )
254
+ )
255
+ assistant_text = session["chat"].get_history()[-1].parts[0].text
256
  else:
257
+ assistant_text = part.text
258
 
259
+ # Record assistant
260
+ session["messages"].append({
261
+ "role":"assistant","content":assistant_text
262
+ })
 
 
 
 
 
 
 
263
 
264
  # Update panels
265
+ if "iframe" in result: session["iframe"] = result["iframe"]
266
+ if "log" in result: session["log"] = result["log"]
267
+ if "files" in result: session["files"] = result["files"]
268
 
269
  return (
270
  session["messages"],
 
278
 
279
  def sync_manual(profile, token, session):
280
  if not (profile and token and session.get("repo_id")):
281
+ return (
282
+ session.get("iframe",""),
283
+ "⚠️ Cannot sync manual changes.",
284
+ session.get("files",""),
285
+ session
286
+ )
287
+ fl = list_repo_files(
288
+ session["repo_id"], token=token.token, repo_type="space"
289
+ )
290
+ session["files"] = "\n".join(fl)
291
+ session["log"] = "🔄 Manual changes synced."
292
+ return (
293
+ session.get("iframe",""),
294
+ session["log"],
295
+ session["files"],
296
+ session
297
+ )
298
 
299
  # — BUILD THE UI —
300
 
 
307
  # Sidebar
308
  with gr.Column(elem_id="sidebar", scale=1):
309
  gr.Markdown("### 🔑 HF Login & Config")
310
+ login_btn = gr.LoginButton(variant="huggingface", size="sm")
311
  profile_state = gr.State(None)
312
+ token_state = gr.State(None)
313
+
314
  login_btn.click(None, [], [profile_state, token_state])
315
 
316
  status_md = gr.Markdown("*Not logged in.*")
317
+ profile_state.change(show_profile,
318
+ inputs=[profile_state],
319
+ outputs=[status_md])
320
 
321
  models_md = gr.Markdown()
322
  profile_state.change(list_private_models,
323
+ inputs=[profile_state, token_state],
324
+ outputs=[models_md])
325
 
326
+ gemini_key = gr.Textbox(label="Gemini API Key", type="password")
327
  sidebar_repo = gr.Textbox(label="Space name", placeholder="my-space")
328
+ sidebar_sdk = gr.Radio(["gradio","streamlit"],
329
+ value="gradio", label="SDK")
330
+
331
  gr.Markdown("---")
332
+ confirm_btn = gr.Button("🔄 Confirm Manual Changes")
333
 
334
  # Main area
335
  with gr.Column(elem_id="main", scale=3):
336
  tabs = gr.Tabs()
337
  with tabs:
338
  with gr.TabItem("💬 Chat"):
339
+ chatbox = gr.Chatbot(type="messages")
340
+ user_input = gr.Textbox(show_label=False,
341
+ placeholder="Ask the LLM…")
342
+ send_btn = gr.Button("Send")
343
  with gr.TabItem("🛠️ Manual"):
344
+ gr.Markdown("#### Create a Space")
345
+ repo_m = gr.Textbox(label="Name")
346
+ sdk_m = gr.Radio(["gradio","streamlit"],
347
+ value="gradio", label="SDK")
348
+ create_btn = gr.Button("Create Space")
349
+ sess_id = gr.Textbox(visible=False)
350
+ log_c = gr.Textbox(label="Log",
351
+ interactive=False, lines=2)
352
+ preview = gr.HTML("<p>No Space yet.</p>")
353
+
354
+ create_btn.click(create_space,
355
+ inputs=[repo_m, sdk_m,
356
+ profile_state, token_state],
357
+ outputs=[sess_id, log_c, preview])
358
+
359
+ gr.Markdown("#### Upload File")
360
+ path = gr.Textbox(label="Path", value="app.py")
361
+ file_u = gr.File()
362
+ upload_btn = gr.Button("Upload File")
363
+ log_u = gr.Textbox(label="Log",
364
+ interactive=False, lines=2)
365
+
366
+ upload_btn.click(upload_file_to_space,
367
+ inputs=[file_u, path, sess_id,
368
+ profile_state, token_state],
369
+ outputs=[log_u])
370
+
371
+ gr.Markdown("#### Fetch Logs")
372
+ b_btn = gr.Button("Build Logs")
373
+ r_btn = gr.Button("Run Logs")
374
+ log_b = gr.Textbox(label="Build",
375
+ interactive=False, lines=5)
376
+ log_r = gr.Textbox(label="Run",
377
+ interactive=False, lines=5)
378
+
379
+ b_btn.click(get_build_logs,
380
+ inputs=[sess_id,
381
+ profile_state, token_state],
382
+ outputs=[log_b])
383
+ r_btn.click(get_container_logs,
384
+ inputs=[sess_id,
385
+ profile_state, token_state],
386
+ outputs=[log_r])
387
+
388
+ # Persistent panels
389
  gr.Markdown("---")
390
  iframe_out = gr.HTML(label="🖼️ Preview")
391
+ log_out = gr.Textbox(label="📋 Latest Log",
392
+ lines=4)
393
+ files_out = gr.Textbox(label="📚 Files", lines=4)
394
 
395
  state = gr.State({})
396
+ send_btn.click(process_message,
397
+ inputs=[profile_state, token_state,
398
+ user_input, gemini_key,
399
+ sidebar_repo, sidebar_sdk,
400
+ chatbox, state],
401
+ outputs=[chatbox, iframe_out,
402
+ log_out, files_out, state])
403
+
404
+ confirm_btn.click(sync_manual,
405
+ inputs=[profile_state, token_state, state],
406
+ outputs=[iframe_out, log_out, files_out, state])
 
407
 
408
  if __name__ == "__main__":
409
  demo.launch()