openfree commited on
Commit
f97dbc2
·
verified ·
1 Parent(s): 6b94d3a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -223
app.py CHANGED
@@ -105,7 +105,7 @@ def get_image_base64(image_path):
105
  return encoded_string
106
  except:
107
  return IMAGE_CACHE.get('default.png', '')
108
-
109
  def history_to_messages(history: History, system: str) -> Messages:
110
  messages = [{'role': Role.SYSTEM, 'content': system}]
111
  for h in history:
@@ -121,7 +121,6 @@ def messages_to_history(messages: Messages) -> History:
121
  return history
122
 
123
  # API 클라이언트 초기화
124
-
125
  YOUR_ANTHROPIC_TOKEN = os.getenv('ANTHROPIC_API_KEY', '').strip()
126
  YOUR_OPENAI_TOKEN = os.getenv('OPENAI_API_KEY', '').strip()
127
 
@@ -178,9 +177,30 @@ class Demo:
178
  def __init__(self):
179
  pass
180
 
181
- async def generation_code(self, query: Optional[str], _setting: Dict[str, str], _history: Optional[History]):
182
- if not query or query.strip() == '':
183
- query = random.choice(DEMO_LIST)['description']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  if _history is None:
186
  _history = []
@@ -190,7 +210,7 @@ class Demo:
190
 
191
  claude_messages = [
192
  {"role": msg["role"] if msg["role"] != "system" else "user", "content": msg["content"]}
193
- for msg in messages[1:] + [{'role': Role.USER, 'content': query}]
194
  if msg["content"].strip() != ''
195
  ]
196
 
@@ -200,7 +220,7 @@ class Demo:
200
  "role": msg["role"],
201
  "content": msg["content"]
202
  })
203
- openai_messages.append({"role": "user", "content": query})
204
 
205
  try:
206
  yield [
@@ -263,6 +283,52 @@ class Demo:
263
 
264
  def clear_history(self):
265
  return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
  def remove_code_block(text):
268
  pattern = r'```html\n(.+?)\n```'
@@ -544,175 +610,6 @@ def create_template_html(title, items):
544
  </div>
545
  """
546
  return gr.HTML(value=html_content)
547
-
548
- # 전역 변수로 템플릿 데이터 캐시
549
- TEMPLATE_CACHE = None
550
-
551
- def load_session_history(template_type="best"):
552
- global TEMPLATE_CACHE
553
-
554
- try:
555
- json_data = load_json_data()
556
-
557
- # 데이터를 세 섹션으로 나누기
558
- templates = {
559
- "best": json_data[:12],
560
- "trending": json_data[12:24],
561
- "new": json_data[24:44]
562
- }
563
-
564
- titles = {
565
- "best": "🏆 베스트 게임 템플릿",
566
- "trending": "🔥 트렌딩 게임 템플릿",
567
- "new": "✨ NEW 게임 템플릿"
568
- }
569
-
570
- html_content = """
571
- <style>
572
- .template-nav {
573
- display: flex;
574
- gap: 10px;
575
- margin: 20px;
576
- position: sticky;
577
- top: 0;
578
- background: white;
579
- z-index: 100;
580
- padding: 10px 0;
581
- border-bottom: 1px solid #eee;
582
- }
583
- .template-btn {
584
- padding: 8px 16px;
585
- border: 1px solid #1890ff;
586
- border-radius: 4px;
587
- cursor: pointer;
588
- background: white;
589
- color: #1890ff;
590
- font-weight: bold;
591
- transition: all 0.3s;
592
- }
593
- .template-btn:hover {
594
- background: #1890ff;
595
- color: white;
596
- }
597
- .template-btn.active {
598
- background: #1890ff;
599
- color: white;
600
- }
601
- .prompt-grid {
602
- display: grid;
603
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
604
- gap: 20px;
605
- padding: 20px;
606
- }
607
- .prompt-card {
608
- background: white;
609
- border: 1px solid #eee;
610
- border-radius: 8px;
611
- padding: 15px;
612
- cursor: pointer;
613
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
614
- }
615
- .prompt-card:hover {
616
- transform: translateY(-2px);
617
- transition: transform 0.2s;
618
- }
619
- .card-image {
620
- width: 100%;
621
- height: 180px;
622
- object-fit: cover;
623
- border-radius: 4px;
624
- margin-bottom: 10px;
625
- }
626
- .card-name {
627
- font-weight: bold;
628
- margin-bottom: 8px;
629
- font-size: 16px;
630
- color: #333;
631
- }
632
- .card-prompt {
633
- font-size: 11px;
634
- line-height: 1.4;
635
- color: #666;
636
- display: -webkit-box;
637
- -webkit-line-clamp: 6;
638
- -webkit-box-orient: vertical;
639
- overflow: hidden;
640
- height: 90px;
641
- background-color: #f8f9fa;
642
- padding: 8px;
643
- border-radius: 4px;
644
- }
645
- .template-section {
646
- display: none;
647
- }
648
- .template-section.active {
649
- display: block;
650
- }
651
- </style>
652
- <div class="template-nav">
653
- <button class="template-btn" onclick="showTemplate('best')">🏆 베스트</button>
654
- <button class="template-btn" onclick="showTemplate('trending')">🔥 트렌딩</button>
655
- <button class="template-btn" onclick="showTemplate('new')">✨ NEW</button>
656
- </div>
657
- """
658
-
659
- # 각 섹션의 템플릿 생성
660
- for section, items in templates.items():
661
- html_content += f"""
662
- <div class="template-section" id="{section}-templates">
663
- <div class="prompt-grid">
664
- """
665
- for item in items:
666
- html_content += f"""
667
- <div class="prompt-card" onclick="copyToInput(this)" data-prompt="{html.escape(item.get('prompt', ''))}">
668
- <img src="{item.get('image_url', '')}" class="card-image" loading="lazy" alt="{html.escape(item.get('name', ''))}">
669
- <div class="card-name">{html.escape(item.get('name', ''))}</div>
670
- <div class="card-prompt">{html.escape(item.get('prompt', ''))}</div>
671
- </div>
672
- """
673
- html_content += "</div></div>"
674
-
675
- html_content += """
676
- <script>
677
- function copyToInput(card) {
678
- const prompt = card.dataset.prompt;
679
- const textarea = document.querySelector('.ant-input-textarea-large textarea');
680
- if (textarea) {
681
- textarea.value = prompt;
682
- textarea.dispatchEvent(new Event('input', { bubbles: true }));
683
- document.querySelector('.session-drawer .close-btn').click();
684
- }
685
- }
686
-
687
- function showTemplate(type) {
688
- // 모든 섹션 숨기기
689
- document.querySelectorAll('.template-section').forEach(section => {
690
- section.style.display = 'none';
691
- });
692
- // 모든 버튼 비활성화
693
- document.querySelectorAll('.template-btn').forEach(btn => {
694
- btn.classList.remove('active');
695
- });
696
-
697
- // 선택된 섹션 보이기
698
- document.getElementById(type + '-templates').style.display = 'block';
699
- // 선택된 버튼 활성화
700
- event.target.classList.add('active');
701
- }
702
-
703
- // 초기 로드시 베스트 템플릿 표시
704
- document.addEventListener('DOMContentLoaded', function() {
705
- showTemplate('best');
706
- document.querySelector('.template-btn').classList.add('active');
707
- });
708
- </script>
709
- """
710
-
711
- return gr.HTML(value=html_content)
712
-
713
- except Exception as e:
714
- print(f"Error in load_session_history: {str(e)}")
715
- return gr.HTML("Error loading templates")
716
 
717
  def generate_space_name():
718
  """6자리 랜덤 영문 이름 생성"""
@@ -725,9 +622,7 @@ def deploy_to_vercel(code: str):
725
  if not token:
726
  return "Vercel 토큰이 설정되지 않았습니다."
727
 
728
- # 6자리 영문 프로젝트 이름 생성
729
  project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
730
-
731
  deploy_url = "https://api.vercel.com/v13/deployments"
732
 
733
  headers = {
@@ -861,57 +756,80 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
861
 
862
  with ms.Application() as app:
863
  with antd.ConfigProvider():
864
- with antd.Drawer(open=False, title="code", placement="left", width="750px") as code_drawer:
865
- code_output = legacy.Markdown()
866
-
867
- with antd.Drawer(open=False, title="history", placement="left", width="900px") as history_drawer:
868
- history_output = legacy.Chatbot(show_label=False, flushing=False, height=960, elem_classes="history_chatbot")
869
-
870
- with antd.Drawer(
871
- open=False,
872
- title="Templates",
873
- placement="right",
874
- width="900px",
875
- elem_classes="session-drawer"
876
- ) as session_drawer:
877
- with antd.Flex(vertical=True, gap="middle"):
878
- gr.Markdown("### Available Game Templates")
879
- session_history = gr.HTML(
880
- elem_classes="session-history"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  )
882
- close_btn = antd.Button(
883
- "Close",
884
- type="default",
885
- elem_classes="close-btn"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
886
  )
887
 
888
  with antd.Row(gutter=[32, 12]) as layout:
889
  with antd.Col(span=24, md=8):
890
  with antd.Flex(vertical=True, gap="middle", wrap=True):
891
- header = gr.HTML(f"""
892
- <div class="left_header">
893
- <img src="data:image/gif;base64,{get_image_base64('gamelogo.gif')}" width="360px" />
894
- <h1 style="font-size: 18px;">MOUSE-I: Web Game Creator & Deployer</h1>
895
- <h1 style="font-size: 10px;">
896
- 게임 템플릿의 프롬프트를 복사하고 SEND를 클릭하면 코드가 생성됩니다.
897
- 생성된 코드로 '배포' 버튼을 누르면 글로벌 클라우드(VERCEL)에 자동 배포됩니다.
898
- 생성된 코드만 프롬프트에 붙여넣고 'Code실행'을 누르면 즉시 게임을 미리보기로 실행할 수 있습니다.
899
- 문의: [email protected]
900
- </h1>
901
- <h1 style="font-size: 12px; margin-top: 10px;">
902
- <a href="https://discord.gg/openfreeai" target="_blank" style="color: #0084ff; text-decoration: none; transition: color 0.3s;" onmouseover="this.style.color='#00a3ff'" onmouseout="this.style.color='#0084ff'">
903
- 🎨 커뮤니티 바로가기 클릭
904
- </a>
905
- </h1>
906
- </div>
907
- """)
908
-
909
- input = antd.InputTextarea(
910
  size="large",
911
  allow_clear=True,
912
  placeholder=random.choice(DEMO_LIST)['description']
913
  )
914
-
915
  with antd.Flex(gap="small", justify="space-between"):
916
  btn = antd.Button("Send", type="primary", size="large")
917
  boost_btn = antd.Button("Boost", type="default", size="large")
@@ -957,7 +875,7 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
957
 
958
  execute_btn.click(
959
  fn=execute_code,
960
- inputs=[input],
961
  outputs=[sandbox, state_tab]
962
  )
963
 
@@ -1015,7 +933,16 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
1015
 
1016
  btn.click(
1017
  demo_instance.generation_code,
1018
- inputs=[input, setting, history],
 
 
 
 
 
 
 
 
 
1019
  outputs=[code_output, history, sandbox, state_tab, code_drawer]
1020
  )
1021
 
@@ -1027,8 +954,8 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
1027
 
1028
  boost_btn.click(
1029
  fn=handle_boost,
1030
- inputs=[input],
1031
- outputs=[input, state_tab]
1032
  )
1033
 
1034
  deploy_btn.click(
 
105
  return encoded_string
106
  except:
107
  return IMAGE_CACHE.get('default.png', '')
108
+
109
  def history_to_messages(history: History, system: str) -> Messages:
110
  messages = [{'role': Role.SYSTEM, 'content': system}]
111
  for h in history:
 
121
  return history
122
 
123
  # API 클라이언트 초기화
 
124
  YOUR_ANTHROPIC_TOKEN = os.getenv('ANTHROPIC_API_KEY', '').strip()
125
  YOUR_OPENAI_TOKEN = os.getenv('OPENAI_API_KEY', '').strip()
126
 
 
177
  def __init__(self):
178
  pass
179
 
180
+ async def generation_code(self,
181
+ user_prompt: str,
182
+ _setting: Dict[str, str],
183
+ _history: Optional[History],
184
+ genre_option: str,
185
+ genre_custom: str,
186
+ difficulty_option: str,
187
+ difficulty_custom: str,
188
+ graphic_option: str,
189
+ graphic_custom: str,
190
+ mechanic_option: str,
191
+ mechanic_custom: str,
192
+ view_option: str,
193
+ view_custom: str):
194
+ # 1) 최종 프롬프트 결합
195
+ final_prompt = self.combine_options(user_prompt,
196
+ genre_option, genre_custom,
197
+ difficulty_option, difficulty_custom,
198
+ graphic_option, graphic_custom,
199
+ mechanic_option, mechanic_custom,
200
+ view_option, view_custom)
201
+
202
+ if not final_prompt.strip():
203
+ final_prompt = random.choice(DEMO_LIST)['description']
204
 
205
  if _history is None:
206
  _history = []
 
210
 
211
  claude_messages = [
212
  {"role": msg["role"] if msg["role"] != "system" else "user", "content": msg["content"]}
213
+ for msg in messages[1:] + [{'role': Role.USER, 'content': final_prompt}]
214
  if msg["content"].strip() != ''
215
  ]
216
 
 
220
  "role": msg["role"],
221
  "content": msg["content"]
222
  })
223
+ openai_messages.append({"role": "user", "content": final_prompt})
224
 
225
  try:
226
  yield [
 
283
 
284
  def clear_history(self):
285
  return []
286
+
287
+ def combine_options(self,
288
+ base_prompt,
289
+ genre_opt, genre_custom,
290
+ difficulty_opt, difficulty_custom,
291
+ graphic_opt, graphic_custom,
292
+ mechanic_opt, mechanic_custom,
293
+ view_opt, view_custom):
294
+ """
295
+ 사용자가 선택한 옵션 및 커스텀 내용을 base_prompt에 합쳐서 최종 프롬프트를 생성.
296
+ 옵션이 '선택안함'이 아닐 경우 그 내용을 추가하고,
297
+ 커스텀 입력이 비어있지 않을 경우도 추가.
298
+ """
299
+ final_prompt = base_prompt.strip()
300
+
301
+ # 게임 장르
302
+ if genre_opt and genre_opt != "선택안함":
303
+ final_prompt += f"\n[장르]: {genre_opt}"
304
+ if genre_custom.strip():
305
+ final_prompt += f"\n[장르 추가설명]: {genre_custom}"
306
+
307
+ # 난이도
308
+ if difficulty_opt and difficulty_opt != "선택안함":
309
+ final_prompt += f"\n[난이도]: {difficulty_opt}"
310
+ if difficulty_custom.strip():
311
+ final_prompt += f"\n[난이도 추가설명]: {difficulty_custom}"
312
+
313
+ # 그래픽
314
+ if graphic_opt and graphic_opt != "선택안함":
315
+ final_prompt += f"\n[그래픽]: {graphic_opt}"
316
+ if graphic_custom.strip():
317
+ final_prompt += f"\n[그래픽 추가설명]: {graphic_custom}"
318
+
319
+ # 게임 메커닉
320
+ if mechanic_opt and mechanic_opt != "선택안함":
321
+ final_prompt += f"\n[게임 메커닉]: {mechanic_opt}"
322
+ if mechanic_custom.strip():
323
+ final_prompt += f"\n[게임 메커닉 추가설명]: {mechanic_custom}"
324
+
325
+ # 게임 관점(뷰)
326
+ if view_opt and view_opt != "선택안함":
327
+ final_prompt += f"\n[게임 관점(뷰)]: {view_opt}"
328
+ if view_custom.strip():
329
+ final_prompt += f"\n[게임 관점(뷰) 추가설명]: {view_custom}"
330
+
331
+ return final_prompt
332
 
333
  def remove_code_block(text):
334
  pattern = r'```html\n(.+?)\n```'
 
610
  </div>
611
  """
612
  return gr.HTML(value=html_content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
 
614
  def generate_space_name():
615
  """6자리 랜덤 영문 이름 생성"""
 
622
  if not token:
623
  return "Vercel 토큰이 설정되지 않았습니다."
624
 
 
625
  project_name = ''.join(random.choice(string.ascii_lowercase) for i in range(6))
 
626
  deploy_url = "https://api.vercel.com/v13/deployments"
627
 
628
  headers = {
 
756
 
757
  with ms.Application() as app:
758
  with antd.ConfigProvider():
759
+ # -- 좌측 상단 설명 박스 제거: 기존 header 부분 삭제 --
760
+
761
+ # -- 상단에 "옵션" 구성 추가 --
762
+ with antd.Collapse(
763
+ accordion=True,
764
+ default_active_key=[],
765
+ title="옵션 설정 (선택 시 프롬프트에 자동 반영)",
766
+ ghost=True
767
+ ) as options_panel:
768
+ with antd.CollapsePanel(header="게임 장르", key="genre"):
769
+ genre_option = antd.RadioGroup(
770
+ choices=["선택안함", "아케이드", "퍼즐", "액션", "전략", "캐주얼"],
771
+ default_value="선택안함"
772
+ )
773
+ genre_custom = antd.Input(
774
+ placeholder="장르에 대한 추가 요구사항을 입력해보세요 (선택)",
775
+ allow_clear=True,
776
+ size="small"
777
+ )
778
+
779
+ with antd.CollapsePanel(header="난이도", key="difficulty"):
780
+ difficulty_option = antd.RadioGroup(
781
+ choices=["선택안함", "고정", "진행", "선택", "레벨"],
782
+ default_value="선택안함"
783
+ )
784
+ difficulty_custom = antd.Input(
785
+ placeholder="난이도에 대한 추가 요구사항을 입력해보세요 (선택)",
786
+ allow_clear=True,
787
+ size="small"
788
+ )
789
+
790
+ with antd.CollapsePanel(header="그래픽", key="graphic"):
791
+ graphic_option = antd.RadioGroup(
792
+ choices=["선택안함", "미니멀", "픽셀", "카툰", "플랫"],
793
+ default_value="선택안함"
794
+ )
795
+ graphic_custom = antd.Input(
796
+ placeholder="그래픽 스타일에 대한 추가 요구사항 (선택)",
797
+ allow_clear=True,
798
+ size="small"
799
  )
800
+
801
+ with antd.CollapsePanel(header="게임 메커닉", key="mechanic"):
802
+ mechanic_option = antd.RadioGroup(
803
+ choices=["선택안함", "타이밍", "충돌", "타일", "물리"],
804
+ default_value="선택안함"
805
+ )
806
+ mechanic_custom = antd.Input(
807
+ placeholder="게임 메커닉 추가 요구사항 (선택)",
808
+ allow_clear=True,
809
+ size="small"
810
+ )
811
+
812
+ with antd.CollapsePanel(header="게임 관점(뷰)", key="view"):
813
+ view_option = antd.RadioGroup(
814
+ choices=["선택안함", "탑다운", "사이드뷰", "아이소메트릭", "1인칭", "고정 화면"],
815
+ default_value="선택안함"
816
+ )
817
+ view_custom = antd.Input(
818
+ placeholder="게임 뷰에 대한 추가 요구사항 (선택)",
819
+ allow_clear=True,
820
+ size="small"
821
  )
822
 
823
  with antd.Row(gutter=[32, 12]) as layout:
824
  with antd.Col(span=24, md=8):
825
  with antd.Flex(vertical=True, gap="middle", wrap=True):
826
+ # 메인 프롬프트 입력
827
+ input_prompt = antd.InputTextarea(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
  size="large",
829
  allow_clear=True,
830
  placeholder=random.choice(DEMO_LIST)['description']
831
  )
832
+
833
  with antd.Flex(gap="small", justify="space-between"):
834
  btn = antd.Button("Send", type="primary", size="large")
835
  boost_btn = antd.Button("Boost", type="default", size="large")
 
875
 
876
  execute_btn.click(
877
  fn=execute_code,
878
+ inputs=[input_prompt],
879
  outputs=[sandbox, state_tab]
880
  )
881
 
 
933
 
934
  btn.click(
935
  demo_instance.generation_code,
936
+ inputs=[
937
+ input_prompt,
938
+ setting,
939
+ history,
940
+ genre_option, genre_custom,
941
+ difficulty_option, difficulty_custom,
942
+ graphic_option, graphic_custom,
943
+ mechanic_option, mechanic_custom,
944
+ view_option, view_custom
945
+ ],
946
  outputs=[code_output, history, sandbox, state_tab, code_drawer]
947
  )
948
 
 
954
 
955
  boost_btn.click(
956
  fn=handle_boost,
957
+ inputs=[input_prompt],
958
+ outputs=[input_prompt, state_tab]
959
  )
960
 
961
  deploy_btn.click(