wuhp commited on
Commit
2d4c4f3
·
verified ·
1 Parent(s): d2b8f97

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +206 -229
app.py CHANGED
@@ -1,7 +1,13 @@
1
- import re, json, os
 
 
2
  import gradio as gr
3
  from huggingface_hub import (
4
- create_repo, list_models, upload_file, list_repo_files, constants
 
 
 
 
5
  )
6
  from huggingface_hub.utils import build_hf_headers, get_session, hf_raise_for_status
7
  from google import genai
@@ -35,121 +41,125 @@ def create_space(repo_name, sdk, profile, token):
35
  if not (profile and token):
36
  return "", "⚠️ Please log in first.", "<p>No Space created yet.</p>"
37
  rid = f"{profile.username}/{repo_name}"
38
- create_repo(rid,
39
- token=token.token,
40
- exist_ok=True,
41
- repo_type="space",
42
- space_sdk=sdk)
 
 
43
  url = f"https://huggingface.co/spaces/{rid}"
44
  log = f"✅ Space ready: {url} (SDK: {sdk})"
45
  iframe = f'<iframe src="{url}" width="100%" height="400px"></iframe>'
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":{
113
- "repo_name":{"type":"string"},
114
- "sdk":{"type":"string","enum":["gradio","streamlit"]}
115
- },
116
- "required":["repo_name","sdk"]
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
- }
 
129
  },
130
  {
131
- "name":"list_files","description":"List files in the Space",
132
- "parameters":{
133
- "type":"object",
134
- "properties":{"repo_id":{"type":"string"}},
135
- "required":["repo_id"]
136
- }
 
137
  },
138
  {
139
- "name":"get_build_logs","description":"Fetch build logs",
140
- "parameters":{
141
- "type":"object",
142
- "properties":{"repo_id":{"type":"string"}},
143
- "required":["repo_id"]
144
- }
 
145
  },
146
  {
147
- "name":"get_run_logs","description":"Fetch run logs",
148
- "parameters":{
149
- "type":"object",
150
- "properties":{"repo_id":{"type":"string"}},
151
- "required":["repo_id"]
152
- }
 
153
  }
154
  ]
155
 
@@ -158,119 +168,117 @@ func_decls = [
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"],
271
- session.get("iframe",""),
272
- session.get("log",""),
273
- session.get("files",""),
274
  session
275
  )
276
 
@@ -279,18 +287,18 @@ def process_message(profile, token, user_msg,
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
@@ -307,103 +315,72 @@ with gr.Blocks(css="""
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()
 
1
+ import re
2
+ import json
3
+ import os
4
  import gradio as gr
5
  from huggingface_hub import (
6
+ create_repo,
7
+ list_models,
8
+ upload_file,
9
+ list_repo_files,
10
+ constants
11
  )
12
  from huggingface_hub.utils import build_hf_headers, get_session, hf_raise_for_status
13
  from google import genai
 
41
  if not (profile and token):
42
  return "", "⚠️ Please log in first.", "<p>No Space created yet.</p>"
43
  rid = f"{profile.username}/{repo_name}"
44
+ create_repo(
45
+ rid,
46
+ token=token.token,
47
+ exist_ok=True,
48
+ repo_type="space",
49
+ space_sdk=sdk
50
+ )
51
  url = f"https://huggingface.co/spaces/{rid}"
52
  log = f"✅ Space ready: {url} (SDK: {sdk})"
53
  iframe = f'<iframe src="{url}" width="100%" height="400px"></iframe>'
54
  return rid, log, iframe
55
 
56
  def write_file(path, content, repo_id, profile, token):
 
 
 
 
57
  if not (profile and token):
58
  return "⚠️ Please log in first."
59
  if not repo_id:
60
  return "⚠️ Please create a Space first."
 
61
  with open(path, "w") as f:
62
  f.write(content)
63
+ upload_file(path, path, repo_id, token=token.token, repo_type="space")
 
 
64
  return f"✅ Wrote and uploaded `{path}`"
65
 
66
+ def list_files(repo_id, profile, token):
67
+ if not (profile and token and repo_id):
68
+ return "⚠️ Please log in and create a Space first."
69
+ files = list_repo_files(repo_id, token=token.token, repo_type="space")
70
+ return "\n".join(files)
 
 
 
 
 
71
 
72
+ def get_build_logs(repo_id, profile, token):
73
+ if not (profile and token and repo_id):
74
+ return "⚠️ Please log in and create a Space first."
75
  r = get_session().get(
76
  f"{constants.ENDPOINT}/api/spaces/{repo_id}/jwt",
77
  headers=build_hf_headers()
78
  )
79
  hf_raise_for_status(r)
80
  jwt = r.json()["token"]
81
+ url = f"https://api.hf.space/v1/{repo_id}/logs/build"
82
  lines = []
83
+ with get_session().get(url, headers=build_hf_headers(token=jwt), stream=True) as resp:
 
 
84
  hf_raise_for_status(resp)
85
  for raw in resp.iter_lines():
86
  if raw.startswith(b"data: "):
87
  ev = json.loads(raw[6:].decode())
88
+ lines.append(f"[{ev.get('timestamp')}] {ev.get('data')}")
89
  return "\n".join(lines)
90
 
 
 
 
 
 
91
  def get_container_logs(repo_id, profile, token):
92
  if not (profile and token and repo_id):
93
  return "⚠️ Please log in and create a Space first."
94
+ r = get_session().get(
95
+ f"{constants.ENDPOINT}/api/spaces/{repo_id}/jwt",
96
+ headers=build_hf_headers()
97
+ )
98
+ hf_raise_for_status(r)
99
+ jwt = r.json()["token"]
100
+ url = f"https://api.hf.space/v1/{repo_id}/logs/run"
101
+ lines = []
102
+ with get_session().get(url, headers=build_hf_headers(token=jwt), stream=True) as resp:
103
+ hf_raise_for_status(resp)
104
+ for raw in resp.iter_lines():
105
+ if raw.startswith(b"data: "):
106
+ ev = json.loads(raw[6:].decode())
107
+ lines.append(f"[{ev.get('timestamp')}] {ev.get('data')}")
108
+ return "\n".join(lines)
109
 
110
+ # — FUNCTION DECLARATIONS —
111
 
112
  func_decls = [
113
  {
114
+ "name": "create_space",
115
+ "description": "Create or get a HF Space",
116
+ "parameters": {
117
+ "type": "object",
118
+ "properties": {
119
+ "repo_name": {"type": "string"},
120
+ "sdk": {"type": "string", "enum": ["gradio", "streamlit"]}
121
+ },
122
+ "required": ["repo_name", "sdk"]
123
+ }
124
  },
125
  {
126
+ "name": "write_file",
127
+ "description": "Write or overwrite a file in the Space",
128
+ "parameters": {
129
+ "type": "object",
130
+ "properties": {
131
+ "path": {"type": "string"},
132
+ "content": {"type": "string"}
133
+ },
134
+ "required": ["path", "content"]
135
+ }
136
  },
137
  {
138
+ "name": "list_files",
139
+ "description": "List files in the Space",
140
+ "parameters": {
141
+ "type": "object",
142
+ "properties": {"repo_id": {"type": "string"}},
143
+ "required": ["repo_id"]
144
+ }
145
  },
146
  {
147
+ "name": "get_build_logs",
148
+ "description": "Fetch build logs",
149
+ "parameters": {
150
+ "type": "object",
151
+ "properties": {"repo_id": {"type": "string"}},
152
+ "required": ["repo_id"]
153
+ }
154
  },
155
  {
156
+ "name": "get_run_logs",
157
+ "description": "Fetch run logs",
158
+ "parameters": {
159
+ "type": "object",
160
+ "properties": {"repo_id": {"type": "string"}},
161
+ "required": ["repo_id"]
162
+ }
163
  }
164
  ]
165
 
 
168
  def process_message(profile, token, user_msg,
169
  gemini_key, sidebar_repo, sidebar_sdk,
170
  chat_history, session):
171
+ # Initialize
172
  if session.get("chat") is None:
173
  client = genai.Client(api_key=gemini_key)
174
  cfg = types.GenerateContentConfig(
175
  system_instruction=(
176
+ "You are a HF Spaces developer assistant. "
177
+ "You MUST use function calls (create_space, write_file, list_files, "
178
+ "get_build_logs, get_run_logs) to perform all actions—never return code inline."
179
  ),
180
  temperature=0,
181
+ tools=[types.Tool(function_declarations=func_decls)],
182
+ tool_config=types.ToolConfig(
183
+ function_calling_config=types.FunctionCallingConfig(mode="ANY")
184
+ )
185
  )
186
+ session["chat"] = client.chats.create(
187
  model="gemini-2.0-flash", config=cfg
188
  )
189
+ session["repo_id"] = None
190
+ session["messages"] = []
191
 
192
+ # Auto-create from “call it NAME”
193
  if session["repo_id"] is None:
194
  m = re.search(r"call (?:it )?([A-Za-z0-9_-]+)", user_msg, re.IGNORECASE)
195
  if m:
196
  name = m.group(1)
197
+ rid, log, iframe = create_space(name, sidebar_sdk, profile, token)
 
 
198
  session["repo_id"] = rid
199
+ session["iframe"] = iframe
200
+ session["log"] = log
201
+ session["files"] = ""
202
  session["messages"].append({
203
+ "role": "assistant",
204
+ "content": log
205
  })
206
+ return session["messages"], iframe, log, "", session
 
207
 
208
+ # Record user
209
+ session["messages"].append({"role": "user", "content": user_msg})
210
 
211
  # Send to Gemini
212
  resp = session["chat"].send_message(user_msg)
213
  part = resp.candidates[0].content.parts[0]
214
 
215
+ # Handle function call
216
+ args = part.function_call.args
217
+ if isinstance(args, str):
218
+ args = json.loads(args)
219
+ fn = part.function_call.name
220
  result = {}
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ if fn == "create_space":
223
+ rid, log, iframe = create_space(
224
+ args["repo_name"], args["sdk"], profile, token
225
+ )
226
+ session["repo_id"] = rid
227
+ result = {"log": log, "iframe": iframe}
228
 
229
+ elif fn == "write_file":
230
+ status = write_file(
231
+ args["path"], args["content"],
232
+ session["repo_id"], profile, token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  )
234
+ result = {"status": status}
235
+
236
+ elif fn == "list_files":
237
+ result = {"files": list_files(
238
+ session["repo_id"], profile, token
239
+ )}
240
+
241
+ elif fn == "get_build_logs":
242
+ result = {"log": get_build_logs(
243
+ session["repo_id"], profile, token
244
+ )}
245
+
246
+ elif fn == "get_run_logs":
247
+ result = {"log": get_container_logs(
248
+ session["repo_id"], profile, token
249
+ )}
250
+
251
  else:
252
+ result = {"log": f"⚠️ Unknown function {fn}"}
253
+
254
+ # Respond via function_response
255
+ session["chat"].send_message(
256
+ types.Content(
257
+ role="function",
258
+ parts=[types.Part(
259
+ function_call=part.function_call,
260
+ function_response=json.dumps(result)
261
+ )]
262
+ )
263
+ )
264
+ assistant_text = session["chat"].get_history()[-1].parts[0].text
265
 
266
  # Record assistant
267
  session["messages"].append({
268
+ "role": "assistant",
269
+ "content": assistant_text
270
  })
271
 
272
  # Update panels
273
  if "iframe" in result: session["iframe"] = result["iframe"]
274
+ if "log" in result: session["log"] = result["log"]
275
+ if "files" in result: session["files"] = result["files"]
276
 
277
  return (
278
  session["messages"],
279
+ session.get("iframe", ""),
280
+ session.get("log", ""),
281
+ session.get("files", ""),
282
  session
283
  )
284
 
 
287
  def sync_manual(profile, token, session):
288
  if not (profile and token and session.get("repo_id")):
289
  return (
290
+ session.get("iframe", ""),
291
  "⚠️ Cannot sync manual changes.",
292
+ session.get("files", ""),
293
  session
294
  )
295
  fl = list_repo_files(
296
  session["repo_id"], token=token.token, repo_type="space"
297
  )
298
  session["files"] = "\n".join(fl)
299
+ session["log"] = "🔄 Manual changes synced."
300
  return (
301
+ session["iframe"],
302
  session["log"],
303
  session["files"],
304
  session
 
315
  # Sidebar
316
  with gr.Column(elem_id="sidebar", scale=1):
317
  gr.Markdown("### 🔑 HF Login & Config")
318
+ login_btn = gr.LoginButton(variant="huggingface", size="sm")
319
  profile_state = gr.State(None)
320
+ token_state = gr.State(None)
321
 
322
  login_btn.click(None, [], [profile_state, token_state])
323
 
324
  status_md = gr.Markdown("*Not logged in.*")
325
+ profile_state.change(
326
+ show_profile, inputs=[profile_state], outputs=[status_md]
327
+ )
328
 
329
  models_md = gr.Markdown()
330
+ profile_state.change(
331
+ list_private_models,
332
+ inputs=[profile_state, token_state],
333
+ outputs=[models_md]
334
+ )
335
 
336
+ gemini_key = gr.Textbox(
337
+ label="Gemini API Key", type="password"
338
+ )
339
+ sidebar_repo = gr.Textbox(
340
+ label="Space name", placeholder="my-space"
341
+ )
342
+ sidebar_sdk = gr.Radio(
343
+ ["gradio", "streamlit"], value="gradio", label="SDK"
344
+ )
345
 
346
  gr.Markdown("---")
347
+ confirm_btn = gr.Button("🔄 Confirm Manual Changes")
348
 
349
  # Main area
350
  with gr.Column(elem_id="main", scale=3):
351
  tabs = gr.Tabs()
352
  with tabs:
353
  with gr.TabItem("💬 Chat"):
354
+ chatbox = gr.Chatbot(type="messages")
355
+ user_input = gr.Textbox(
356
+ show_label=False, placeholder="Ask the LLM…"
357
+ )
358
+ send_btn = gr.Button("Send")
359
  with gr.TabItem("🛠️ Manual"):
360
+ gr.Markdown("#### Manual Controls")
361
+ # (Repeat buttons for create/upload/logs as needed)
362
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  gr.Markdown("---")
364
  iframe_out = gr.HTML(label="🖼️ Preview")
365
+ log_out = gr.Textbox(label="📋 Latest Log", lines=4)
366
+ files_out = gr.Textbox(label="📚 Files", lines=4)
 
367
 
368
  state = gr.State({})
369
+ send_btn.click(
370
+ process_message,
371
+ inputs=[
372
+ profile_state, token_state, user_input,
373
+ gemini_key, sidebar_repo, sidebar_sdk,
374
+ chatbox, state
375
+ ],
376
+ outputs=[chatbox, iframe_out, log_out, files_out, state]
377
+ )
378
+
379
+ confirm_btn.click(
380
+ sync_manual,
381
+ inputs=[profile_state, token_state, state],
382
+ outputs=[iframe_out, log_out, files_out, state]
383
+ )
384
 
385
  if __name__ == "__main__":
386
  demo.launch()