openfree commited on
Commit
4e3c423
·
verified ·
1 Parent(s): 356a23a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +8 -1300
app.py CHANGED
@@ -41,1307 +41,15 @@ import docx
41
  import io
42
  import mimetypes
43
 
44
- # 파일 처리를 위한 새로운 클래스 정의
45
- class FileProcessor:
46
- ALLOWED_EXTENSIONS = {
47
- 'image': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'],
48
- 'text': ['.txt', '.pdf', '.docx', '.rtf'],
49
- }
50
- MAX_FILE_SIZE = 50 * 1024 * 1024 # 50MB
51
- MAX_FILES = 5
52
 
53
- @staticmethod
54
- def is_allowed_file(filename):
55
- ext = os.path.splitext(filename)[1].lower()
56
- allowed = []
57
- for extensions in FileProcessor.ALLOWED_EXTENSIONS.values():
58
- allowed.extend(extensions)
59
- return ext in allowed
60
 
61
- @staticmethod
62
- def get_file_type(filename):
63
- ext = os.path.splitext(filename)[1].lower()
64
- for file_type, extensions in FileProcessor.ALLOWED_EXTENSIONS.items():
65
- if ext in extensions:
66
- return file_type
67
- return None
68
-
69
- @staticmethod
70
- async def process_file(file) -> str:
71
- try:
72
- file_type = FileProcessor.get_file_type(file.name)
73
-
74
- if file_type == 'image':
75
- return await FileProcessor.process_image(file)
76
- elif file_type == 'text':
77
- return await FileProcessor.process_text(file)
78
- else:
79
- return "Unsupported file type"
80
-
81
- except Exception as e:
82
- return f"Error processing file: {str(e)}"
83
-
84
- @staticmethod
85
- async def process_image(file):
86
- try:
87
- image = Image.open(file)
88
-
89
- # Claude API를 통한 이미지 분석
90
- image_bytes = io.BytesIO()
91
- image.save(image_bytes, format=image.format)
92
- image_base64 = base64.b64encode(image_bytes.getvalue()).decode('utf-8')
93
-
94
- messages = [
95
- {
96
- "role": "user",
97
- "content": [
98
- {
99
- "type": "image",
100
- "source": {
101
- "type": "base64",
102
- "media_type": f"image/{image.format.lower()}",
103
- "data": image_base64
104
- }
105
- },
106
- {
107
- "type": "text",
108
- "text": "Please analyze this image and provide a detailed description."
109
- }
110
- ]
111
- }
112
- ]
113
-
114
- response = claude_client.messages.create(
115
- model="claude-3-opus-20240229",
116
- max_tokens=1000,
117
- messages=messages
118
- )
119
-
120
- return response.content[0].text
121
-
122
- except Exception as e:
123
- return f"Error processing image: {str(e)}"
124
-
125
- @staticmethod
126
- async def process_text(file):
127
- try:
128
- text_content = ""
129
- ext = os.path.splitext(file.name)[1].lower()
130
-
131
- if ext == '.pdf':
132
- pdf = fitz.open(stream=file.read(), filetype="pdf")
133
- for page in pdf:
134
- text_content += page.get_text()
135
- elif ext == '.docx':
136
- doc = docx.Document(file)
137
- text_content = "\n".join([paragraph.text for paragraph in doc.paragraphs])
138
- else: # .txt, .rtf
139
- text_content = file.read().decode('utf-8')
140
-
141
- # Claude API를 통한 텍스트 분석
142
- response = claude_client.messages.create(
143
- model="claude-3-opus-20240229",
144
- max_tokens=1000,
145
- messages=[
146
- {
147
- "role": "user",
148
- "content": f"Please analyze this text and provide a summary and key insights:\n\n{text_content[:2000]}..."
149
- }
150
- ]
151
- )
152
-
153
- return response.content[0].text
154
-
155
- except Exception as e:
156
- return f"Error processing text file: {str(e)}"
157
-
158
- # 캐시 경로 설정
159
- cache_path = path.join(path.dirname(path.abspath(__file__)), "models")
160
- os.environ["TRANSFORMERS_CACHE"] = cache_path
161
- os.environ["HF_HUB_CACHE"] = cache_path
162
- os.environ["HF_HOME"] = cache_path
163
-
164
- # Hugging Face 토큰 설정
165
- HF_TOKEN = os.getenv("HF_TOKEN")
166
- if not HF_TOKEN:
167
- print("Warning: HF_TOKEN not found in environment variables")
168
-
169
- # FLUX 모델 초기화
170
- if not path.exists(cache_path):
171
- os.makedirs(cache_path, exist_ok=True)
172
-
173
- try:
174
- pipe = FluxPipeline.from_pretrained(
175
- "black-forest-labs/FLUX.1-dev",
176
- torch_dtype=torch.bfloat16,
177
- use_auth_token=HF_TOKEN # Hugging Face 토큰 추가
178
- )
179
- pipe.load_lora_weights(
180
- hf_hub_download(
181
- "ByteDance/Hyper-SD",
182
- "Hyper-FLUX.1-dev-8steps-lora.safetensors",
183
- token=HF_TOKEN # Hugging Face 토큰 추가
184
- )
185
- )
186
- pipe.fuse_lora(lora_scale=0.125)
187
- pipe.to(device="cuda", dtype=torch.bfloat16)
188
- print("Successfully initialized FLUX model with authentication")
189
- except Exception as e:
190
- print(f"Error initializing FLUX model: {str(e)}")
191
- pipe = None
192
-
193
- # 이미지 생성 함수 추가
194
- @spaces.GPU
195
- def generate_image(prompt, height=512, width=512, steps=8, scales=3.5, seed=3413):
196
- with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16):
197
- return pipe(
198
- prompt=[prompt],
199
- generator=torch.Generator().manual_seed(int(seed)),
200
- num_inference_steps=int(steps),
201
- guidance_scale=float(scales),
202
- height=int(height),
203
- width=int(width),
204
- max_sequence_length=256
205
- ).images[0]
206
-
207
- # SystemPrompt 부분을 직접 정의
208
- SystemPrompt = """You are 'MOUSE-I', an advanced AI visualization expert. Your mission is to transform every response into a visually stunning and highly informative presentation.
209
-
210
- Core Capabilities:
211
- - Transform text responses into rich visual experiences
212
- - Create interactive data visualizations and charts
213
- - Design beautiful and intuitive user interfaces
214
- - Utilize engaging animations and transitions
215
- - Present information in a clear, structured manner
216
-
217
- Visual Elements to Include:
218
- - Charts & Graphs (using Chart.js, D3.js)
219
- - Interactive Data Visualizations
220
- - Modern UI Components
221
- - Engaging Animations
222
- - Informative Icons & Emojis
223
- - Color-coded Information Blocks
224
- - Progress Indicators
225
- - Timeline Visualizations
226
- - Statistical Representations
227
- - Comparison Tables
228
-
229
- Technical Requirements:
230
- - Modern HTML5/CSS3/JavaScript
231
- - Responsive Design
232
- - Interactive Elements
233
- - Clean Typography
234
- - Professional Color Schemes
235
- - Smooth Animations
236
- - Cross-browser Compatibility
237
-
238
- Libraries Available:
239
- - Chart.js for Data Visualization
240
- - D3.js for Complex Graphics
241
- - Bootstrap for Layout
242
- - jQuery for Interactions
243
- - Three.js for 3D Elements
244
-
245
- Design Principles:
246
- - Visual Hierarchy
247
- - Clear Information Flow
248
- - Consistent Styling
249
- - Intuitive Navigation
250
- - Engaging User Experience
251
- - Accessibility Compliance
252
-
253
- Remember to:
254
- - Present data in the most visually appealing way
255
- - Use appropriate charts for different data types
256
- - Include interactive elements where relevant
257
- - Maintain a professional and modern aesthetic
258
- - Ensure responsive design for all devices
259
-
260
- Return only HTML code wrapped in code blocks, focusing on creating visually stunning and informative presentations.
261
- """
262
-
263
- from config import DEMO_LIST
264
-
265
- class Role:
266
- SYSTEM = "system"
267
- USER = "user"
268
- ASSISTANT = "assistant"
269
-
270
- History = List[Tuple[str, str]]
271
- Messages = List[Dict[str, str]]
272
-
273
- # 이미지 캐시를 메모리에 저장
274
- IMAGE_CACHE = {}
275
-
276
- # boost_prompt 함수와 handle_boost 함수를 추가합니다
277
- def boost_prompt(prompt: str) -> str:
278
- if not prompt:
279
- return ""
280
-
281
- # 증강을 위한 시스템 프롬프트
282
- boost_system_prompt = """
283
- 당신은 웹 개발 프롬프트 전문가입니다.
284
- 주어진 프롬프트를 분석하여 더 상세하고 전문적인 요구사항으로 확장하되,
285
- 원래 의도와 목적은 그대로 유지하면서 다음 관점들을 고려하여 증강하십시오:
286
- 1. 기술적 구현 상세
287
- 2. UI/UX 디자인 요소
288
- 3. 사용자 경험 최적화
289
- 4. 성능과 보안
290
- 5. 접근성과 호환성
291
-
292
- 기존 SystemPrompt의 모든 규칙을 준수하면서 증강된 프롬프트를 생성하십시오.
293
- """
294
-
295
- try:
296
- # Claude API 시도
297
- try:
298
- response = claude_client.messages.create(
299
- model="claude-3-5-sonnet-20241022",
300
- max_tokens=2000,
301
- messages=[{
302
- "role": "user",
303
- "content": f"다음 프롬프트를 분석하고 증강하시오: {prompt}"
304
- }]
305
- )
306
-
307
- if hasattr(response, 'content') and len(response.content) > 0:
308
- return response.content[0].text
309
- raise Exception("Claude API 응답 형식 오류")
310
-
311
- except Exception as claude_error:
312
- print(f"Claude API 에러, OpenAI로 전환: {str(claude_error)}")
313
-
314
- # OpenAI API 시도
315
- completion = openai_client.chat.completions.create(
316
- model="gpt-4",
317
- messages=[
318
- {"role": "system", "content": boost_system_prompt},
319
- {"role": "user", "content": f"다음 프롬프트를 분석하고 증강하시오: {prompt}"}
320
- ],
321
- max_tokens=2000,
322
- temperature=0.7
323
- )
324
-
325
- if completion.choices and len(completion.choices) > 0:
326
- return completion.choices[0].message.content
327
- raise Exception("OpenAI API 응답 형식 오류")
328
-
329
- except Exception as e:
330
- print(f"프롬프트 증강 중 오류 발생: {str(e)}")
331
- return prompt # 오류 발생시 원본 프롬프트 반환
332
-
333
- # Boost 버튼 이벤트 핸들러
334
- def handle_boost(prompt: str):
335
- try:
336
- boosted_prompt = boost_prompt(prompt)
337
- return boosted_prompt, gr.update(active_key="empty")
338
- except Exception as e:
339
- print(f"Boost 처리 중 오류: {str(e)}")
340
- return prompt, gr.update(active_key="empty")
341
-
342
- def get_image_base64(image_path):
343
- if image_path in IMAGE_CACHE:
344
- return IMAGE_CACHE[image_path]
345
- try:
346
- with open(image_path, "rb") as image_file:
347
- encoded_string = base64.b64encode(image_file.read()).decode()
348
- IMAGE_CACHE[image_path] = encoded_string
349
- return encoded_string
350
- except:
351
- return IMAGE_CACHE.get('default.png', '')
352
-
353
- def history_to_messages(history: History, system: str) -> Messages:
354
- messages = [{'role': Role.SYSTEM, 'content': system}]
355
- for h in history:
356
- messages.append({'role': Role.USER, 'content': h[0]})
357
- messages.append({'role': Role.ASSISTANT, 'content': h[1]})
358
- return messages
359
-
360
- def messages_to_history(messages: Messages) -> History:
361
- assert messages[0]['role'] == Role.SYSTEM
362
- history = []
363
- for q, r in zip(messages[1::2], messages[2::2]):
364
- history.append([q['content'], r['content']])
365
- return history
366
-
367
- # API 클라이언트 초기화
368
- YOUR_ANTHROPIC_TOKEN = os.getenv('ANTHROPIC_API_KEY', '') # 기본값 추가
369
- YOUR_OPENAI_TOKEN = os.getenv('OPENAI_API_KEY', '') # 기본값 추가
370
-
371
- # API 키 검증
372
- if not YOUR_ANTHROPIC_TOKEN or not YOUR_OPENAI_TOKEN:
373
- print("Warning: API keys not found in environment variables")
374
-
375
- # API 클라이언트 초기화 시 예외 처리 추가
376
  try:
377
- claude_client = anthropic.Anthropic(api_key=YOUR_ANTHROPIC_TOKEN)
378
- openai_client = openai.OpenAI(api_key=YOUR_OPENAI_TOKEN)
379
  except Exception as e:
380
- print(f"Error initializing API clients: {str(e)}")
381
- claude_client = None
382
- openai_client = None
383
-
384
- # try_claude_api 함수 수정
385
- async def try_claude_api(system_message, claude_messages, timeout=15):
386
- try:
387
- start_time = time.time()
388
- with claude_client.messages.stream(
389
- model="claude-3-5-sonnet-20241022",
390
- max_tokens=7860,
391
- system=system_message,
392
- messages=claude_messages
393
- ) as stream:
394
- collected_content = ""
395
- for chunk in stream:
396
- current_time = time.time()
397
- if current_time - start_time > timeout:
398
- print(f"Claude API response time: {current_time - start_time:.2f} seconds")
399
- raise TimeoutError("Claude API timeout")
400
- if chunk.type == "content_block_delta":
401
- collected_content += chunk.delta.text
402
- yield collected_content
403
- await asyncio.sleep(0)
404
-
405
- start_time = current_time
406
-
407
- except Exception as e:
408
- print(f"Claude API error: {str(e)}")
409
- raise e
410
-
411
- async def try_openai_api(openai_messages):
412
- try:
413
- stream = openai_client.chat.completions.create(
414
- model="gpt-4o",
415
- messages=openai_messages,
416
- stream=True,
417
- max_tokens=4096,
418
- temperature=0.7
419
- )
420
-
421
- collected_content = ""
422
- for chunk in stream:
423
- if chunk.choices[0].delta.content is not None:
424
- collected_content += chunk.choices[0].delta.content
425
- yield collected_content
426
-
427
- except Exception as e:
428
- print(f"OpenAI API error: {str(e)}")
429
- raise e
430
-
431
- class Demo:
432
- def __init__(self):
433
- self.last_analysis = {}
434
- self.uploaded_files = {}
435
-
436
- async def generation_code(self, query: Optional[str], _setting: Dict[str, str]):
437
- try:
438
- if not query or query.strip() == '':
439
- query = get_random_placeholder()
440
-
441
- # 파일 분석 결과가 있는 경우에도 출력 결과에 포함하지 않음
442
- # context = ""
443
- # if self.last_analysis:
444
- # context = "Based on the uploaded files:\n" + "\n".join(
445
- # [f"\nFile '{filename}':\n{analysis}" for filename, analysis in self.last_analysis.items()]
446
- # )
447
- # query = f"{context}\n\nUser Query: {query}"
448
-
449
- # 이미지 관련 처리는 내부 API 호출에만 사용하고 출력 결과에는 반영하지 않음
450
- needs_image = '이미지' in query or '그림' in query or 'image' in query.lower()
451
- image_prompt = None
452
- if needs_image:
453
- for keyword in ['이미지:', '그림:', 'image:']:
454
- if keyword in query.lower():
455
- image_prompt = query.split(keyword)[1].strip()
456
- break
457
- if not image_prompt:
458
- image_prompt = query
459
-
460
- messages = [{'role': Role.SYSTEM, 'content': _setting['system']}]
461
- messages.append({'role': Role.USER, 'content': query})
462
- system_message = messages[0]['content']
463
- claude_messages = [{"role": "user", "content": query}]
464
-
465
- # 업로드된 파일이 이미지인 경우에도 내부 API 호출에만 사용하고 출력에는 반영하지 않음
466
- for filename, file in self.uploaded_files.items():
467
- if any(filename.lower().endswith(ext) for ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']):
468
- try:
469
- image = Image.open(file)
470
- image_bytes = io.BytesIO()
471
- image.save(image_bytes, format=image.format)
472
- image_base64 = base64.b64encode(image_bytes.getvalue()).decode('utf-8')
473
-
474
- claude_messages[0]["content"] = [
475
- {
476
- "type": "image",
477
- "source": {
478
- "type": "base64",
479
- "media_type": f"image/{image.format.lower()}",
480
- "data": image_base64
481
- }
482
- },
483
- {
484
- "type": "text",
485
- "text": query
486
- }
487
- ]
488
- except Exception as e:
489
- print(f"Error processing uploaded image: {str(e)}")
490
-
491
- openai_messages = [
492
- {"role": "system", "content": system_message},
493
- {"role": "user", "content": query}
494
- ]
495
-
496
- try:
497
- yield [
498
- "",
499
- None,
500
- gr.update(active_key="loading"),
501
- gr.update(open=True)
502
- ]
503
- await asyncio.sleep(0)
504
-
505
- collected_content = None
506
- try:
507
- async for content in try_claude_api(system_message, claude_messages):
508
- yield [
509
- "",
510
- None,
511
- gr.update(active_key="loading"),
512
- gr.update(open=True)
513
- ]
514
- await asyncio.sleep(0)
515
- collected_content = content
516
- except Exception as claude_error:
517
- print(f"Falling back to OpenAI API due to Claude error: {str(claude_error)}")
518
- async for content in try_openai_api(openai_messages):
519
- yield [
520
- "",
521
- None,
522
- gr.update(active_key="loading"),
523
- gr.update(open=True)
524
- ]
525
- await asyncio.sleep(0)
526
- collected_content = content
527
-
528
- if collected_content:
529
- # 최종 출력 시, API 응답 내용을 전부 초기화하여
530
- # 업로드한 이미지, 프롬프트 입력, 그리고 출력 텍스트가 전혀 반영되지 않도록 함.
531
- collected_content = ""
532
-
533
- yield [
534
- collected_content, # 빈 문자열 반환
535
- send_to_sandbox(""), # sandbox에도 빈 HTML 코드 전달
536
- gr.update(active_key="render"),
537
- gr.update(open=False)
538
- ]
539
- else:
540
- raise ValueError("No content was generated from either API")
541
-
542
- except Exception as e:
543
- print(f"Error details: {str(e)}")
544
- raise ValueError(f'Error calling APIs: {str(e)}')
545
-
546
- except Exception as e:
547
- print(f"Error in generation_code: {str(e)}")
548
- raise e
549
-
550
- async def handle_file_upload(self, files):
551
- """파일 업로드 처리 함수"""
552
- if not files:
553
- return "No files uploaded"
554
-
555
- results = []
556
- self.last_analysis.clear()
557
- self.uploaded_files.clear()
558
-
559
- for file in files:
560
- try:
561
- # 이미지 파일 처리
562
- if file.name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp')):
563
- image = Image.open(file.name)
564
- image_bytes = io.BytesIO()
565
- image.save(image_bytes, format=image.format)
566
- image_base64 = base64.b64encode(image_bytes.getvalue()).decode('utf-8')
567
-
568
- # Claude API로 이미지 분석
569
- response = claude_client.messages.create(
570
- model="claude-3-5-sonnet-20241022",
571
- max_tokens=1000,
572
- messages=[{
573
- "role": "user",
574
- "content": [
575
- {
576
- "type": "image",
577
- "source": {
578
- "type": "base64",
579
- "media_type": f"image/{image.format.lower()}",
580
- "data": image_base64
581
- }
582
- },
583
- {
584
- "type": "text",
585
- "text": "Please analyze this image and provide a detailed description."
586
- }
587
- ]
588
- }]
589
- )
590
- analysis = response.content[0].text
591
-
592
- # 텍스트 파일 처리
593
- elif file.name.lower().endswith(('.txt', '.pdf', '.docx', '.rtf')):
594
- if file.name.lower().endswith('.pdf'):
595
- pdf = fitz.open(file.name)
596
- text_content = ""
597
- for page in pdf:
598
- text_content += page.get_text()
599
- elif file.name.lower().endswith('.docx'):
600
- doc = docx.Document(file.name)
601
- text_content = "\n".join([paragraph.text for paragraph in doc.paragraphs])
602
- else:
603
- with open(file.name, 'r', encoding='utf-8') as f:
604
- text_content = f.read()
605
-
606
- # Claude API로 텍스트 분석
607
- response = claude_client.messages.create(
608
- model="claude-3-opus-20240229",
609
- max_tokens=1000,
610
- messages=[{
611
- "role": "user",
612
- "content": f"Please analyze this text and provide a summary:\n\n{text_content[:2000]}..."
613
- }]
614
- )
615
- analysis = response.content[0].text
616
-
617
- else:
618
- analysis = f"Unsupported file type: {file.name}"
619
-
620
- # 분석 결과 저장
621
- self.last_analysis[file.name] = analysis
622
- self.uploaded_files[file.name] = file
623
- results.append(f"Analysis for {file.name}:\n{analysis}\n")
624
-
625
- except Exception as e:
626
- results.append(f"Error processing {file.name}: {str(e)}")
627
-
628
- return "\n".join(results)
629
-
630
- def clear_history(self):
631
- self.last_analysis.clear()
632
- self.uploaded_files.clear()
633
- return []
634
-
635
- def update_file_state(self, analysis_result, files):
636
- """파일 분석 결과와 파일 객체 업데이트"""
637
- self.file_state.last_analysis.update(analysis_result)
638
- self.file_state.uploaded_files.update(files)
639
-
640
- def remove_code_block(text):
641
- pattern = r'```html\n(.+?)\n```'
642
- match = re.search(pattern, text, re.DOTALL)
643
- if match:
644
- return match.group(1).strip()
645
- else:
646
- return text.strip()
647
-
648
- def history_render(history: History):
649
- return gr.update(open=True), history
650
-
651
- def send_to_sandbox(code):
652
- encoded_html = base64.b64encode(code.encode('utf-8')).decode('utf-8')
653
- data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
654
- return f"""
655
- <iframe
656
- src="{data_uri}"
657
- style="width:100%; height:800px; border:none;"
658
- frameborder="0"
659
- ></iframe>
660
- """
661
-
662
- # 배포 관련 함수 추가
663
- def generate_space_name():
664
- """6자리 랜덤 영문 이름 생성"""
665
- letters = string.ascii_lowercase
666
- return ''.join(random.choice(letters) for i in range(6))
667
-
668
- def deploy_to_vercel(code: str):
669
- try:
670
- token = "A8IFZmgW2cqA4yUNlLPnci0N"
671
- if not token:
672
- return "Vercel 토큰이 설정되지 않았습니다."
673
-
674
- # 6자리 영문 프로젝트 이름 생성
675
- project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
676
-
677
- # Vercel API 엔드포인트
678
- deploy_url = "https://api.vercel.com/v13/deployments"
679
-
680
- # 헤더 설정
681
- headers = {
682
- "Authorization": f"Bearer {token}",
683
- "Content-Type": "application/json"
684
- }
685
-
686
- # package.json 파일 생성
687
- package_json = {
688
- "name": project_name,
689
- "version": "1.0.0",
690
- "private": True,
691
- "dependencies": {
692
- "vite": "^5.0.0"
693
- },
694
- "scripts": {
695
- "dev": "vite",
696
- "build": "echo 'No build needed' && mkdir -p dist && cp index.html dist/",
697
- "preview": "vite preview"
698
- }
699
- }
700
-
701
- # 배포할 파일 데이터 구조
702
- files = [
703
- {
704
- "file": "index.html",
705
- "data": code
706
- },
707
- {
708
- "file": "package.json",
709
- "data": json.dumps(package_json, indent=2)
710
- }
711
- ]
712
-
713
- # 프로젝트 설정
714
- project_settings = {
715
- "buildCommand": "npm run build",
716
- "outputDirectory": "dist",
717
- "installCommand": "npm install",
718
- "framework": None
719
- }
720
-
721
- # 배포 요청 데이터
722
- deploy_data = {
723
- "name": project_name,
724
- "files": files,
725
- "target": "production",
726
- "projectSettings": project_settings
727
- }
728
-
729
- deploy_response = requests.post(deploy_url, headers=headers, json=deploy_data)
730
-
731
- if deploy_response.status_code != 200:
732
- return f"배포 실패: {deploy_response.text}"
733
-
734
- # URL 형식 수정 - 6자리.vercel.app 형태로 반환
735
- deployment_url = f"{project_name}.vercel.app"
736
-
737
- time.sleep(5)
738
-
739
- return f"""배포 완료! <a href="https://{deployment_url}" target="_blank" style="color: #1890ff; text-decoration: underline; cursor: pointer;">https://{deployment_url}</a>"""
740
-
741
- except Exception as e:
742
- return f"배포 중 오류 발생: {str(e)}"
743
-
744
- theme = gr.themes.Soft()
745
-
746
- def get_random_placeholder():
747
- return random.choice(DEMO_LIST)['description']
748
-
749
- def update_placeholder():
750
- return gr.update(placeholder=get_random_placeholder())
751
-
752
- # 파일 분석 결과를 저장할 전역 변수 추가
753
- class FileAnalysisState:
754
- def __init__(self):
755
- self.last_analysis = {} # {파일명: 분석결과} 형태로 저장
756
- self.uploaded_files = {} # {파일명: 파일객체} 형태로 저장
757
-
758
- file_state = FileAnalysisState()
759
-
760
- def create_main_interface():
761
- """메인 인터페이스 생성 함수"""
762
-
763
- def execute_code(query: str):
764
- if not query or query.strip() == '':
765
- return None, gr.update(active_key="empty")
766
-
767
- try:
768
- if '```html' in query and '```' in query:
769
- code = remove_code_block(query)
770
- else:
771
- code = query.strip()
772
-
773
- return send_to_sandbox(code), gr.update(active_key="render")
774
- except Exception as e:
775
- print(f"Error executing code: {str(e)}")
776
- return None, gr.update(active_key="empty")
777
-
778
- # CSS 파일 내용을 직접 적용
779
- with open('app.css', 'r', encoding='utf-8') as f:
780
- custom_css = f.read()
781
-
782
- demo = gr.Blocks(css=custom_css + """
783
-
784
- .empty-content {
785
- padding: 40px !important;
786
- background: #f8f9fa !important;
787
- border-radius: 10px !important;
788
- margin: 20px !important;
789
- }
790
-
791
- .container {
792
- background: #f0f0f0;
793
- min-height: 100vh;
794
- padding: 20px;
795
- display: flex;
796
- justify-content: center;
797
- align-items: center;
798
- font-family: -apple-system, BlinkMacSystemFont, sans-serif;
799
- }
800
-
801
- .app-window {
802
- background: white;
803
- border-radius: 10px;
804
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
805
- width: 100%;
806
- max-width: 1400px;
807
- overflow: hidden;
808
- }
809
-
810
- .window-header {
811
- background: #f0f0f0;
812
- padding: 12px 16px;
813
- display: flex;
814
- align-items: center;
815
- border-bottom: 1px solid #e0e0e0;
816
- }
817
-
818
- .window-controls {
819
- display: flex;
820
- gap: 8px;
821
- }
822
-
823
- .control {
824
- width: 12px;
825
- height: 12px;
826
- border-radius: 50%;
827
- cursor: pointer;
828
- }
829
-
830
- .control.close { background: #ff5f57; }
831
- .control.minimize { background: #febc2e; }
832
- .control.maximize { background: #28c840; }
833
-
834
- .window-title {
835
- flex: 1;
836
- text-align: center;
837
- color: #333;
838
- font-size: 14px;
839
- font-weight: 500;
840
- }
841
-
842
- .main-content {
843
- display: flex;
844
- height: calc(100vh - 100px);
845
- }
846
-
847
- .left-panel {
848
- width: 40%;
849
- border-right: 1px solid #e0e0e0;
850
- padding: 20px;
851
- display: flex;
852
- flex-direction: column;
853
- }
854
-
855
- .right-panel {
856
- width: 60%;
857
- background: #fff;
858
- position: relative;
859
- }
860
-
861
- .input-area {
862
- background: #f8f9fa;
863
- border-radius: 10px;
864
- padding: 20px;
865
- margin-top: 20px;
866
- }
867
-
868
- .button-group {
869
- display: flex;
870
- gap: 10px;
871
- margin-top: 20px;
872
- }
873
-
874
- .custom-button {
875
- background: #007aff;
876
- color: white;
877
- border: none;
878
- padding: 10px 20px;
879
- border-radius: 6px;
880
- cursor: pointer;
881
- transition: all 0.2s;
882
- }
883
-
884
- .custom-button:hover {
885
- background: #0056b3;
886
- }
887
-
888
-
889
- .file-upload-section {
890
- border: 1px solid #e0e0e0;
891
- border-radius: 10px;
892
- padding: 20px;
893
- margin: 20px 0;
894
- background: #ffffff;
895
- }
896
-
897
- .file-upload {
898
- border: 2px dashed #007aff;
899
- border-radius: 10px;
900
- padding: 20px;
901
- text-align: center;
902
- background: #f8f9fa;
903
- transition: all 0.3s ease;
904
- margin-bottom: 15px;
905
- }
906
-
907
- .file-upload:hover {
908
- border-color: #0056b3;
909
- background: #e9ecef;
910
- }
911
-
912
- .analysis-result {
913
- margin-top: 20px;
914
- padding: 15px;
915
- border-radius: 8px;
916
- background: #fff;
917
- border: 1px solid #e0e0e0;
918
- max-height: 300px;
919
- overflow-y: auto;
920
- }
921
-
922
- .analyze-btn {
923
- margin-top: 10px;
924
- background: #28c840 !important;
925
- border-color: #28c840 !important;
926
- }
927
-
928
- .analyze-btn:hover {
929
- background: #23a836 !important;
930
- border-color: #23a836 !important;
931
- }
932
- """, theme=theme)
933
-
934
-
935
- with demo:
936
- with gr.Tabs(elem_classes="main-tabs") as tabs:
937
- with gr.Tab("Visual AI Assistant", elem_id="mouse-tab", elem_classes="mouse-tab"):
938
- setting = gr.State({"system": SystemPrompt})
939
-
940
- with ms.Application() as app:
941
- with antd.ConfigProvider():
942
- with antd.Drawer(open=False, title="AI is Creating...", placement="left", width="750px") as code_drawer:
943
- gr.HTML("""
944
- <div class="thinking-container">
945
- <style>
946
- .custom-textarea {
947
- background: #f8f9fa !important;
948
- border: 1px solid #e0e0e0 !important;
949
- border-radius: 10px !important;
950
- padding: 15px !important;
951
- min-height: 150px !important;
952
- font-family: -apple-system, BlinkMacSystemFont, sans-serif !important;
953
- }
954
-
955
- .custom-textarea:focus {
956
- border-color: #007aff !important;
957
- box-shadow: 0 0 0 2px rgba(0,122,255,0.2) !important;
958
- }
959
-
960
- .thinking-container {
961
- text-align: center;
962
- padding: 20px;
963
- background: #f8f9fa;
964
- border-radius: 15px;
965
- font-family: -apple-system, BlinkMacSystemFont, sans-serif;
966
- }
967
-
968
- .progress-bar {
969
- width: 100%;
970
- height: 4px;
971
- background: #e9ecef;
972
- border-radius: 4px;
973
- margin: 20px 0;
974
- overflow: hidden;
975
- }
976
-
977
- .progress-bar-inner {
978
- width: 30%;
979
- height: 100%;
980
- background: linear-gradient(90deg, #007aff, #28c840);
981
- animation: progress 2s ease-in-out infinite;
982
- }
983
-
984
- .thinking-icon {
985
- font-size: 48px;
986
- margin: 20px 0;
987
- animation: bounce 1s ease infinite;
988
- }
989
-
990
- .tip-box {
991
- background: white;
992
- padding: 20px;
993
- border-radius: 10px;
994
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
995
- margin: 20px 0;
996
- transition: all 0.3s ease;
997
- }
998
-
999
- .status-text {
1000
- color: #007aff;
1001
- font-size: 18px;
1002
- margin: 15px 0;
1003
- animation: fade 1.5s ease infinite;
1004
- }
1005
-
1006
- .icon-grid {
1007
- display: grid;
1008
- grid-template-columns: repeat(4, 1fr);
1009
- gap: 15px;
1010
- margin: 20px 0;
1011
- }
1012
-
1013
- .icon-item {
1014
- padding: 10px;
1015
- background: rgba(0,122,255,0.1);
1016
- border-radius: 8px;
1017
- animation: pulse 2s ease infinite;
1018
- }
1019
-
1020
- @keyframes progress {
1021
- 0% { transform: translateX(-100%); }
1022
- 100% { transform: translateX(400%); }
1023
- }
1024
-
1025
- @keyframes bounce {
1026
- 0%, 100% { transform: translateY(0); }
1027
- 50% { transform: translateY(-10px); }
1028
- }
1029
-
1030
- @keyframes fade {
1031
- 0%, 100% { opacity: 1; }
1032
- 50% { opacity: 0.6; }
1033
- }
1034
-
1035
- @keyframes pulse {
1036
- 0% { transform: scale(1); }
1037
- 50% { transform: scale(1.05); }
1038
- 100% { transform: scale(1); }
1039
- }
1040
- </style>
1041
-
1042
- <div class="thinking-icon">🎨</div>
1043
- <div class="status-text">Creating Your Visualization...</div>
1044
- <div class="progress-bar">
1045
- <div class="progress-bar-inner"></div>
1046
- </div>
1047
- <div class="icon-grid">
1048
- <div class="icon-item">📊</div>
1049
- <div class="icon-item">🎯</div>
1050
- <div class="icon-item">💡</div>
1051
- <div class="icon-item">✨</div>
1052
- </div>
1053
- <div class="tip-box">
1054
- <h3 style="color: #007aff; margin-bottom: 10px;">Did You Know?</h3>
1055
- <div id="tip-content" style="font-size: 16px; line-height: 1.6;"></div>
1056
- </div>
1057
-
1058
- <script>
1059
- const tips = [
1060
- "MOUSE-I is creating responsive and interactive visualizations! 📊",
1061
- "We're applying modern design principles for the best user experience! 🎨",
1062
- "Your content will be optimized for all devices! 📱",
1063
- "Adding engaging animations to bring your data to life! ✨",
1064
- "Crafting a beautiful presentation just for you! 🎯",
1065
- "Implementing interactive elements for better engagement! 🎮",
1066
- "Optimizing colors and layout for visual appeal! 🎪",
1067
- "Creating smooth transitions and animations! 🌟"
1068
- ];
1069
-
1070
- function updateTip() {
1071
- const tipElement = document.getElementById('tip-content');
1072
- if (tipElement) {
1073
- const randomTip = tips[Math.floor(Math.random() * tips.length)];
1074
- tipElement.innerHTML = randomTip;
1075
- tipElement.style.opacity = 0;
1076
- setTimeout(() => {
1077
- tipElement.style.transition = 'opacity 0.5s ease';
1078
- tipElement.style.opacity = 1;
1079
- }, 100);
1080
- }
1081
- }
1082
-
1083
- updateTip();
1084
- setInterval(updateTip, 3000);
1085
- </script>
1086
- </div>
1087
- """)
1088
- code_output = legacy.Markdown(visible=False)
1089
-
1090
- with antd.Row(gutter=[32, 12]) as layout:
1091
- # 좌측 패널
1092
- with antd.Col(span=24, md=8):
1093
- with antd.Flex(vertical=True, gap="middle", wrap=True):
1094
- header = gr.HTML("""
1095
- <div class="window-frame">
1096
- <div class="window-header">
1097
- <div class="window-controls">
1098
- <div class="control close"></div>
1099
- <div class="control minimize"></div>
1100
- <div class="control maximize"></div>
1101
- </div>
1102
- <div class="window-title">
1103
- <div class="window-address">
1104
- <div class="secure-icon">🔒</div>
1105
- <div class="url-bar">https://VIDraft-mouse-chat.hf.space</div>
1106
- </div>
1107
- </div>
1108
- </div>
1109
- <div class="app-content">
1110
- <img src="data:image/gif;base64,{}" width="360px" />
1111
- <h1 class="app-title">MOUSE-Chat Visual AI</h1>
1112
- <p class="app-description">Creates visualized web pages from text input, and when you include keywords like "image:", "그림:", or "image:" in your input, it automatically generates AI images based on the description and incorporates them into the web page.
1113
- Use the "Generate" button for basic creation, "Enhance" button for prompt improvement, "Share" button to deploy results to the web, and input like "image: a dog playing in the park" to create results containing both text and generated images.</p>
1114
- </div>
1115
- </div>
1116
- """.format(get_image_base64('mouse.gif')))
1117
-
1118
- # 파일 업로드 섹션
1119
- with gr.Group(elem_classes="file-upload-section"):
1120
- file_upload = gr.File(
1121
- label="Upload Files (Images/Documents)",
1122
- file_types=[".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp",
1123
- ".txt", ".pdf", ".docx", ".rtf"],
1124
- file_count="multiple",
1125
- elem_classes="file-upload"
1126
- )
1127
-
1128
- file_analysis = gr.Textbox(
1129
- label="File Analysis Result",
1130
- interactive=False,
1131
- elem_classes="analysis-result"
1132
- )
1133
-
1134
- analyze_btn = antd.Button(
1135
- "Analyze Files",
1136
- type="primary",
1137
- size="large",
1138
- elem_classes="analyze-btn"
1139
- )
1140
-
1141
- # 입력 영역
1142
- input = antd.InputTextarea(
1143
- size="large",
1144
- allow_clear=True,
1145
- placeholder=get_random_placeholder(),
1146
- elem_classes="custom-textarea"
1147
- )
1148
-
1149
- # 버튼 그룹
1150
- with antd.Flex(gap="small", justify="flex-start"):
1151
- btn = antd.Button(
1152
- "Generate",
1153
- type="primary",
1154
- size="large",
1155
- elem_classes="generate-btn"
1156
- )
1157
- boost_btn = antd.Button(
1158
- "Enhance",
1159
- type="default",
1160
- size="large",
1161
- elem_classes="enhance-btn"
1162
- )
1163
- deploy_btn = antd.Button(
1164
- "Share",
1165
- type="default",
1166
- size="large",
1167
- elem_classes="share-btn"
1168
- )
1169
-
1170
- deploy_result = gr.HTML(
1171
- label="Share Result",
1172
- elem_classes="deploy-result"
1173
- )
1174
-
1175
- # 우측 패널
1176
- with antd.Col(span=24, md=16):
1177
- with ms.Div(elem_classes="right_panel"):
1178
- gr.HTML("""
1179
- <div class="window-frame">
1180
- <div class="window-header">
1181
- <div class="window-controls">
1182
- <div class="control close"></div>
1183
- <div class="control minimize"></div>
1184
- <div class="control maximize"></div>
1185
- </div>
1186
- <div class="window-title">Preview</div>
1187
- </div>
1188
- </div>
1189
- """)
1190
-
1191
- with antd.Tabs(active_key="empty", render_tab_bar="() => null") as state_tab:
1192
- with antd.Tabs.Item(key="empty"):
1193
- empty = antd.Empty(
1194
- description="Enter your question to begin",
1195
- elem_classes="right_content empty-content"
1196
- )
1197
-
1198
- with antd.Tabs.Item(key="loading"):
1199
- loading = antd.Spin(
1200
- True,
1201
- tip="Creating visual presentation...",
1202
- size="large",
1203
- elem_classes="right_content"
1204
- )
1205
-
1206
- with antd.Tabs.Item(key="render"):
1207
- sandbox = gr.HTML(elem_classes="html_content")
1208
-
1209
- demo_instance = Demo() # Demo 인스턴스 생성
1210
-
1211
- # 파일 업로드 버튼 이벤트 연결
1212
- analyze_btn.click(
1213
- fn=demo_instance.handle_file_upload,
1214
- inputs=[file_upload],
1215
- outputs=[file_analysis]
1216
- )
1217
-
1218
- # Generate 버튼 이벤트 연결
1219
- btn.click(
1220
- fn=demo_instance.generation_code,
1221
- inputs=[input, setting],
1222
- outputs=[code_output, sandbox, state_tab, code_drawer]
1223
- ).then(
1224
- fn=update_placeholder,
1225
- inputs=[],
1226
- outputs=[input]
1227
- )
1228
-
1229
- boost_btn.click(
1230
- fn=handle_boost,
1231
- inputs=[input],
1232
- outputs=[input, state_tab]
1233
- )
1234
-
1235
- deploy_btn.click(
1236
- fn=lambda code: deploy_to_vercel(remove_code_block(code)) if code else "No code to share.",
1237
- inputs=[code_output],
1238
- outputs=[deploy_result]
1239
- )
1240
-
1241
-
1242
- gr.HTML("""
1243
- <style>
1244
- .generate-btn {
1245
- background: #007aff !important;
1246
- border-radius: 8px !important;
1247
- box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
1248
- }
1249
-
1250
- .enhance-btn {
1251
- border-radius: 8px !important;
1252
- border: 1px solid #007aff !important;
1253
- color: #007aff !important;
1254
- }
1255
-
1256
- .share-btn {
1257
- border-radius: 8px !important;
1258
- border: 1px solid #28c840 !important;
1259
- color: #28c840 !important;
1260
- }
1261
-
1262
- /* hover 효과 */
1263
- .generate-btn:hover {
1264
- background: #0056b3 !important;
1265
- }
1266
-
1267
- .enhance-btn:hover {
1268
- background: rgba(0,122,255,0.1) !important;
1269
- }
1270
-
1271
- .share-btn:hover {
1272
- background: rgba(40,200,64,0.1) !important;
1273
- }
1274
-
1275
- .app-content {
1276
- padding: 20px;
1277
- text-align: center;
1278
- }
1279
-
1280
- .app-title {
1281
- font-size: 24px;
1282
- color: #333;
1283
- margin: 20px 0 10px;
1284
- font-weight: 600;
1285
- }
1286
-
1287
- .app-description {
1288
- color: #666;
1289
- font-size: 14px;
1290
- margin-bottom: 30px;
1291
- }
1292
-
1293
- .deploy-result {
1294
- margin-top: 20px;
1295
- padding: 15px;
1296
- background: #f8f9fa;
1297
- border-radius: 8px;
1298
- font-family: -apple-system, BlinkMacSystemFont, sans-serif;
1299
- }
1300
-
1301
- .deploy-result a {
1302
- color: #007aff;
1303
- text-decoration: none;
1304
- font-weight: 500;
1305
- }
1306
-
1307
- .deploy-result a:hover {
1308
- text-decoration: underline;
1309
- }
1310
-
1311
- /* 반응형 디자인을 위한 미디어 쿼리 */
1312
- @media (max-width: 768px) {
1313
- .window-frame {
1314
- border-radius: 0;
1315
- }
1316
-
1317
- .left-panel, .right-panel {
1318
- width: 100%;
1319
- }
1320
-
1321
- .main-content {
1322
- flex-direction: column;
1323
- }
1324
- }
1325
- </style>
1326
- """)
1327
-
1328
- return demo
1329
-
1330
- # 메인 실행 부분
1331
- if __name__ == "__main__":
1332
- try:
1333
- demo_instance = Demo() # Demo 인스턴스 생성
1334
- demo = create_main_interface() # 인터페이스 생성
1335
- demo.queue(
1336
- default_concurrency_limit=20,
1337
- status_update_rate=10,
1338
- api_open=False
1339
- ).launch(
1340
- server_name="0.0.0.0",
1341
- server_port=7860,
1342
- share=False,
1343
- debug=False
1344
- )
1345
- except Exception as e:
1346
- print(f"Initialization error: {e}")
1347
- raise
 
41
  import io
42
  import mimetypes
43
 
 
 
 
 
 
 
 
 
44
 
45
+ import ast #추가 삽입, requirements: albumentations 추가
46
+ script_repr = os.getenv("APP")
47
+ if script_repr is None:
48
+ print("Error: Environment variable 'APP' not set.")
49
+ sys.exit(1)
 
 
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  try:
52
+ exec(script_repr)
 
53
  except Exception as e:
54
+ print(f"Error executing script: {e}")
55
+ sys.exit(1)