cutechicken commited on
Commit
47c8e15
ยท
verified ยท
1 Parent(s): 3eab771

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +285 -212
app.py CHANGED
@@ -1,18 +1,18 @@
1
  import gradio as gr
2
- from fastapi import FastAPI, UploadFile, File, Request
3
- from fastapi.responses import FileResponse, HTMLResponse
4
  import uuid
5
  import os
6
  import json
7
  from datetime import datetime
8
  from typing import Dict, List
9
  import shutil
10
- import asyncio
11
- from contextlib import asynccontextmanager
12
 
13
  # Initialize data storage
14
  peers: Dict[str, Dict] = {}
15
  jobs: List[Dict] = []
 
 
16
 
17
  # Create directories
18
  os.makedirs("results", exist_ok=True)
@@ -25,6 +25,7 @@ import time
25
  import os
26
  import sys
27
  from datetime import datetime
 
28
 
29
  # ์„ค์ •
30
  PEER_ID = f"peer-{os.getenv('COMPUTERNAME', 'unknown')}-{datetime.now().strftime('%Y%m%d%H%M%S')}"
@@ -45,14 +46,48 @@ def check_gpu():
45
  def register_peer():
46
  """์„œ๋ฒ„์— Peer ๋“ฑ๋ก"""
47
  try:
48
- response = requests.post(f"{SERVER_URL}/api/peers/register", params={"peer_id": PEER_ID})
 
 
 
 
49
  if response.status_code == 200:
50
- print(f"โœ… Peer ๋“ฑ๋ก ์„ฑ๊ณต: {PEER_ID}")
51
- return True
 
 
52
  except Exception as e:
53
  print(f"โŒ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
54
  return False
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  def generate_image_cpu(prompt, output_path):
57
  """CPU๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
58
  from PIL import Image, ImageDraw, ImageFont
@@ -74,6 +109,24 @@ def generate_image_cpu(prompt, output_path):
74
  img.save(output_path)
75
  print(f"๐Ÿ“ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ: {output_path}")
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  def main():
78
  print("๐Ÿš€ P2P GPU ํด๋ผ์ด์–ธํŠธ ์‹œ์ž‘...")
79
 
@@ -84,45 +137,39 @@ def main():
84
  while True:
85
  try:
86
  # Heartbeat
87
- requests.post(f"{SERVER_URL}/api/peers/heartbeat", params={"peer_id": PEER_ID})
 
 
 
 
88
 
89
  # ์ž‘์—… ์š”์ฒญ
90
- response = requests.get(f"{SERVER_URL}/api/jobs/request", params={"peer_id": PEER_ID})
91
- if response.status_code == 200:
92
- job_data = response.json()
 
93
 
94
- if job_data.get("job"):
95
- job = job_data["job"]
96
- job_id = job["id"]
97
- prompt = job["prompt"]
98
-
99
- print(f"\\n๐Ÿ“‹ ์ƒˆ ์ž‘์—… ์ˆ˜์‹ : {prompt}")
100
-
101
- # ์ด๋ฏธ์ง€ ์ƒ์„ฑ
102
- output_path = f"{job_id}.png"
103
-
104
- if check_gpu():
105
- print("๐ŸŽฎ GPU ๋ชจ๋“œ๋กœ ์ƒ์„ฑ ์ค‘...")
106
- # ์‹ค์ œ GPU ์ƒ์„ฑ ์ฝ”๋“œ๊ฐ€ ์—ฌ๊ธฐ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค
107
- generate_image_cpu(prompt, output_path)
108
- else:
109
- print("๐Ÿ’ป CPU ๋ชจ๋“œ๋กœ ์ƒ์„ฑ ์ค‘...")
110
- generate_image_cpu(prompt, output_path)
111
-
112
- # ๊ฒฐ๊ณผ ์—…๋กœ๋“œ
113
- with open(output_path, 'rb') as f:
114
- files = {'file': (output_path, f, 'image/png')}
115
- response = requests.post(
116
- f"{SERVER_URL}/api/jobs/result",
117
- params={"job_id": job_id},
118
- files=files
119
- )
120
-
121
- if response.status_code == 200:
122
- print("โœ… ๊ฒฐ๊ณผ ์—…๋กœ๋“œ ์„ฑ๊ณต")
123
-
124
- # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
125
- os.remove(output_path)
126
 
127
  time.sleep(10) # 10์ดˆ๋งˆ๋‹ค ํ™•์ธ
128
 
@@ -145,14 +192,15 @@ if __name__ == "__main__":
145
  '''
146
 
147
  # Create client files
148
- with open("client/peer_agent.py", "w", encoding="utf-8") as f:
149
- f.write(CLIENT_CODE)
 
150
 
151
- with open("client/requirements.txt", "w") as f:
152
- f.write("requests\npillow\n")
153
 
154
- with open("client/README.md", "w", encoding="utf-8") as f:
155
- f.write("""# P2P GPU ํด๋ผ์ด์–ธํŠธ (Windows)
156
 
157
  ## ์„ค์น˜ ๋ฐฉ๋ฒ•
158
  1. Python 3.8 ์ด์ƒ ์„ค์น˜
@@ -165,141 +213,113 @@ with open("client/README.md", "w", encoding="utf-8") as f:
165
  - GPU๊ฐ€ ์—†์œผ๋ฉด CPU ๋ชจ๋“œ๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
166
  """)
167
 
168
- # FastAPI app with lifespan
169
- @asynccontextmanager
170
- async def lifespan(app: FastAPI):
171
- # Startup
172
- print("P2P GPU Hub ์‹œ์ž‘...")
173
- yield
174
- # Shutdown
175
- print("P2P GPU Hub ์ข…๋ฃŒ...")
176
-
177
- app = FastAPI(lifespan=lifespan)
178
 
179
- # API endpoints
180
- @app.get("/api/status")
181
- async def get_status():
182
- """์‹œ์Šคํ…œ ์ƒํƒœ ์กฐํšŒ"""
183
- active_peers = sum(1 for p in peers.values()
184
- if (datetime.now() - p['last_seen']).seconds < 60)
185
- pending_jobs = sum(1 for j in jobs if j['status'] == 'pending')
186
- completed_jobs = sum(1 for j in jobs if j['status'] == 'completed')
187
-
188
- recent_results = [
189
- {"filename": j['filename'], "prompt": j['prompt']}
190
- for j in jobs[-10:] if j['status'] == 'completed' and 'filename' in j
191
- ]
192
-
193
- return {
194
- "active_peers": active_peers,
195
- "pending_jobs": pending_jobs,
196
- "completed_jobs": completed_jobs,
197
- "recent_results": recent_results
198
- }
199
-
200
- @app.post("/api/peers/register")
201
- async def register_peer(peer_id: str):
202
  """Peer ๋“ฑ๋ก"""
203
- peers[peer_id] = {
204
- "status": "idle",
205
- "last_seen": datetime.now(),
206
- "jobs_completed": 0
207
- }
208
- return {"status": "registered", "peer_id": peer_id}
209
-
210
- @app.post("/api/peers/heartbeat")
211
- async def heartbeat(peer_id: str):
212
- """Peer ์ƒํƒœ ์—…๋ฐ์ดํŠธ"""
213
- if peer_id in peers:
214
- peers[peer_id]["last_seen"] = datetime.now()
215
- return {"status": "alive"}
216
- return {"status": "unregistered"}
217
-
218
- @app.post("/api/jobs/submit")
219
- async def submit_job(request: Request):
220
  """์ž‘์—… ์ œ์ถœ"""
221
- data = await request.json()
 
 
222
  job_id = str(uuid.uuid4())
223
- job = {
224
- "id": job_id,
225
- "prompt": data.get("prompt", ""),
226
- "status": "pending",
227
- "created_at": datetime.now()
228
- }
229
- jobs.append(job)
230
- return {"job_id": job_id, "status": "submitted"}
231
-
232
- @app.get("/api/jobs/request")
233
- async def request_job(peer_id: str):
234
- """์ž‘์—… ์š”์ฒญ"""
235
- for job in jobs:
236
- if job["status"] == "pending":
237
- job["status"] = "assigned"
238
- job["peer_id"] = peer_id
239
- job["assigned_at"] = datetime.now()
240
- return {"job": job}
241
 
242
- return {"job": None}
243
 
244
- @app.post("/api/jobs/result")
245
- async def submit_result(job_id: str, file: UploadFile = File(...)):
 
 
 
 
 
 
 
 
 
 
246
  """์ž‘์—… ๊ฒฐ๊ณผ ์ œ์ถœ"""
247
- filename = f"{job_id}.png"
248
- file_path = f"results/{filename}"
249
-
250
- with open(file_path, "wb") as buffer:
251
- shutil.copyfileobj(file.file, buffer)
252
-
253
- for job in jobs:
254
- if job["id"] == job_id:
255
- job["status"] = "completed"
256
- job["filename"] = filename
257
- job["completed_at"] = datetime.now()
258
-
259
- if "peer_id" in job and job["peer_id"] in peers:
260
- peers[job["peer_id"]]["jobs_completed"] += 1
261
- break
262
 
263
- return {"status": "success", "filename": filename}
264
-
265
- @app.get("/api/results/{filename}")
266
- async def get_result(filename: str):
267
- """์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ์กฐํšŒ"""
268
- file_path = f"results/{filename}"
269
- if os.path.exists(file_path):
270
- return FileResponse(file_path)
271
- return {"error": "File not found"}
272
-
273
- @app.get("/api/client/{filename}")
274
- async def get_client_file(filename: str):
275
- """ํด๋ผ์ด์–ธํŠธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ"""
276
- file_path = f"client/{filename}"
277
- if os.path.exists(file_path):
278
- return FileResponse(file_path, filename=filename)
279
- return {"error": "File not found"}
 
 
 
 
 
 
 
 
 
 
280
 
281
- # Gradio interface functions
282
- def gradio_submit_job(prompt):
283
- """Gradio๋ฅผ ํ†ตํ•œ ์ž‘์—… ์ œ์ถœ"""
284
- if not prompt:
285
- return "ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
286
 
287
- job_id = str(uuid.uuid4())
288
- job = {
289
- "id": job_id,
290
- "prompt": prompt,
291
- "status": "pending",
292
- "created_at": datetime.now()
293
- }
294
- jobs.append(job)
295
- return f"โœ… ์ž‘์—…์ด ์ œ์ถœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!\n์ž‘์—… ID: {job_id[:8]}..."
296
-
297
- def gradio_get_status():
298
- """Gradio๋ฅผ ํ†ตํ•œ ์ƒํƒœ ์กฐํšŒ"""
299
- active_peers = sum(1 for p in peers.values()
300
- if (datetime.now() - p['last_seen']).seconds < 60)
301
- pending = sum(1 for j in jobs if j['status'] == 'pending')
302
- completed = sum(1 for j in jobs if j['status'] == 'completed')
303
 
304
  status_text = f"""## ๐Ÿ“Š ์‹œ์Šคํ…œ ์ƒํƒœ
305
 
@@ -311,28 +331,36 @@ def gradio_get_status():
311
  ### ์ตœ๊ทผ ์ž‘์—… ๋ชฉ๋ก
312
  """
313
 
314
- # Add recent jobs
315
- recent_jobs = jobs[-5:][::-1] # Last 5 jobs, reversed
316
- if recent_jobs:
317
- for job in recent_jobs:
318
- status_emoji = "โœ…" if job['status'] == 'completed' else "โณ" if job['status'] == 'pending' else "๐Ÿ”„"
319
- status_text += f"\n{status_emoji} **{job['id'][:8]}**: {job['prompt'][:50]}... ({job['status']})"
320
- else:
321
- status_text += "\n์•„์ง ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค."
322
 
323
  return status_text
324
 
325
- def gradio_get_gallery():
326
  """๊ฐค๋Ÿฌ๋ฆฌ์šฉ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ"""
327
  image_files = []
328
- for job in jobs[-20:][::-1]: # Last 20 jobs, newest first
329
- if job['status'] == 'completed' and 'filename' in job:
330
- file_path = f"results/{job['filename']}"
331
- if os.path.exists(file_path):
332
- image_files.append((file_path, job['prompt']))
 
333
 
334
  return image_files
335
 
 
 
 
 
 
 
 
336
  # Custom CSS
337
  custom_css = """
338
  .container {
@@ -386,7 +414,7 @@ with gr.Blocks(title="P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ", css=custom_css, theme=gr
386
  """)
387
 
388
  submit_btn.click(
389
- fn=gradio_submit_job,
390
  inputs=prompt_input,
391
  outputs=result_text
392
  )
@@ -399,15 +427,14 @@ with gr.Blocks(title="P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ", css=custom_css, theme=gr
399
  status_display = gr.Markdown()
400
  with gr.Row():
401
  refresh_btn = gr.Button("๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
402
- auto_refresh = gr.Checkbox(label="์ž๋™ ์ƒˆ๋กœ๊ณ ์นจ (10์ดˆ)", value=True)
403
 
404
  refresh_btn.click(
405
- fn=gradio_get_status,
406
  outputs=status_display
407
  )
408
 
409
- # Auto-refresh status
410
- demo.load(fn=gradio_get_status, outputs=status_display)
411
 
412
  with gr.Tab("๐Ÿ–ผ๏ธ ๊ฐค๋Ÿฌ๋ฆฌ"):
413
  gr.Markdown("### ์ตœ๊ทผ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋“ค")
@@ -424,23 +451,42 @@ with gr.Blocks(title="P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ", css=custom_css, theme=gr
424
  refresh_gallery_btn = gr.Button("๐Ÿ”„ ๊ฐค๋Ÿฌ๋ฆฌ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
425
 
426
  refresh_gallery_btn.click(
427
- fn=gradio_get_gallery,
428
  outputs=gallery
429
  )
430
 
431
  # Auto-load gallery
432
- demo.load(fn=gradio_get_gallery, outputs=gallery)
433
 
434
  with gr.Tab("๐Ÿ’ป ํด๋ผ์ด์–ธํŠธ ๋‹ค์šด๋กœ๋“œ"):
435
  gr.Markdown("""
436
  ## Windows ํด๋ผ์ด์–ธํŠธ ์„ค์ • ๊ฐ€์ด๋“œ
437
 
438
  ### ๐Ÿ“ฅ 1๋‹จ๊ณ„: ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
439
- ๋‹ค์Œ ํŒŒ์ผ๋“ค์„ ๋‹ค์šด๋กœ๋“œํ•˜์„ธ์š”:
440
- - [peer_agent.py](/api/client/peer_agent.py) - ๋ฉ”์ธ ํด๋ผ์ด์–ธํŠธ
441
- - [requirements.txt](/api/client/requirements.txt) - ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ๋ชฉ๋ก
442
- - [README.md](/api/client/README.md) - ์„ค๋ช…์„œ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
 
444
  ### ๐Ÿ 2๋‹จ๊ณ„: Python ์„ค์น˜
445
  - Python 3.8 ์ด์ƒ ๋ฒ„์ „ ํ•„์š”
446
  - [python.org](https://python.org)์—์„œ ๋‹ค์šด๋กœ๋“œ
@@ -466,19 +512,46 @@ with gr.Blocks(title="P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ", css=custom_css, theme=gr
466
  - CPU ํด๋ฐฑ ์ง€์›
467
  - ์ž๋™ ์ž‘์—… ์ฒ˜๋ฆฌ
468
  - 10์ดˆ๋งˆ๋‹ค ์ƒˆ ์ž‘์—… ํ™•์ธ
469
- """)
470
 
471
- with gr.Row():
472
- gr.Markdown("""
473
- ### ๐ŸŽฎ GPU ์ง€์›
474
- - NVIDIA GPU๊ฐ€ ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ํ™œ์šฉ
475
- - GPU๊ฐ€ ์—†์–ด๋„ CPU๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
476
- - GPU ์‚ฌ์šฉ๋ฅ  20% ๋ฏธ๋งŒ์ผ ๋•Œ๋งŒ ์ž‘์—… ์ˆ˜ํ–‰
477
- """)
478
-
479
- # Mount Gradio app to FastAPI
480
- app = gr.mount_gradio_app(app, demo, path="/")
481
-
482
- # For local testing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  if __name__ == "__main__":
484
  demo.launch()
 
1
  import gradio as gr
 
 
2
  import uuid
3
  import os
4
  import json
5
  from datetime import datetime
6
  from typing import Dict, List
7
  import shutil
8
+ import time
9
+ import threading
10
 
11
  # Initialize data storage
12
  peers: Dict[str, Dict] = {}
13
  jobs: List[Dict] = []
14
+ job_lock = threading.Lock()
15
+ peer_lock = threading.Lock()
16
 
17
  # Create directories
18
  os.makedirs("results", exist_ok=True)
 
25
  import os
26
  import sys
27
  from datetime import datetime
28
+ import json
29
 
30
  # ์„ค์ •
31
  PEER_ID = f"peer-{os.getenv('COMPUTERNAME', 'unknown')}-{datetime.now().strftime('%Y%m%d%H%M%S')}"
 
46
  def register_peer():
47
  """์„œ๋ฒ„์— Peer ๋“ฑ๋ก"""
48
  try:
49
+ response = requests.post(
50
+ f"{SERVER_URL}/run/register_peer",
51
+ json={"data": [PEER_ID]},
52
+ headers={"Content-Type": "application/json"}
53
+ )
54
  if response.status_code == 200:
55
+ result = response.json()
56
+ if result.get("data") and result["data"][0] == "registered":
57
+ print(f"โœ… Peer ๋“ฑ๋ก ์„ฑ๊ณต: {PEER_ID}")
58
+ return True
59
  except Exception as e:
60
  print(f"โŒ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
61
  return False
62
 
63
+ def send_heartbeat():
64
+ """์„œ๋ฒ„์— heartbeat ์ „์†ก"""
65
+ try:
66
+ response = requests.post(
67
+ f"{SERVER_URL}/run/heartbeat",
68
+ json={"data": [PEER_ID]},
69
+ headers={"Content-Type": "application/json"}
70
+ )
71
+ return response.status_code == 200
72
+ except:
73
+ return False
74
+
75
+ def request_job():
76
+ """์ž‘์—… ์š”์ฒญ"""
77
+ try:
78
+ response = requests.post(
79
+ f"{SERVER_URL}/run/request_job",
80
+ json={"data": [PEER_ID]},
81
+ headers={"Content-Type": "application/json"}
82
+ )
83
+ if response.status_code == 200:
84
+ result = response.json()
85
+ if result.get("data") and result["data"][0]:
86
+ return json.loads(result["data"][0])
87
+ except Exception as e:
88
+ print(f"์ž‘์—… ์š”์ฒญ ์‹คํŒจ: {e}")
89
+ return None
90
+
91
  def generate_image_cpu(prompt, output_path):
92
  """CPU๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
93
  from PIL import Image, ImageDraw, ImageFont
 
109
  img.save(output_path)
110
  print(f"๐Ÿ“ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ: {output_path}")
111
 
112
+ def submit_result(job_id, image_path):
113
+ """์ž‘์—… ๊ฒฐ๊ณผ ์ œ์ถœ"""
114
+ try:
115
+ # ์ด๋ฏธ์ง€๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ
116
+ import base64
117
+ with open(image_path, "rb") as f:
118
+ image_data = base64.b64encode(f.read()).decode()
119
+
120
+ response = requests.post(
121
+ f"{SERVER_URL}/run/submit_result",
122
+ json={"data": [job_id, image_data]},
123
+ headers={"Content-Type": "application/json"}
124
+ )
125
+ return response.status_code == 200
126
+ except Exception as e:
127
+ print(f"๊ฒฐ๊ณผ ์ œ์ถœ ์‹คํŒจ: {e}")
128
+ return False
129
+
130
  def main():
131
  print("๐Ÿš€ P2P GPU ํด๋ผ์ด์–ธํŠธ ์‹œ์ž‘...")
132
 
 
137
  while True:
138
  try:
139
  # Heartbeat
140
+ if not send_heartbeat():
141
+ print("์„œ๋ฒ„ ์—ฐ๊ฒฐ ๋Š๊น€. ์žฌ๋“ฑ๋ก ์‹œ๋„...")
142
+ if not register_peer():
143
+ time.sleep(30)
144
+ continue
145
 
146
  # ์ž‘์—… ์š”์ฒญ
147
+ job = request_job()
148
+ if job:
149
+ job_id = job["id"]
150
+ prompt = job["prompt"]
151
 
152
+ print(f"\\n๐Ÿ“‹ ์ƒˆ ์ž‘์—… ์ˆ˜์‹ : {prompt}")
153
+
154
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ
155
+ output_path = f"{job_id}.png"
156
+
157
+ if check_gpu():
158
+ print("๐ŸŽฎ GPU ๋ชจ๋“œ๋กœ ์ƒ์„ฑ ์ค‘...")
159
+ # ์‹ค์ œ GPU ์ƒ์„ฑ ์ฝ”๋“œ๊ฐ€ ์—ฌ๊ธฐ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค
160
+ generate_image_cpu(prompt, output_path)
161
+ else:
162
+ print("๐Ÿ’ป CPU ๋ชจ๋“œ๋กœ ์ƒ์„ฑ ์ค‘...")
163
+ generate_image_cpu(prompt, output_path)
164
+
165
+ # ๊ฒฐ๊ณผ ์—…๋กœ๋“œ
166
+ if submit_result(job_id, output_path):
167
+ print("โœ… ๊ฒฐ๊ณผ ์—…๋กœ๋“œ ์„ฑ๊ณต")
168
+ else:
169
+ print("โŒ ๊ฒฐ๊ณผ ์—…๋กœ๋“œ ์‹คํŒจ")
170
+
171
+ # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
172
+ os.remove(output_path)
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  time.sleep(10) # 10์ดˆ๋งˆ๋‹ค ํ™•์ธ
175
 
 
192
  '''
193
 
194
  # Create client files
195
+ def create_client_files():
196
+ with open("client/peer_agent.py", "w", encoding="utf-8") as f:
197
+ f.write(CLIENT_CODE)
198
 
199
+ with open("client/requirements.txt", "w") as f:
200
+ f.write("requests\npillow\n")
201
 
202
+ with open("client/README.md", "w", encoding="utf-8") as f:
203
+ f.write("""# P2P GPU ํด๋ผ์ด์–ธํŠธ (Windows)
204
 
205
  ## ์„ค์น˜ ๋ฐฉ๋ฒ•
206
  1. Python 3.8 ์ด์ƒ ์„ค์น˜
 
213
  - GPU๊ฐ€ ์—†์œผ๋ฉด CPU ๋ชจ๋“œ๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
214
  """)
215
 
216
+ create_client_files()
 
 
 
 
 
 
 
 
 
217
 
218
+ # Cleanup old peers periodically
219
+ def cleanup_old_peers():
220
+ while True:
221
+ time.sleep(60) # Check every minute
222
+ with peer_lock:
223
+ current_time = datetime.now()
224
+ to_remove = []
225
+ for peer_id, peer_info in peers.items():
226
+ if (current_time - peer_info['last_seen']).seconds > 120: # 2 minutes timeout
227
+ to_remove.append(peer_id)
228
+ for peer_id in to_remove:
229
+ del peers[peer_id]
230
+
231
+ # Start cleanup thread
232
+ cleanup_thread = threading.Thread(target=cleanup_old_peers, daemon=True)
233
+ cleanup_thread.start()
234
+
235
+ # Gradio API functions
236
+ def register_peer(peer_id: str):
 
 
 
 
237
  """Peer ๋“ฑ๋ก"""
238
+ with peer_lock:
239
+ peers[peer_id] = {
240
+ "status": "idle",
241
+ "last_seen": datetime.now(),
242
+ "jobs_completed": 0
243
+ }
244
+ return "registered"
245
+
246
+ def heartbeat(peer_id: str):
247
+ """Peer heartbeat"""
248
+ with peer_lock:
249
+ if peer_id in peers:
250
+ peers[peer_id]["last_seen"] = datetime.now()
251
+ return "alive"
252
+ return "unregistered"
253
+
254
+ def submit_job(prompt: str):
255
  """์ž‘์—… ์ œ์ถœ"""
256
+ if not prompt:
257
+ return "ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
258
+
259
  job_id = str(uuid.uuid4())
260
+ with job_lock:
261
+ job = {
262
+ "id": job_id,
263
+ "prompt": prompt,
264
+ "status": "pending",
265
+ "created_at": datetime.now().isoformat()
266
+ }
267
+ jobs.append(job)
 
 
 
 
 
 
 
 
 
 
268
 
269
+ return f"โœ… ์ž‘์—…์ด ์ œ์ถœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!\n์ž‘์—… ID: {job_id[:8]}..."
270
 
271
+ def request_job(peer_id: str):
272
+ """์ž‘์—… ์š”์ฒญ"""
273
+ with job_lock:
274
+ for job in jobs:
275
+ if job["status"] == "pending":
276
+ job["status"] = "assigned"
277
+ job["peer_id"] = peer_id
278
+ job["assigned_at"] = datetime.now().isoformat()
279
+ return json.dumps(job)
280
+ return None
281
+
282
+ def submit_result(job_id: str, image_base64: str):
283
  """์ž‘์—… ๊ฒฐ๊ณผ ์ œ์ถœ"""
284
+ import base64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
+ # base64 ๋””์ฝ”๋”ฉํ•˜์—ฌ ํŒŒ์ผ๋กœ ์ €์žฅ
287
+ try:
288
+ image_data = base64.b64decode(image_base64)
289
+ filename = f"{job_id}.png"
290
+ file_path = f"results/{filename}"
291
+
292
+ with open(file_path, "wb") as f:
293
+ f.write(image_data)
294
+
295
+ with job_lock:
296
+ for job in jobs:
297
+ if job["id"] == job_id:
298
+ job["status"] = "completed"
299
+ job["filename"] = filename
300
+ job["completed_at"] = datetime.now().isoformat()
301
+
302
+ # Peer ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
303
+ if "peer_id" in job:
304
+ with peer_lock:
305
+ if job["peer_id"] in peers:
306
+ peers[job["peer_id"]]["jobs_completed"] += 1
307
+ break
308
+
309
+ return "success"
310
+ except Exception as e:
311
+ print(f"Error saving result: {e}")
312
+ return "error"
313
 
314
+ def get_status():
315
+ """์‹œ์Šคํ…œ ์ƒํƒœ ์กฐํšŒ"""
316
+ with peer_lock:
317
+ active_peers = sum(1 for p in peers.values()
318
+ if (datetime.now() - p['last_seen']).seconds < 60)
319
 
320
+ with job_lock:
321
+ pending = sum(1 for j in jobs if j['status'] == 'pending')
322
+ completed = sum(1 for j in jobs if j['status'] == 'completed')
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
  status_text = f"""## ๐Ÿ“Š ์‹œ์Šคํ…œ ์ƒํƒœ
325
 
 
331
  ### ์ตœ๊ทผ ์ž‘์—… ๋ชฉ๋ก
332
  """
333
 
334
+ with job_lock:
335
+ recent_jobs = jobs[-5:][::-1] # Last 5 jobs, reversed
336
+ if recent_jobs:
337
+ for job in recent_jobs:
338
+ status_emoji = "โœ…" if job['status'] == 'completed' else "โณ" if job['status'] == 'pending' else "๐Ÿ”„"
339
+ status_text += f"\n{status_emoji} **{job['id'][:8]}**: {job['prompt'][:50]}... ({job['status']})"
340
+ else:
341
+ status_text += "\n์•„์ง ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค."
342
 
343
  return status_text
344
 
345
+ def get_gallery():
346
  """๊ฐค๋Ÿฌ๋ฆฌ์šฉ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ"""
347
  image_files = []
348
+ with job_lock:
349
+ for job in jobs[-20:][::-1]: # Last 20 jobs, newest first
350
+ if job['status'] == 'completed' and 'filename' in job:
351
+ file_path = f"results/{job['filename']}"
352
+ if os.path.exists(file_path):
353
+ image_files.append((file_path, job['prompt']))
354
 
355
  return image_files
356
 
357
+ def download_client_file(filename):
358
+ """ํด๋ผ์ด์–ธํŠธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ"""
359
+ file_path = f"client/{filename}"
360
+ if os.path.exists(file_path):
361
+ return file_path
362
+ return None
363
+
364
  # Custom CSS
365
  custom_css = """
366
  .container {
 
414
  """)
415
 
416
  submit_btn.click(
417
+ fn=submit_job,
418
  inputs=prompt_input,
419
  outputs=result_text
420
  )
 
427
  status_display = gr.Markdown()
428
  with gr.Row():
429
  refresh_btn = gr.Button("๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
 
430
 
431
  refresh_btn.click(
432
+ fn=get_status,
433
  outputs=status_display
434
  )
435
 
436
+ # Auto-refresh status on load
437
+ demo.load(fn=get_status, outputs=status_display)
438
 
439
  with gr.Tab("๐Ÿ–ผ๏ธ ๊ฐค๋Ÿฌ๋ฆฌ"):
440
  gr.Markdown("### ์ตœ๊ทผ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋“ค")
 
451
  refresh_gallery_btn = gr.Button("๐Ÿ”„ ๊ฐค๋Ÿฌ๋ฆฌ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
452
 
453
  refresh_gallery_btn.click(
454
+ fn=get_gallery,
455
  outputs=gallery
456
  )
457
 
458
  # Auto-load gallery
459
+ demo.load(fn=get_gallery, outputs=gallery)
460
 
461
  with gr.Tab("๐Ÿ’ป ํด๋ผ์ด์–ธํŠธ ๋‹ค์šด๋กœ๋“œ"):
462
  gr.Markdown("""
463
  ## Windows ํด๋ผ์ด์–ธํŠธ ์„ค์ • ๊ฐ€์ด๋“œ
464
 
465
  ### ๐Ÿ“ฅ 1๋‹จ๊ณ„: ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
466
+ ํด๋ผ์ด์–ธํŠธ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”:
467
+ """)
468
+
469
+ with gr.Row():
470
+ download_peer_btn = gr.Button("๐Ÿ“ฅ peer_agent.py ๋‹ค์šด๋กœ๋“œ")
471
+ download_req_btn = gr.Button("๐Ÿ“ฅ requirements.txt ๋‹ค์šด๋กœ๋“œ")
472
+ download_readme_btn = gr.Button("๐Ÿ“ฅ README.md ๋‹ค์šด๋กœ๋“œ")
473
+
474
+ download_output = gr.File(label="๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ", visible=False)
475
+
476
+ download_peer_btn.click(
477
+ fn=lambda: download_client_file("peer_agent.py"),
478
+ outputs=download_output
479
+ )
480
+ download_req_btn.click(
481
+ fn=lambda: download_client_file("requirements.txt"),
482
+ outputs=download_output
483
+ )
484
+ download_readme_btn.click(
485
+ fn=lambda: download_client_file("README.md"),
486
+ outputs=download_output
487
+ )
488
 
489
+ gr.Markdown("""
490
  ### ๐Ÿ 2๋‹จ๊ณ„: Python ์„ค์น˜
491
  - Python 3.8 ์ด์ƒ ๋ฒ„์ „ ํ•„์š”
492
  - [python.org](https://python.org)์—์„œ ๋‹ค์šด๋กœ๋“œ
 
512
  - CPU ํด๋ฐฑ ์ง€์›
513
  - ์ž๋™ ์ž‘์—… ์ฒ˜๋ฆฌ
514
  - 10์ดˆ๋งˆ๋‹ค ์ƒˆ ์ž‘์—… ํ™•์ธ
 
515
 
516
+ ### ๐ŸŽฎ GPU ์ง€์›
517
+ - NVIDIA GPU๊ฐ€ ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ํ™œ์šฉ
518
+ - GPU๊ฐ€ ์—†์–ด๋„ CPU๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
519
+ - GPU ์‚ฌ์šฉ๋ฅ  20% ๋ฏธ๋งŒ์ผ ๋•Œ๋งŒ ์ž‘์—… ์ˆ˜ํ–‰
520
+ """)
521
+
522
+ # Hidden API endpoints for client communication
523
+ gr.Interface(
524
+ fn=register_peer,
525
+ inputs=gr.Textbox(visible=False),
526
+ outputs=gr.Textbox(visible=False),
527
+ api_name="register_peer",
528
+ visible=False
529
+ )
530
+
531
+ gr.Interface(
532
+ fn=heartbeat,
533
+ inputs=gr.Textbox(visible=False),
534
+ outputs=gr.Textbox(visible=False),
535
+ api_name="heartbeat",
536
+ visible=False
537
+ )
538
+
539
+ gr.Interface(
540
+ fn=request_job,
541
+ inputs=gr.Textbox(visible=False),
542
+ outputs=gr.Textbox(visible=False),
543
+ api_name="request_job",
544
+ visible=False
545
+ )
546
+
547
+ gr.Interface(
548
+ fn=submit_result,
549
+ inputs=[gr.Textbox(visible=False), gr.Textbox(visible=False)],
550
+ outputs=gr.Textbox(visible=False),
551
+ api_name="submit_result",
552
+ visible=False
553
+ )
554
+
555
+ # Launch the app
556
  if __name__ == "__main__":
557
  demo.launch()