이정수/품질관리팀 commited on
Commit
79e1e8d
·
1 Parent(s): 6c97837

simplize'

Browse files
Files changed (1) hide show
  1. app.py +100 -471
app.py CHANGED
@@ -7,22 +7,16 @@ import time
7
  # 전역 변수로 서버 URL 저장
8
  SERVER_URL = ""
9
  # 타임아웃 설정 (초)
10
- REQUEST_TIMEOUT = 15 # 더 많은 시간으로 설정하면 오히려 프로세스가 걸릴 수 있음
11
 
12
- # 영구적인 HTTP 세션 생성
13
  session = requests.Session()
14
- # 연결 풀링 설정 및 자동 재시도 강화
15
  adapter = requests.adapters.HTTPAdapter(
16
  pool_connections=5,
17
  pool_maxsize=10,
18
  max_retries=requests.adapters.Retry(
19
- total=3, # 재시도 횟수
20
- backoff_factor=0.5, # 재시도 대기 시간 증가 계수
21
- status_forcelist=[429, 500, 502, 503, 504], # 재시도할 HTTP 상태 코드
22
- allowed_methods=["GET", "POST"], # 재시도할 HTTP 메서드
23
- connect=3, # 연결 오류에 대한 재시도 횟수
24
- read=3, # 읽기 오류에 대한 재시도 횟수
25
- redirect=3 # 리다이렉트 재시도 횟수
26
  )
27
  )
28
  session.mount('http://', adapter)
@@ -31,21 +25,12 @@ session.mount('https://', adapter)
31
  def connect_server(ngrok_url):
32
  """서버 연결 시도"""
33
  global SERVER_URL
34
-
35
  try:
36
- # 입력이 비어있는지 확인
37
- if not ngrok_url:
38
- return "서버 URL을 입력해주세요!"
39
-
40
- # 마지막 슬래시 제거
41
- if ngrok_url.endswith('/'):
42
- ngrok_url = ngrok_url[:-1]
43
-
44
- # 서버 상태 확인
45
  response = session.get(f"{ngrok_url}/health", timeout=REQUEST_TIMEOUT)
46
-
47
  if response.status_code == 200:
48
- SERVER_URL = ngrok_url # 전역 변수 업데이트
49
  return "서버 연결 성공!"
50
  else:
51
  return f"서버 응답 오류: HTTP {response.status_code}"
@@ -55,13 +40,9 @@ def connect_server(ngrok_url):
55
  def check_status():
56
  """서버 상태 확인"""
57
  global SERVER_URL
58
-
59
- if not SERVER_URL:
60
- return "서버 URL이 설정되지 않았습니다. 먼저 서버에 연결하세요."
61
-
62
  try:
63
  response = session.get(f"{SERVER_URL}/api/status", timeout=REQUEST_TIMEOUT)
64
-
65
  if response.status_code == 200:
66
  return json.dumps(response.json(), indent=2, ensure_ascii=False)
67
  else:
@@ -72,27 +53,11 @@ def check_status():
72
  def echo_test(message):
73
  """에코 테스트"""
74
  global SERVER_URL
75
-
76
- if not SERVER_URL:
77
- return "서버 URL이 설정되지 않았습니다. 먼저 서버에 연결하세요."
78
-
79
- if not message:
80
- return "메시지를 입력해주세요."
81
-
82
  try:
83
- payload = {
84
- "action": "echo",
85
- "data": {"message": message},
86
- "timestamp": int(time.time() * 1000)
87
- }
88
-
89
- response = session.post(
90
- f"{SERVER_URL}/api/send",
91
- json=payload,
92
- headers={"Content-Type": "application/json"},
93
- timeout=REQUEST_TIMEOUT
94
- )
95
-
96
  if response.status_code == 200:
97
  return json.dumps(response.json(), indent=2, ensure_ascii=False)
98
  else:
@@ -100,531 +65,198 @@ def echo_test(message):
100
  except RequestException as e:
101
  return f"에코 테스트 중 오류 발생: {str(e)}"
102
 
 
103
  def get_programs():
104
- """프로그램 목록 조회"""
105
  global SERVER_URL
106
-
107
- # 디버그 정보 기록
108
  print(f"[DEBUG] 프로그램 목록 조회 함수 시작")
 
109
 
110
  # 서버 URL이 없을 경우 테스트 데이터 사용
111
  if not SERVER_URL:
112
  print(f"[DEBUG] 서버 URL이 설정되지 않았습니다. 테스트 데이터를 사용합니다.")
113
- # 테스트 프로그램 데이터
114
  test_programs = [
115
  {"id": "test1", "name": "테스트 프로그램 1", "description": "테스트 설명 1", "path": "C:\\test\\program1.exe"},
116
  {"id": "test2", "name": "테스트 프로그램 2", "description": "테스트 설명 2", "path": "C:\\test\\program2.exe"}
117
  ]
118
-
119
- # HTML 생성: 드롭다운 대신 버튼 목록 사용
120
  html_output = "<div style='padding: 15px; background-color: #fffaeb; border-radius: 5px; border-left: 5px solid #ffcc00;'>"
121
- html_output += "<h3 style='margin-top: 0; color: #856404;'>서버 연결 필요</h3>"
122
- html_output += "<p>서버에 연결되지 않았습니다. 먼저 페이지 상단의 '서버 연결' 영역에서 ngrok URL을 력하고 연결하세요.</p>"
123
- html_output += "<p>* 테스트용 프로그램 목록:</p>"
124
-
125
- # 프로그램 목록을 버튼으로 표시
126
- html_output += "<div class='program-buttons' style='margin-top: 15px;'>"
127
  for program in test_programs:
128
  program_id = program.get("id", "")
129
  program_name = program.get("name", "Unknown Program")
130
  program_desc = program.get("description", "")
131
- # JavaScript onclick 이벤트로 프로그램 ID를 전달
132
- html_output += f"<button onclick='selectProgram(\"{program_id}\")' "
133
- html_output += f"style='margin: 5px; padding: 8px 15px; background-color: #4a6ee0; color: white; border: none; border-radius: 5px; cursor: pointer;'>"
134
- html_output += f"{program_name} - {program_desc}</button><br>"
135
- html_output += "</div>"
136
-
137
- # 버튼 클릭 시 JavaScript 함수를 통해 프로그램 ID를 표시하는 스크립트 추가
138
- html_output += "<script>"
139
- html_output += "function selectProgram(programId) {"
140
- html_output += " var resultDiv = document.getElementById('selected_program_result');"
141
- html_output += " resultDiv.innerHTML = '<div style=\"padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;\">'"
142
- html_output += " + '<h3 style=\"margin-top: 0; color: #155724;\">\uD504\uB85C\uADF8\uB7A8 \uC120\uD0DD\uB428</h3>'"
143
- html_output += " + '<p>\uC120\uD0DD\uD55C \uD504\uB85C\uADF8\uB7A8 ID: ' + programId + '</p>'"
144
- html_output += " + '<p>\uB9C1\uD06C\uB97C \uD074\uB9AD\uD558\uC5EC \uC2E4\uD589: <a href=\"#\" onclick=\"executeProgram(\\'' + programId + '\\')\">\uD504\uB85C\uADF8\uB7A8 \uC2E4\uD589</a></p>'"
145
- html_output += " + '</div>';"
146
- html_output += "};"
147
- html_output += "function executeProgram(programId) {"
148
- html_output += " var resultDiv = document.getElementById('selected_program_result');"
149
- html_output += " resultDiv.innerHTML = '<div style=\"padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;\">'"
150
- html_output += " + '<h3 style=\"margin-top: 0; color: #155724;\">\uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uC131\uACF5</h3>'"
151
- html_output += " + '<p>\uD14C\uC2A4\uD2B8 \uD504\uB85C\uADF8\uB7A8 ' + programId + '\uAC00 \uC2DC\uBBAC\uB808\uC774\uC158\uB418\uC5C8\uC2B5\uB2C8\uB2E4.</p>'"
152
- html_output += " + '<p><strong>\uC8FC\uC758:</strong> \uD604\uC7AC \uD14C\uC2A4\uD2B8 \uBAA8\uB4DC\uC785\uB2C8\uB2E4. \uC2E4\uC81C \uC11C\uBC84\uC5D0 \uC5F0\uACB0\uD558\uC5EC \uC2E4\uD589\uD558\uC2DC\uB824\uBA74 \uC11C\uBC84 URL\uC744 \uC785\uB825\uD558\uACE0 \uC5F0\uACB0\uD574\uC8FC\uC138\uC694.</p>'"
153
- html_output += " + '</div>';"
154
- html_output += "};"
155
- html_output += "</script>"
156
-
157
- # 선택된 프로그램 결과를 표시할 div 추가
158
- html_output += "<div id='selected_program_result' style='margin-top: 15px;'></div>"
159
- html_output += "</div>"
160
-
161
- # 테스트 드롭다운 옵션 생성
162
- program_options = []
163
- for program in test_programs:
164
- program_id = program.get("id", "")
165
- program_name = program.get("name", "Unknown Program")
166
- program_desc = program.get("description", "")
167
- program_options.append((f"{program_name} - {program_desc}", program_id))
168
-
169
- # 테스트 모드의 경우에는 드롭다운 옵션만 반환 (HTML은 위에서 생성)
170
- return html_output, program_options
171
 
172
  # 서버 URL이 있는 경우 API 호출
173
- print(f"[DEBUG] 서버 URL이 있습니다. API 호출 시도합니다: {SERVER_URL}/api/programs")
174
-
175
  try:
176
- # 헤더 추가
177
- headers = {
178
- "User-Agent": "LocalPCAgent-Client/1.0",
179
- "Accept": "application/json",
180
- "Cache-Control": "no-cache",
181
- "Connection": "keep-alive"
182
- }
183
-
184
- # API 요청 실행
185
- print(f"[DEBUG] API 요청 시도 - 타임아웃: {REQUEST_TIMEOUT}")
186
- response = session.get(
187
- f"{SERVER_URL}/api/programs",
188
- headers=headers,
189
- timeout=REQUEST_TIMEOUT
190
- )
191
-
192
  print(f"[DEBUG] API 응답 받음 - 상태코드: {response.status_code}")
193
 
194
  if response.status_code == 200:
195
  result_json = response.json()
196
  programs = result_json.get("programs", [])
197
-
198
- program_options = []
199
-
200
- # 프로그램 목록에서 드롭다운 옵션 생성 (Gradio와의 연동을 위해 필요함)
201
- for program in programs:
202
- program_id = program.get("id", "")
203
- program_name = program.get("name", "Unknown Program")
204
- program_desc = program.get("description", "")
205
- program_options.append((f"{program_name} - {program_desc}", program_id))
206
-
207
- # 디버그 정보 추가
208
- print(f"Found {len(program_options)} programs: {program_options}")
209
-
210
- # 예쁘게 출력하기 위한 HTML 생성
211
  html_output = "<div style='max-height: 400px; overflow-y: auto;'>"
212
-
213
- # 버튼 형식의 프로그램 목록 추가
214
- html_output += "<div class='program-buttons' style='margin-top: 15px; margin-bottom: 15px;'>"
215
- html_output += "<h4>프로그램 선택:</h4>"
216
  for program in programs:
217
  program_id = program.get("id", "")
218
  program_name = program.get("name", "Unknown Program")
219
  program_desc = program.get("description", "")
220
- html_output += f"<button onclick='selectProgram(\"{program_id}\")' "
221
- html_output += f"style='margin: 5px; padding: 8px 15px; background-color: #4a6ee0; color: white; border: none; border-radius: 5px; cursor: pointer;'>"
222
- html_output += f"{program_name}</button>"
223
- html_output += "</div>"
224
-
225
- # 선택된 프로그램 정보를 표시할 영역 추가
226
- html_output += "<div id='selected_program_result' style='margin-top: 15px; margin-bottom: 15px;'></div>"
227
-
228
- # JavaScript 기능 추가
229
- html_output += "<script>"
230
- html_output += "function selectProgram(programId) {"
231
- html_output += " console.log('프로그램 선택됨: ' + programId);"
232
- html_output += " // 선택된 프로그램 정보 표시"
233
- html_output += " var resultDiv = document.getElementById('selected_program_result');"
234
- html_output += " if (resultDiv) {"
235
- # 현재 프로그램 정보 찾기 (JavaScript 배열로 변환)
236
- programs_js = json.dumps(programs) # Python 리스트를 JSON 문자열로 변환
237
- html_output += f" var programs = {programs_js};\n"
238
- html_output += " var programName = 'Unknown';"
239
- html_output += " var programDesc = '';"
240
- html_output += " var programPath = '';"
241
- html_output += " for (var i = 0; i < programs.length; i++) {"
242
- html_output += " if (programs[i].id === programId) {"
243
- html_output += " programName = programs[i].name || 'Unknown';"
244
- html_output += " programDesc = programs[i].description || '';"
245
- html_output += " programPath = programs[i].path || '';"
246
- html_output += " break;"
247
- html_output += " }"
248
- html_output += " }"
249
- html_output += " resultDiv.innerHTML = '<div style=\"padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;\">'"
250
- html_output += " + '<h3 style=\"margin-top: 0; color: #155724;\">프로그램 선택됨</h3>'"
251
- html_output += " + '<p><strong>프로그램:</strong> ' + programName + '</p>'"
252
- html_output += " + '<p><strong>설명:</strong> ' + programDesc + '</p>'"
253
- html_output += " + '<p><strong>경로:</strong> ' + programPath + '</p>'"
254
- html_output += " + '<button onclick=\"executeSelectedProgram()\" style=\"margin-top: 10px; padding: 8px 15px; background-color: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer;\">프로그램 실행</button>'"
255
- html_output += " + '</div>';"
256
- html_output += " } else {"
257
- html_output += " console.error('결과를 표시할 요소를 찾을 수 없습니다.');"
258
- html_output += " }"
259
- html_output += " // 드롭다운 값 설정 (Gradio 연동)"
260
- html_output += " var programDropdown = document.querySelector('select[data-testid*=\"program_dropdown\"]');" # Gradio 4.x+ 대응 시 data-testid 확인 필요
261
- html_output += " if (programDropdown) {"
262
- html_output += " console.log('드롭다운을 찾음, 값 설정 시도: ' + programId);"
263
- html_output += " programDropdown.value = programId;"
264
- html_output += " // 변경 이벤트 발생시키기 (Gradio 4.x+ 에서는 더 복잡할 수 있음)"
265
- html_output += " var event = new Event('change', { 'bubbles': true });"
266
- html_output += " programDropdown.dispatchEvent(event);"
267
- html_output += " } else {"
268
- html_output += " console.error('프로그램 드롭다운을 찾을 수 없습니다');"
269
- html_output += " }"
270
- html_output += "};"
271
- html_output += "function executeSelectedProgram() {"
272
- html_output += " console.log('실행 버튼 클릭됨');"
273
- html_output += " // Gradio 실행 버튼 찾아서 클릭 시도 (여러 선택자 사용)"
274
- html_output += " var executeBtn = document.querySelector('button[data-testid*=\"execute_program_btn\"]');" # Gradio 4.x+ 대응 시 data-testid 확인 필요
275
- html_output += " if (!executeBtn) {"
276
- html_output += " executeBtn = Array.from(document.querySelectorAll('button.gr-button-primary')).find(el => el.textContent.includes('프로그램 실행'));" # Gradio 4.x+ 스타일 가정
277
- html_output += " }"
278
- html_output += " if (!executeBtn) {"
279
- html_output += " executeBtn = Array.from(document.querySelectorAll('button')).find(el => el.textContent.includes('프로그램 실행'));" # 일반 버튼 텍스트 검색
280
- html_output += " }"
281
- html_output += " if (executeBtn) {"
282
- html_output += " console.log('실행 버튼을 찾음, 클릭 시도');"
283
- html_output += " setTimeout(function() { executeBtn.click(); }, 300); // 클릭 타이밍 조정"
284
- html_output += " } else {"
285
- html_output += " console.error('실행 버튼을 찾을 수 없습니다');"
286
- html_output += " alert('프로그램이 선택되었습니다. Gradio 인터페이스의 드롭다운에서 현재 프로그램이 선택되어 있는지 확인한 후 프로그램 실행 버튼을 클릭해 주세요.');"
287
- html_output += " }"
288
- html_output += "};"
289
- html_output += "</script>"
290
-
291
- # 테이블 형식으로 프로그램 목록 표시
292
- html_output += "<table style='width: 100%; border-collapse: collapse;'>"
293
- html_output += "<tr style='background-color: #f2f2f2;'><th style='padding: 8px; text-align: left; border-bottom: 1px solid #ddd;'>이름</th><th style='padding: 8px; text-align: left; border-bottom: 1px solid #ddd;'>설명</th><th style='padding: 8px; text-align: left; border-bottom: 1px solid #ddd;'>경로</th></tr>"
294
-
295
- for program in programs:
296
  html_output += f"<tr style='border-bottom: 1px solid #ddd;'>"
297
- html_output += f"<td style='padding: 8px;'>{program.get('name', 'Unknown')}</td>"
298
- html_output += f"<td style='padding: 8px;'>{program.get('description', '')}</td>"
299
  html_output += f"<td style='padding: 8px;'>{program.get('path', '')}</td>"
300
  html_output += "</tr>"
301
-
302
  html_output += "</table></div>"
303
-
304
  html_output += f"<div style='margin-top: 10px; font-size: 0.9em; color: #666;'>총 {len(programs)}개 프로그램 발견</div>"
305
-
306
- # 원본 JSON 식으로 제공
307
- html_output += "<details style='margin-top: 10px;'>"
308
- html_output += "<summary style='cursor: pointer; color: #4a6ee0;'>원본 JSON 데이터 보기</summary>"
309
- html_output += f"<pre style='background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;'>{json.dumps(result_json, indent=2, ensure_ascii=False)}</pre>"
310
- html_output += "</details>"
311
-
312
- return html_output, program_options
313
  else:
314
- error_html = f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'>"
315
- error_html += f"<h3 style='margin-top: 0; color: #721c24;'>프로그램 목록 조회 실패</h3>"
316
- error_html += f"<p>HTTP 오류 {response.status_code}</p>"
317
- error_html += f"<p>{response.text}</p>"
318
- error_html += "</div>"
319
- return error_html, []
320
  except RequestException as e:
321
- # 오류 상세 로그
322
  print(f"[DEBUG] API 요청 오류: {str(e)}")
323
  error_type = type(e).__name__
324
  error_detail = str(e)
325
-
326
- # 테스트 드롭다운 데이터 생성 (오류 시에도 테스트 가능하게)
327
  test_programs = [
328
- {"id": "test1", "name": "테스트 프로그램 1", "description": "테스트 설명 1", "path": "C:\\test\\program1.exe"},
329
- {"id": "test2", "name": "테스트 프로그램 2", "description": "테스트 설명 2", "path": "C:\\test\\program2.exe"}
330
  ]
331
-
332
- # 드롭다운 옵션 생성
333
- program_options = []
334
- for program in test_programs:
335
- program_id = program.get("id", "")
336
- program_name = program.get("name", "Unknown Program")
337
- program_desc = program.get("description", "")
338
- program_options.append((f"{program_name} - {program_desc}", program_id))
339
-
340
- print(f"[DEBUG] 테스트 용 드롭다운 옵션 추가: {program_options}")
341
-
342
- # 테스트 데이터를 기반으로 영역에 버튼 추가
343
- test_html = "<div class='program-buttons' style='margin-top: 15px;'>"
344
- test_html += "<h4>테스트용 프로그램 선택:</h4>"
345
  for program in test_programs:
346
- program_id = program.get("id", "")
347
- program_name = program.get("name", "Unknown Program")
348
- program_desc = program.get("description", "")
349
- test_html += f"<button onclick='selectProgram(\"{program_id}\")' "
350
- test_html += f"style='margin: 5px; padding: 8px 15px; background-color: #4a6ee0; color: white; border: none; border-radius: 5px; cursor: pointer;'>"
351
- test_html += f"{program_name} - {program_desc}</button><br>"
352
- test_html += "</div>"
353
-
354
- # JavaScript 기능 추가
355
- test_html += "<script>"
356
- test_html += "function selectProgram(programId) {"
357
- test_html += " console.log('프로그램 선택됨: ' + programId);"
358
- test_html += " var resultDiv = document.getElementById('selected_program_result');"
359
- test_html += " if (resultDiv) {"
360
- test_html += " resultDiv.innerHTML = '<div style=\"padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;\">'"
361
- test_html += " + '<h3 style=\"margin-top: 0; color: #155724;\">\uD504\uB85C\uADF8\uB7A8 \uC120\uD0DD\uB428</h3>'"
362
- test_html += " + '<p>\uC120\uD0DD\uD55C \uD504\uB85C\uADF8\uB7A8 ID: ' + programId + '</p>'"
363
- test_html += " + '<p>\uB9C1\uD06C\uB97C \uD074\uB9AD\uD558\uC5EC \uC2E4\uD589: <a href=\"#\" onclick=\"executeProgram(\\'' + programId + '\\')\">\uD504\uB85C\uADF8\uB7A8 \uC2E4\uD589</a></p>'"
364
- test_html += " + '</div>';"
365
- test_html += " } else {"
366
- test_html += " console.error('결과를 표시할 요소를 찾을 수 없습니다.');"
367
- test_html += " }"
368
- test_html += " // 선택한 프로그램 ID를 드롭다운에도 설정 (Gradio와 연동)"
369
- test_html += " var programDropdown = document.querySelector('select[data-testid*=\"program_dropdown\"]');" # Gradio 4.x+ 대응 시 data-testid 확인 필요
370
- test_html += " if (programDropdown) {"
371
- test_html += " console.log('드롭다운을 찾음, 값 설정 시도: ' + programId);"
372
- test_html += " programDropdown.value = programId;"
373
- test_html += " // 변경 이벤트 발생시키기 (Gradio 4.x+ 에서는 더 복잡할 수 있음)"
374
- test_html += " var event = new Event('change', { 'bubbles': true });"
375
- test_html += " programDropdown.dispatchEvent(event);"
376
- test_html += " } else {"
377
- test_html += " console.error('프로그램 드롭다운을 찾을 수 없습니다');"
378
- test_html += " }"
379
- test_html += "};"
380
- test_html += "function executeProgram(programId) {"
381
- test_html += " console.log('프로그램 실행: ' + programId);"
382
- test_html += " var resultDiv = document.getElementById('selected_program_result');"
383
- test_html += " if (resultDiv) {"
384
- test_html += " resultDiv.innerHTML = '<div style=\"padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;\">'"
385
- test_html += " + '<h3 style=\"margin-top: 0; color: #155724;\">\uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uC131\uACF5</h3>'"
386
- test_html += " + '<p>\uD14C\uC2A4\uD2B8 \uD504\uB85C\uADF8\uB7A8 ' + programId + '\uAC00 \uC2DC\uBBAC\uB808\uC774\uC158\uB418\uC5C8\uC2B5\uB2C8\uB2E4.</p>'"
387
- test_html += " + '<p><strong>\uC8FC\uC758:</strong> \uD604\uC7AC \uD14C\uC2A4\uD2B8 \uBAA8\uB4DC\uC785\uB2C8\uB2E4. \uC2E4\uC81C \uC11C\uBC84\uC5D0 \uC5F0\uACB0\uD558\uC5EC \uC2E4\uD589\uD558\uC2DC\uB824\uBA74 \uC11C\uBC84 URL\uC744 \uC785\uB825\uD558\uACE0 \uC5F0\uACB0\uD574\uC8FC\uC138\uC694.</p>'"
388
- test_html += " + '</div>';"
389
- test_html += " } else {"
390
- test_html += " console.error('결과를 표시할 요소를 찾을 수 없습니다.');"
391
- test_html += " }"
392
- test_html += " // Gradio 인터페이스의 실행 버튼에 연동"
393
- test_html += " var executeBtn = document.querySelector('button[data-testid*=\"execute_program_btn\"]');" # Gradio 4.x+ 대응 시 data-testid 확인 필요
394
- test_html += " if (!executeBtn) {"
395
- test_html += " executeBtn = Array.from(document.querySelectorAll('button.gr-button-primary')).find(el => el.textContent.includes('프로그램 실행'));" # Gradio 4.x+ 스타일 가정
396
- test_html += " }"
397
- test_html += " if (!executeBtn) {"
398
- test_html += " executeBtn = Array.from(document.querySelectorAll('button')).find(el => el.textContent.includes('프로그램 실행'));" # 일반 버튼 텍스트 검색
399
- test_html += " }"
400
- test_html += " if (executeBtn) {"
401
- test_html += " console.log('실행 버튼을 찾음, 클릭 시도');"
402
- test_html += " setTimeout(function() { executeBtn.click(); }, 300);" # 클릭 타이밍 조정
403
- test_html += " } else {"
404
- test_html += " console.log('실행 버튼을 찾을 수 없습니다. 테스트 실행을 직접 진행합니다.');"
405
- test_html += " }"
406
- test_html += "};"
407
- test_html += "</script>"
408
 
409
- # 선택된 프로그램 결과를 div
410
- test_html += "<div id='selected_program_result' style='margin-top: 15px;'></div>"
411
-
412
- # 타임아웃일 경우 더 상세한 오류 메시지
413
- if "timeout" in error_detail.lower() or "timed out" in error_detail.lower():
414
- error_html = f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'>"
415
- error_html += f"<h3 style='margin-top: 0; color: #721c24;'>프로그램 목록 조회 타임아웃</h3>"
416
- error_html += f"<p>서버가 {REQUEST_TIMEOUT}초 내에 응답하지 않았습니다.</p>"
417
- error_html += f"<p><strong>해결 방법:</strong> 서버 연결을 다시 확인하고, ngrok을 다시 시작해보세요.</p>"
418
- error_html += f"<p><strong>오류 정보:</strong> {error_type} - {error_detail}</p>"
419
- error_html += f"<p style='margin-top: 15px;'><strong>테스트 모드:</strong> 임시 테스트용 데이터가 로드되었습니다.</p>"
420
- error_html += test_html # 테스트 HTML 포함
421
- error_html += "</div>"
422
- return error_html, program_options
423
- else:
424
- error_html = f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'>"
425
- error_html += f"<h3 style='margin-top: 0; color: #721c24;'>프로그램 목록 조회 중 오류 발생</h3>"
426
- error_html += f"<p>{str(e)}</p>"
427
- error_html += f"<p><strong>오류 유형:</strong> {error_type}</p>"
428
- error_html += f"<p><strong>해결 방법:</strong> 서버 연���을 확인하고 다시 시도해보세요.</p>"
429
- error_html += f"<p style='margin-top: 15px;'><strong>테스트 모드:</strong> 임시 테스트용 데이터가 로드되었습니다.</p>"
430
- error_html += test_html # 테스트 HTML 포함
431
- error_html += "</div>"
432
-
433
- return error_html, program_options
434
 
 
435
  def execute_program(program_id):
436
  """프로그램 실행"""
437
  global SERVER_URL
438
-
439
- # 디버그 정보 기록
440
  print(f"[DEBUG] 프로그램 실행 시도: {program_id}")
 
441
 
442
- if not program_id:
443
- return "프로그램을 선택해주세요."
444
-
445
- # 테스트 데이터 확인 (테스트 데이터의 경우 테스트 성공 반환)
446
- if program_id.startswith("test"):
447
  print(f"[DEBUG] 테스트 프로그램 확인됨. 테스트 모드로 성공 응답 생성")
448
- # 성공 메시지 HTML
449
- html_output = "<div style='padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;'>"
450
- html_output += f"<h3 style='margin-top: 0; color: #155724;'>테스트 실행 성공</h3>"
451
- html_output += f"<p style='margin-bottom: 0;'>테스트 프로그램 {program_id}가 시뮬레이션되었습니다.</p>"
452
- html_output += f"<p style='margin-top: 10px;'><strong>주의:</strong> 현재 테스트 모드입니다. 실제 서버에 연결하여 실행하시려면 서버 URL을 입력하고 연결해주세요.</p>"
453
- html_output += "</div>"
454
  return html_output
455
 
456
  if not SERVER_URL:
457
- # 서버 URL이 경우 오류 메시지 반환
458
- html_output = "<div style='padding: 15px; background-color: #fff3cd; border-radius: 5px; border-left: 5px solid #ffc107;'>"
459
- html_output += "<h3 style='margin-top: 0; color: #856404;'>서버 연결 필요</h3>"
460
- html_output += "프로그램을 실행하려면 먼저 서버에 연결해야 합니다. 페이지 상단의 '서버 연결' 영역에서 ngrok URL을 입력하고 연결하세요."
461
- html_output += "</div>"
462
- return html_output
463
 
464
- # 디버그 정보 기록
465
  print(f"[DEBUG] 서버에 실행 요청 전송: {SERVER_URL}/api/programs/{program_id}/execute")
466
-
467
  try:
468
- # 실행 요청
469
- headers = {
470
- "User-Agent": "LocalPCAgent-Client/1.0",
471
- "Accept": "application/json",
472
- "Content-Type": "application/json"
473
- }
474
-
475
- print(f"[DEBUG] API 요청 전송 - 타임아웃: {REQUEST_TIMEOUT}")
476
- response = session.post(
477
- f"{SERVER_URL}/api/programs/{program_id}/execute",
478
- headers=headers,
479
- json={"id": program_id}, # API가 JSON 본문을 예상할 수 있음
480
- timeout=REQUEST_TIMEOUT
481
- )
482
-
483
  print(f"[DEBUG] API 응답 받음 - 상태코드: {response.status_code}")
484
 
485
  if response.status_code == 200:
486
  result = response.json()
487
-
488
- # 성공 메시지 HTML로 표시
489
- html_output = "<div style='padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;'>"
490
- html_output += f"<h3 style='margin-top: 0; color: #155724;'>실행 성공</h3>"
491
- html_output += f"<p style='margin-bottom: 0;'>{result.get('message', '프로그램이 성공적으로 실행되었습니다.')}</p>"
492
- html_output += "</div>"
493
-
494
- # 원본 JSON도 접이식으로 제공
495
- html_output += "<details style='margin-top: 10px;'>"
496
- html_output += "<summary style='cursor: pointer; color: #4a6ee0;'>원본 JSON 데이터 보기</summary>"
497
- html_output += f"<pre style='background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;'>{json.dumps(result, indent=2, ensure_ascii=False)}</pre>"
498
- html_output += "</details>"
499
-
500
  return html_output
501
  else:
502
- # 오류 메시지 HTML로 표시
503
- html_output = "<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'>"
504
- html_output += f"<h3 style='margin-top: 0; color: #721c24;'>실행 실패</h3>"
505
- html_output += f"<p style='margin-bottom: 0;'>HTTP 오류 {response.status_code}</p>"
506
- html_output += f"<p style='margin-bottom: 0;'>{response.text}</p>"
507
- html_output += "</div>"
508
-
509
- return html_output
510
  except RequestException as e:
511
- # 오류 상세 로그
512
  print(f"[DEBUG] 프로그램 실행 중 오류: {str(e)}")
513
  error_type = type(e).__name__
514
  error_detail = str(e)
515
-
516
- # 타임아웃 오류 확인
517
  if "timeout" in error_detail.lower() or "timed out" in error_detail.lower():
518
- html_output = "<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'>"
519
- html_output += f"<h3 style='margin-top: 0; color: #721c24;'>프로그램 실행 타임아웃</h3>"
520
- html_output += f"<p style='margin-bottom: 10px;'>서버가 {REQUEST_TIMEOUT}초 내에 응답하지 않았습니다.</p>"
521
- html_output += f"<p><strong>해결 방법:</strong> 서버 연결을 확인하고 다시 시도해보세요. 서버가 다른 작업을 처리하고 있을 수 있습니다.</p>"
522
- html_output += f"<p><strong>오류 정보:</strong> {error_type} - {error_detail}</p>"
523
- html_output += "</div>"
524
  else:
525
- # 오류 메시지 HTML로 표시
526
- html_output = "<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'>"
527
- html_output += f"<h3 style='margin-top: 0; color: #721c24;'>오류 발생</h3>"
528
- html_output += f"<p style='margin-bottom: 10px;'>{str(e)}</p>"
529
- html_output += f"<p><strong>오류 유형:</strong> {error_type}</p>"
530
- html_output += "</div>"
531
 
532
- return html_output
533
 
534
- # UI 구성
535
  with gr.Blocks(title="LocalPCAgent 제어 인터페이스") as demo:
536
  gr.Markdown("# LocalPCAgent 제어 인터페이스")
537
  gr.Markdown("로컬 PC의 기능을 원격으로 제어하고 관리할 수 있는 웹 인터페이스입니다.")
538
 
539
- # 서버 연결 섹션
540
  with gr.Group():
541
  gr.Markdown("## 서버 연결")
542
  with gr.Row():
543
- ngrok_url_input = gr.Textbox(
544
- label="ngrok URL",
545
- placeholder="https://xxxx-xx-xx-xxx-xx.ngrok.io",
546
- value=""
547
- )
548
  connect_btn = gr.Button("연결")
549
-
550
  connection_status = gr.Textbox(label="연결 상태", interactive=False)
551
 
552
- # 작업 메뉴 탭
553
  with gr.Tabs() as tabs:
554
- # 1. 기본 기능 탭
555
  with gr.TabItem("기본 기능"):
556
  with gr.Group():
557
  gr.Markdown("### 서버 상태 확인")
558
  check_status_btn = gr.Button("상태 확인")
559
  status_result = gr.Textbox(label="결과", interactive=False, lines=10)
560
-
561
  with gr.Group():
562
  gr.Markdown("### 에코 테스트")
563
- echo_message = gr.Textbox(
564
- label="메시지",
565
- placeholder="테스트 메시지 입력",
566
- value="Hello from Hugging Face!"
567
- )
568
  echo_btn = gr.Button("에코 테스트")
569
  echo_result = gr.Textbox(label="결과", interactive=False, lines=10)
570
 
571
- # 2. 프로그램 실행 탭 (이전 '장치 관리' 탭 자리에 배치)
572
  with gr.TabItem("프로그램 실행"):
573
  with gr.Group():
574
- gr.Markdown("### 프로그램 목록 조회")
575
  with gr.Row():
576
  get_programs_btn = gr.Button("프로그램 목록 가져오기")
577
  gr.Markdown(f"<p style='color:#666; font-size:0.9em;'>이 작업은 최대 {REQUEST_TIMEOUT}초가 소요될 수 있습니다.</p>")
578
- # 결과 표시 영역 (HTML) 및 프로그램 선택 드롭다운을 위한 변수 선언
579
- programs_result = gr.HTML(label="결과") # HTML 결과를 표시할 컴포넌트
580
- program_dropdown = gr.Dropdown(label="프로그램 선택", choices=[], interactive=True, value=None, elem_id="program_dropdown") # 드롭다운 컴포넌
581
-
582
- with gr.Group():
583
- gr.Markdown("### 프로그램 실행")
584
- # 위에서 선언한 program_dropdown을 여기서 다시 참조하여 사용
585
- program_dropdown_exec = gr.Dropdown(label="실행할 프로그램 선택", choices=[], interactive=True, value=None, elem_id="program_dropdown_exec") # 별도 elem_id 부여
586
- execute_program_btn = gr.Button("프로그램 실행", variant="primary", elem_id="execute_program_btn") # elem_id 부여
587
- execute_result = gr.HTML(label="결과") # HTML 결과를 표시할 컴포넌트
588
- gr.Markdown("<small>* 프로그램 목록을 먼저 가져오고 선택한 후 실행하세요</small>")
589
 
590
 
591
- # 서버 연결 이벤트
592
- connect_btn.click(
593
- fn=connect_server,
594
- inputs=[ngrok_url_input],
595
- outputs=[connection_status]
596
- )
 
 
597
 
598
- # 기본 기능 이벤트
599
- check_status_btn.click(
600
- fn=check_status,
601
- inputs=None,
602
- outputs=[status_result]
603
- )
604
 
605
- echo_btn.click(
606
- fn=echo_test,
607
- inputs=[echo_message],
608
- outputs=[echo_result]
609
- )
610
 
611
- # 프로그램 실행 이벤트
612
  # '프로그램 목록 가져오기' 버튼 클릭 시:
613
- # 1. programs_result (HTML 결과 영역) 업데이트
614
- # 2. program_dropdown (목록 조회 섹션의 드롭다운) 업데이트
615
- # 3. program_dropdown_exec (프로그램 실행 섹션의 드롭다운)도 동일하게 업데이트
616
  get_programs_btn.click(
617
  fn=get_programs,
618
  inputs=None,
619
- outputs=[programs_result, program_dropdown, program_dropdown_exec] # 드롭다운 모두 업데이트
620
  )
621
 
622
- # 프로그램 실행 섹션의 드롭다운 값이 변경될 때 결과 영역에 메시지 표시
623
  program_dropdown_exec.change(
624
- fn=lambda x: print(f"[DEBUG] 실행할 프로그램 선택됨 (dropdown_exec): {x}") or (
625
- "<div style='padding: 15px; background-color: #e7f3ff; border-radius: 5px; border-left: 5px solid #007bff;'>"
626
- f"<p>실행할 프로그램으로 <strong>'{x}'</strong> 가 선택되었습니다.</p>"
627
- "<p>'프로그램 실행' 버튼을 클릭하여 진행하세요.</p>"
628
  "</div>" if x else ""
629
  ),
630
  inputs=[program_dropdown_exec],
@@ -634,17 +266,14 @@ with gr.Blocks(title="LocalPCAgent 제어 인터페이스") as demo:
634
  # '프로그램 실행' 버튼 클릭 시
635
  execute_program_btn.click(
636
  fn=execute_program,
637
- inputs=[program_dropdown_exec], # 실행 섹션의 드롭다운 값을 사용
638
- outputs=[execute_result]
639
  )
640
 
641
  # 앱 실행
642
  if __name__ == "__main__":
643
  print("=== LocalPCAgent 웹 인터페이스 시작 ===\n")
644
- print("* 장치 관리 기능이 제거되었습니다.")
645
- print("* 다양한 오류 디버깅 위해 로그를 자세히 출력합니다.")
646
- print("* 구현된 테스트 기능을 통해 드롭다운 작동 여부를 확인합니다.\n")
647
-
648
- # 디버그 모드로 실행하여 오류 추적이 쉽도록 함
649
  demo.launch(debug=True, show_error=True)
650
  print("\n=== 웹 인터페이스 종료 ===")
 
7
  # 전역 변수로 서버 URL 저장
8
  SERVER_URL = ""
9
  # 타임아웃 설정 (초)
10
+ REQUEST_TIMEOUT = 15
11
 
12
+ # 영구적인 HTTP 세션 생성 및 재시도 설정 (이전과 동일)
13
  session = requests.Session()
 
14
  adapter = requests.adapters.HTTPAdapter(
15
  pool_connections=5,
16
  pool_maxsize=10,
17
  max_retries=requests.adapters.Retry(
18
+ total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504],
19
+ allowed_methods=["GET", "POST"], connect=3, read=3, redirect=3
 
 
 
 
 
20
  )
21
  )
22
  session.mount('http://', adapter)
 
25
  def connect_server(ngrok_url):
26
  """서버 연결 시도"""
27
  global SERVER_URL
 
28
  try:
29
+ if not ngrok_url: return "서버 URL을 입력해주세요!"
30
+ if ngrok_url.endswith('/'): ngrok_url = ngrok_url[:-1]
 
 
 
 
 
 
 
31
  response = session.get(f"{ngrok_url}/health", timeout=REQUEST_TIMEOUT)
 
32
  if response.status_code == 200:
33
+ SERVER_URL = ngrok_url
34
  return "서버 연결 성공!"
35
  else:
36
  return f"서버 응답 오류: HTTP {response.status_code}"
 
40
  def check_status():
41
  """서버 상태 확인"""
42
  global SERVER_URL
43
+ if not SERVER_URL: return "서버 URL이 설정되지 않았습니다. 먼저 서버에 연결하세요."
 
 
 
44
  try:
45
  response = session.get(f"{SERVER_URL}/api/status", timeout=REQUEST_TIMEOUT)
 
46
  if response.status_code == 200:
47
  return json.dumps(response.json(), indent=2, ensure_ascii=False)
48
  else:
 
53
  def echo_test(message):
54
  """에코 테스트"""
55
  global SERVER_URL
56
+ if not SERVER_URL: return "서버 URL이 설정되지 않았습니다. 먼저 서버에 연결하세요."
57
+ if not message: return "메시지를 입력해주세요."
 
 
 
 
 
58
  try:
59
+ payload = {"action": "echo", "data": {"message": message}, "timestamp": int(time.time() * 1000)}
60
+ response = session.post(f"{SERVER_URL}/api/send", json=payload, headers={"Content-Type": "application/json"}, timeout=REQUEST_TIMEOUT)
 
 
 
 
 
 
 
 
 
 
 
61
  if response.status_code == 200:
62
  return json.dumps(response.json(), indent=2, ensure_ascii=False)
63
  else:
 
65
  except RequestException as e:
66
  return f"에코 테스트 중 오류 발생: {str(e)}"
67
 
68
+ # --- get_programs 함수 수정 ---
69
  def get_programs():
70
+ """프로그램 목록 조회 (JavaScript 제거 및 Gradio 업데이트 방식 적용)"""
71
  global SERVER_URL
 
 
72
  print(f"[DEBUG] 프로그램 목록 조회 함수 시작")
73
+ program_options = [] # 드롭다운 선택지 리스트 초기화
74
 
75
  # 서버 URL이 없을 경우 테스트 데이터 사용
76
  if not SERVER_URL:
77
  print(f"[DEBUG] 서버 URL이 설정되지 않았습니다. 테스트 데이터를 사용합니다.")
 
78
  test_programs = [
79
  {"id": "test1", "name": "테스트 프로그램 1", "description": "테스트 설명 1", "path": "C:\\test\\program1.exe"},
80
  {"id": "test2", "name": "테스트 프로그램 2", "description": "테스트 설명 2", "path": "C:\\test\\program2.exe"}
81
  ]
82
+ # HTML 생성 (JavaScript 없이 정보만 표시)
 
83
  html_output = "<div style='padding: 15px; background-color: #fffaeb; border-radius: 5px; border-left: 5px solid #ffcc00;'>"
84
+ html_output += "<h3 style='margin-top: 0; color: #856404;'>서버 연결 필요 (테스트 모드)</h3>"
85
+ html_output += "<p>서버에 연결되지 않았습니다. 아래는 테스트용 프로그램 목록니다.</p>"
86
+ html_output += "<table style='width: 100%; border-collapse: collapse; margin-top: 10px;'>"
87
+ html_output += "<tr style='background-color: #f2f2f2;'><th style='padding: 8px; text-align: left;'>이름</th><th style='padding: 8px; text-align: left;'>설명</th></tr>"
 
 
88
  for program in test_programs:
89
  program_id = program.get("id", "")
90
  program_name = program.get("name", "Unknown Program")
91
  program_desc = program.get("description", "")
92
+ # 드롭다운 옵션 생성
93
+ program_options.append((f"{program_name} ({program_desc})", program_id))
94
+ html_output += f"<tr><td style='padding: 8px;'>{program_name}</td><td style='padding: 8px;'>{program_desc}</td></tr>"
95
+ html_output += "</table></div>"
96
+ print(f"[DEBUG] 테스트 프로그램 옵션: {program_options}")
97
+ # HTML 결과와 함께 드롭다운 업데이트 객체 반환
98
+ return html_output, gr.Dropdown.update(choices=program_options), gr.Dropdown.update(choices=program_options)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  # 서버 URL이 있는 경우 API 호출
101
+ print(f"[DEBUG] API 호출 시도: {SERVER_URL}/api/programs")
 
102
  try:
103
+ headers = {"User-Agent": "LocalPCAgent-Client/1.0", "Accept": "application/json", "Cache-Control": "no-cache", "Connection": "keep-alive"}
104
+ response = session.get(f"{SERVER_URL}/api/programs", headers=headers, timeout=REQUEST_TIMEOUT)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  print(f"[DEBUG] API 응답 받음 - 상태코드: {response.status_code}")
106
 
107
  if response.status_code == 200:
108
  result_json = response.json()
109
  programs = result_json.get("programs", [])
110
+ # HTML 생성 (테이블 형식, JavaScript 없음)
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  html_output = "<div style='max-height: 400px; overflow-y: auto;'>"
112
+ html_output += "<table style='width: 100%; border-collapse: collapse;'>"
113
+ html_output += "<tr style='background-color: #f2f2f2;'><th style='padding: 8px; text-align: left;'>이름</th><th style='padding: 8px; text-align: left;'>설명</th><th style='padding: 8px; text-align: left;'>경로</th></tr>"
 
 
114
  for program in programs:
115
  program_id = program.get("id", "")
116
  program_name = program.get("name", "Unknown Program")
117
  program_desc = program.get("description", "")
118
+ # 드롭다운 옵션 생성
119
+ program_options.append((f"{program_name} ({program_desc})", program_id))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  html_output += f"<tr style='border-bottom: 1px solid #ddd;'>"
121
+ html_output += f"<td style='padding: 8px;'>{program_name}</td>"
122
+ html_output += f"<td style='padding: 8px;'>{program_desc}</td>"
123
  html_output += f"<td style='padding: 8px;'>{program.get('path', '')}</td>"
124
  html_output += "</tr>"
 
125
  html_output += "</table></div>"
 
126
  html_output += f"<div style='margin-top: 10px; font-size: 0.9em; color: #666;'>총 {len(programs)}개 프로그램 발견</div>"
127
+ # 원본 JSON 보기 추가
128
+ html_output += "<details style='margin-top: 10px;'><summary style='cursor: pointer; color: #4a6ee0;'>원본 JSON 보기</summary>"
129
+ html_output += f"<pre style='background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;'>{json.dumps(result_json, indent=2, ensure_ascii=False)}</pre></details>"
130
+ print(f"[DEBUG] 발견된 프로그램 옵션: {program_options}")
131
+ # HTML 결과와 함께 드롭다운 업데이트 객체 반환
132
+ return html_output, gr.Dropdown.update(choices=program_options), gr.Dropdown.update(choices=program_options)
 
 
133
  else:
134
+ error_html = f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'><h3 style='margin-top: 0; color: #721c24;'>프로그램 목록 조회 실패</h3><p>HTTP 오류 {response.status_code}</p><p>{response.text}</p></div>"
135
+ # 오류 발생 드롭다운 업데이트 반환
136
+ return error_html, gr.Dropdown.update(choices=[]), gr.Dropdown.update(choices=[])
 
 
 
137
  except RequestException as e:
 
138
  print(f"[DEBUG] API 요청 오류: {str(e)}")
139
  error_type = type(e).__name__
140
  error_detail = str(e)
141
+ # 테스트용 데이터 생성 (오류 발생 시)
 
142
  test_programs = [
143
+ {"id": "test1", "name": "오류-테스트1", "description": "오류 발생 시 테스트", "path": ""},
144
+ {"id": "test2", "name": "오류-테스트2", "description": "오류 발생 시 테스트", "path": ""}
145
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  for program in test_programs:
147
+ program_options.append((f"{program['name']} ({program['description']})", program['id']))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
+ error_html = f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'><h3 style='margin-top: 0; color: #721c24;'>프로그램 목록 조회 중 오류 발생</h3><p>{str(e)}</p><p><strong>오류 유형:</strong> {error_type}</p><p style='margin-top: 15px;'><strong>테스트 모드:</strong> 임테스트용 데이터 드롭다운에 로드되었습니다.</p></div>"
150
+ print(f"[DEBUG] 오류 테스트 옵션: {program_options}")
151
+ # HTML 오류 메시지와 함께 테스트용 드롭다운 업데이트 객체 반환
152
+ return error_html, gr.Dropdown.update(choices=program_options), gr.Dropdown.update(choices=program_options)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
+ # --- execute_program 함수는 이전과 거의 동일 ---
155
  def execute_program(program_id):
156
  """프로그램 실행"""
157
  global SERVER_URL
 
 
158
  print(f"[DEBUG] 프로그램 실행 시도: {program_id}")
159
+ if not program_id: return "프로그램을 선택해주세요."
160
 
161
+ # 테스트 데이터 처리
162
+ if program_id.startswith("test") or program_id.startswith("오류-테스트"):
 
 
 
163
  print(f"[DEBUG] 테스트 프로그램 확인됨. 테스트 모드로 성공 응답 생성")
164
+ html_output = "<div style='padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;'><h3 style='margin-top: 0; color: #155724;'>테스트 실행 성공</h3><p style='margin-bottom: 0;'>테스트 프그램 {program_id}가 뮬레이션되었습니다.</p><p style='margin-top: 10px;'><strong>주의:</strong> 실제 서버에 연결된 상태가 아닙니다.</p></div>"
 
 
 
 
 
165
  return html_output
166
 
167
  if not SERVER_URL:
168
+ return "<div style='padding: 15px; background-color: #fff3cd; border-radius: 5px; border-left: 5px solid #ffc107;'><h3 style='margin-top: 0; color: #856404;'>서버 연결 필요</h3><p>프로그램실행하려면 먼저 서버에 연결해야 합니다.</p></div>"
 
 
 
 
 
169
 
 
170
  print(f"[DEBUG] 서버에 실행 요청 전송: {SERVER_URL}/api/programs/{program_id}/execute")
 
171
  try:
172
+ headers = {"User-Agent": "LocalPCAgent-Client/1.0", "Accept": "application/json", "Content-Type": "application/json"}
173
+ response = session.post(f"{SERVER_URL}/api/programs/{program_id}/execute", headers=headers, json={"id": program_id}, timeout=REQUEST_TIMEOUT)
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  print(f"[DEBUG] API 응답 받음 - 상태코드: {response.status_code}")
175
 
176
  if response.status_code == 200:
177
  result = response.json()
178
+ html_output = "<div style='padding: 15px; background-color: #d4edda; border-radius: 5px; border-left: 5px solid #28a745;'><h3 style='margin-top: 0; color: #155724;'>실행 성공</h3><p style='margin-bottom: 0;'>{result.get('message', '프로그램이 성공적으로 실행되었습니다.')}</p></div>"
179
+ html_output += "<details style='margin-top: 10px;'><summary style='cursor: pointer; color: #4a6ee0;'>원본 JSON 데이터 보기</summary>"
180
+ html_output += f"<pre style='background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;'>{json.dumps(result, indent=2, ensure_ascii=False)}</pre></details>"
 
 
 
 
 
 
 
 
 
 
181
  return html_output
182
  else:
183
+ return f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'><h3 style='margin-top: 0; color: #721c24;'>실행 실패</h3><p>HTTP 오류 {response.status_code}</p><p>{response.text}</p></div>"
 
 
 
 
 
 
 
184
  except RequestException as e:
 
185
  print(f"[DEBUG] 프로그램 실행 중 오류: {str(e)}")
186
  error_type = type(e).__name__
187
  error_detail = str(e)
188
+ # 타임아웃 또는 일반 오류 메시지 생성 (이전과 유사)
 
189
  if "timeout" in error_detail.lower() or "timed out" in error_detail.lower():
190
+ return f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'><h3 style='margin-top: 0; color: #721c24;'>프로그램 실행 타임아웃</h3><p>서버가 {REQUEST_TIMEOUT}초 내에 응답하지 않았습니다.</p><p><strong>오류 정보:</strong> {error_type} - {error_detail}</p></div>"
 
 
 
 
 
191
  else:
192
+ return f"<div style='padding: 15px; background-color: #f8d7da; border-radius: 5px; border-left: 5px solid #dc3545;'><h3 style='margin-top: 0; color: #721c24;'>오류 발생</h3><p>{str(e)}</p><p><strong>오류 유형:</strong> {error_type}</p></div>"
 
 
 
 
 
193
 
 
194
 
195
+ # --- UI 구성 수정 ---
196
  with gr.Blocks(title="LocalPCAgent 제어 인터페이스") as demo:
197
  gr.Markdown("# LocalPCAgent 제어 인터페이스")
198
  gr.Markdown("로컬 PC의 기능을 원격으로 제어하고 관리할 수 있는 웹 인터페이스입니다.")
199
 
 
200
  with gr.Group():
201
  gr.Markdown("## 서버 연결")
202
  with gr.Row():
203
+ ngrok_url_input = gr.Textbox(label="ngrok URL", placeholder="https://xxxx-xx-xx-xxx-xx.ngrok.io", value="")
 
 
 
 
204
  connect_btn = gr.Button("연결")
 
205
  connection_status = gr.Textbox(label="연결 상태", interactive=False)
206
 
 
207
  with gr.Tabs() as tabs:
 
208
  with gr.TabItem("기본 기능"):
209
  with gr.Group():
210
  gr.Markdown("### 서버 상태 확인")
211
  check_status_btn = gr.Button("상태 확인")
212
  status_result = gr.Textbox(label="결과", interactive=False, lines=10)
 
213
  with gr.Group():
214
  gr.Markdown("### 에코 테스트")
215
+ echo_message = gr.Textbox(label="메시지", placeholder="테스트 메시지 입력", value="Hello from Gradio!")
 
 
 
 
216
  echo_btn = gr.Button("에코 테스트")
217
  echo_result = gr.Textbox(label="결과", interactive=False, lines=10)
218
 
 
219
  with gr.TabItem("프로그램 실행"):
220
  with gr.Group():
221
+ gr.Markdown("### 1. 프로그램 목록 조회")
222
  with gr.Row():
223
  get_programs_btn = gr.Button("프로그램 목록 가져오기")
224
  gr.Markdown(f"<p style='color:#666; font-size:0.9em;'>이 작업은 최대 {REQUEST_TIMEOUT}초가 소요될 수 있습니다.</p>")
225
+ # HTML 결과 표시 영역
226
+ programs_result = gr.HTML(label="프로그램 목록 결과")
227
+ # 임시 드롭다운 (선택 표시용, UI상에선 크게 의미 없음) - 업데이 대상 O
228
+ program_dropdown_display = gr.Dropdown(label="조회된 프로그램 (참고용)", choices=[], interactive=False)
 
 
 
 
 
 
 
229
 
230
 
231
+ with gr.Group():
232
+ gr.Markdown("### 2. 프로그램 실행")
233
+ # 실제 사용자가 선택할 드롭다운 - 업데이트 대상 O
234
+ program_dropdown_exec = gr.Dropdown(label="실행할 프로그램 선택", choices=[], interactive=True, value=None)
235
+ execute_program_btn = gr.Button("프로그램 실행", variant="primary")
236
+ # 실행 결과 표시 영역
237
+ execute_result = gr.HTML(label="실행 결과")
238
+ gr.Markdown("<small>* 위에서 '프로그램 목록 가져오기'를 먼저 실행한 후, 여기서 프로그램을 선택하고 실행하세요.</small>")
239
 
 
 
 
 
 
 
240
 
241
+ # --- 이벤트 핸들러 수정 ---
242
+ connect_btn.click(fn=connect_server, inputs=[ngrok_url_input], outputs=[connection_status])
243
+ check_status_btn.click(fn=check_status, inputs=None, outputs=[status_result])
244
+ echo_btn.click(fn=echo_test, inputs=[echo_message], outputs=[echo_result])
 
245
 
 
246
  # '프로그램 목록 가져오기' 버튼 클릭 시:
247
+ # get_programs 함수는 (html_output, dropdown_update1, dropdown_update2) 반환
248
+ # outputs 리스트의 순서에 맞게 ���포넌트가 업데이트
 
249
  get_programs_btn.click(
250
  fn=get_programs,
251
  inputs=None,
252
+ outputs=[programs_result, program_dropdown_display, program_dropdown_exec] # HTML 결과, 참고용 드롭다운, 실행용 드롭다운 순서로 업데이트
253
  )
254
 
255
+ # 실행할 프로그램 드롭다운 값이 변경될 때 실행 결과 영역에 메시지 표시 (단순 확인용)
256
  program_dropdown_exec.change(
257
+ fn=lambda x: print(f"[DEBUG] 실행할 프로그램 선택됨: {x}") or (
258
+ "<div style='padding: 10px; background-color: #e7f3ff; border-radius: 5px; border-left: 5px solid #007bff; font-size: 0.9em;'>"
259
+ f"<p>실행할 프로그램으로 <strong>'{x}'</strong> 가 선택되었습니다. 하단의 '프로그램 실행' 버튼을 클릭하세요.</p>"
 
260
  "</div>" if x else ""
261
  ),
262
  inputs=[program_dropdown_exec],
 
266
  # '프로그램 실행' 버튼 클릭 시
267
  execute_program_btn.click(
268
  fn=execute_program,
269
+ inputs=[program_dropdown_exec], # 사용자가 선택한 드롭다운 값을 입력으로 사용
270
+ outputs=[execute_result] # 실행 결과 영역 업데이트
271
  )
272
 
273
  # 앱 실행
274
  if __name__ == "__main__":
275
  print("=== LocalPCAgent 웹 인터페이스 시작 ===\n")
276
+ print("* UI 업데 로직 수정됨 (JavaScript 제거, Gradio 표준 방식 적용).")
277
+ print("* 디버깅 로그를 통해 작동 상태를 확인하세요.\n")
 
 
 
278
  demo.launch(debug=True, show_error=True)
279
  print("\n=== 웹 인터페이스 종료 ===")