Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
@@ -122,7 +122,7 @@ def messages_to_history(messages: Messages) -> History:
|
|
122 |
|
123 |
# API 클라이언트 초기화
|
124 |
YOUR_ANTHROPIC_TOKEN = os.getenv('ANTHROPIC_API_KEY', '').strip()
|
125 |
-
YOUR_OPENAI_TOKEN = os.getenv('OPENAI_API_KEY',
|
126 |
|
127 |
claude_client = anthropic.Anthropic(api_key=YOUR_ANTHROPIC_TOKEN)
|
128 |
openai_client = openai.OpenAI(api_key=YOUR_OPENAI_TOKEN)
|
@@ -146,9 +146,7 @@ async def try_claude_api(system_message, claude_messages, timeout=15):
|
|
146 |
collected_content += chunk.delta.text
|
147 |
yield collected_content
|
148 |
await asyncio.sleep(0)
|
149 |
-
|
150 |
start_time = current_time
|
151 |
-
|
152 |
except Exception as e:
|
153 |
print(f"Claude API error: {str(e)}")
|
154 |
raise e
|
@@ -168,7 +166,6 @@ async def try_openai_api(openai_messages):
|
|
168 |
if chunk.choices[0].delta.content is not None:
|
169 |
collected_content += chunk.choices[0].delta.content
|
170 |
yield collected_content
|
171 |
-
|
172 |
except Exception as e:
|
173 |
print(f"OpenAI API error: {str(e)}")
|
174 |
raise e
|
@@ -191,6 +188,10 @@ class Demo:
|
|
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,
|
@@ -199,6 +200,7 @@ class Demo:
|
|
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 |
|
@@ -209,7 +211,7 @@ class Demo:
|
|
209 |
system_message = messages[0]['content']
|
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 |
]
|
@@ -223,6 +225,7 @@ class Demo:
|
|
223 |
openai_messages.append({"role": "user", "content": final_prompt})
|
224 |
|
225 |
try:
|
|
|
226 |
yield [
|
227 |
"Generating code...",
|
228 |
_history,
|
@@ -234,6 +237,7 @@ class Demo:
|
|
234 |
|
235 |
collected_content = None
|
236 |
try:
|
|
|
237 |
async for content in try_claude_api(system_message, claude_messages):
|
238 |
yield [
|
239 |
content,
|
@@ -248,6 +252,7 @@ class Demo:
|
|
248 |
except Exception as claude_error:
|
249 |
print(f"Falling back to OpenAI API due to Claude error: {str(claude_error)}")
|
250 |
|
|
|
251 |
async for content in try_openai_api(openai_messages):
|
252 |
yield [
|
253 |
content,
|
@@ -260,6 +265,7 @@ class Demo:
|
|
260 |
collected_content = content
|
261 |
|
262 |
if collected_content:
|
|
|
263 |
_history = messages_to_history([
|
264 |
{'role': Role.SYSTEM, 'content': system_message}
|
265 |
] + claude_messages + [{
|
@@ -267,6 +273,7 @@ class Demo:
|
|
267 |
'content': collected_content
|
268 |
}])
|
269 |
|
|
|
270 |
yield [
|
271 |
collected_content,
|
272 |
_history,
|
@@ -292,9 +299,7 @@ class Demo:
|
|
292 |
mechanic_opt, mechanic_custom,
|
293 |
view_opt, view_custom):
|
294 |
"""
|
295 |
-
사용자가 선택한 옵션
|
296 |
-
옵션이 '선택안함'이 아닐 경우 그 내용을 추가하고,
|
297 |
-
커스텀 입력이 비어있지 않을 경우도 추가.
|
298 |
"""
|
299 |
final_prompt = base_prompt.strip()
|
300 |
|
@@ -346,8 +351,6 @@ def send_to_sandbox(code):
|
|
346 |
data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
|
347 |
return f"<iframe src=\"{data_uri}\" width=\"100%\" height=\"920px\"></iframe>"
|
348 |
|
349 |
-
theme = gr.themes.Soft()
|
350 |
-
|
351 |
def load_json_data():
|
352 |
# 하드코딩된 데이터 반환 (게임 목록)
|
353 |
return [
|
@@ -677,7 +680,6 @@ def deploy_to_vercel(code: str):
|
|
677 |
deployment_url = f"{project_name}.vercel.app"
|
678 |
|
679 |
time.sleep(5)
|
680 |
-
|
681 |
return f"""배포 완료! <a href="https://{deployment_url}" target="_blank" style="color: #1890ff; text-decoration: underline; cursor: pointer;">https://{deployment_url}</a>"""
|
682 |
|
683 |
except Exception as e:
|
@@ -756,69 +758,69 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
|
|
756 |
|
757 |
with ms.Application() as app:
|
758 |
with antd.ConfigProvider():
|
759 |
-
#
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
|
789 |
-
|
790 |
-
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
-
|
801 |
-
|
802 |
-
|
803 |
-
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
|
810 |
-
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
|
823 |
with antd.Row(gutter=[32, 12]) as layout:
|
824 |
with antd.Col(span=24, md=8):
|
@@ -861,7 +863,6 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
|
|
861 |
def execute_code(query: str):
|
862 |
if not query or query.strip() == '':
|
863 |
return None, gr.update(active_key="empty")
|
864 |
-
|
865 |
try:
|
866 |
if '```html' in query and '```' in query:
|
867 |
code = remove_code_block(query)
|
@@ -885,6 +886,9 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
|
|
885 |
outputs=[code_drawer]
|
886 |
)
|
887 |
|
|
|
|
|
|
|
888 |
code_drawer.close(
|
889 |
lambda: gr.update(open=False),
|
890 |
inputs=[],
|
@@ -894,9 +898,12 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
|
|
894 |
historyBtn.click(
|
895 |
history_render,
|
896 |
inputs=[history],
|
897 |
-
outputs=[history_drawer,
|
898 |
)
|
899 |
|
|
|
|
|
|
|
900 |
history_drawer.close(
|
901 |
lambda: gr.update(open=False),
|
902 |
inputs=[],
|
@@ -921,6 +928,11 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
|
|
921 |
queue=False
|
922 |
)
|
923 |
|
|
|
|
|
|
|
|
|
|
|
924 |
session_drawer.close(
|
925 |
lambda: (gr.update(open=False), gr.HTML("")),
|
926 |
outputs=[session_drawer, session_history]
|
@@ -934,8 +946,8 @@ with gr.Blocks(css_paths="app.css", theme=theme) as demo:
|
|
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,
|
|
|
122 |
|
123 |
# API 클라이언트 초기화
|
124 |
YOUR_ANTHROPIC_TOKEN = os.getenv('ANTHROPIC_API_KEY', '').strip()
|
125 |
+
YOUR_OPENAI_TOKEN = os.getenv('OPENAI_API_KEY', '').strip()
|
126 |
|
127 |
claude_client = anthropic.Anthropic(api_key=YOUR_ANTHROPIC_TOKEN)
|
128 |
openai_client = openai.OpenAI(api_key=YOUR_OPENAI_TOKEN)
|
|
|
146 |
collected_content += chunk.delta.text
|
147 |
yield collected_content
|
148 |
await asyncio.sleep(0)
|
|
|
149 |
start_time = current_time
|
|
|
150 |
except Exception as e:
|
151 |
print(f"Claude API error: {str(e)}")
|
152 |
raise e
|
|
|
166 |
if chunk.choices[0].delta.content is not None:
|
167 |
collected_content += chunk.choices[0].delta.content
|
168 |
yield collected_content
|
|
|
169 |
except Exception as e:
|
170 |
print(f"OpenAI API error: {str(e)}")
|
171 |
raise e
|
|
|
188 |
mechanic_custom: str,
|
189 |
view_option: str,
|
190 |
view_custom: str):
|
191 |
+
"""
|
192 |
+
옵션(장르/난이도/그래픽/메커닉/뷰)과 메인 프롬프트를 합쳐 최종 명령을 구성한 뒤,
|
193 |
+
Claude -> OpenAI 순으로 코드 생성을 시도합니다.
|
194 |
+
"""
|
195 |
# 1) 최종 프롬프트 결합
|
196 |
final_prompt = self.combine_options(user_prompt,
|
197 |
genre_option, genre_custom,
|
|
|
200 |
mechanic_option, mechanic_custom,
|
201 |
view_option, view_custom)
|
202 |
|
203 |
+
# 기본 입력이 비어 있으면 임의 DEMO_LIST 중 하나를 사용
|
204 |
if not final_prompt.strip():
|
205 |
final_prompt = random.choice(DEMO_LIST)['description']
|
206 |
|
|
|
211 |
system_message = messages[0]['content']
|
212 |
|
213 |
claude_messages = [
|
214 |
+
{"role": msg["role"] if msg["role"] != "system" else "user", "content": msg["content"]}
|
215 |
for msg in messages[1:] + [{'role': Role.USER, 'content': final_prompt}]
|
216 |
if msg["content"].strip() != ''
|
217 |
]
|
|
|
225 |
openai_messages.append({"role": "user", "content": final_prompt})
|
226 |
|
227 |
try:
|
228 |
+
# 첫 yield (로딩 상태)
|
229 |
yield [
|
230 |
"Generating code...",
|
231 |
_history,
|
|
|
237 |
|
238 |
collected_content = None
|
239 |
try:
|
240 |
+
# Claude 시도
|
241 |
async for content in try_claude_api(system_message, claude_messages):
|
242 |
yield [
|
243 |
content,
|
|
|
252 |
except Exception as claude_error:
|
253 |
print(f"Falling back to OpenAI API due to Claude error: {str(claude_error)}")
|
254 |
|
255 |
+
# OpenAI 시도
|
256 |
async for content in try_openai_api(openai_messages):
|
257 |
yield [
|
258 |
content,
|
|
|
265 |
collected_content = content
|
266 |
|
267 |
if collected_content:
|
268 |
+
# 히스토리 업데이트
|
269 |
_history = messages_to_history([
|
270 |
{'role': Role.SYSTEM, 'content': system_message}
|
271 |
] + claude_messages + [{
|
|
|
273 |
'content': collected_content
|
274 |
}])
|
275 |
|
276 |
+
# 최종 결과 yield
|
277 |
yield [
|
278 |
collected_content,
|
279 |
_history,
|
|
|
299 |
mechanic_opt, mechanic_custom,
|
300 |
view_opt, view_custom):
|
301 |
"""
|
302 |
+
사용자가 선택한 옵션 + 커스텀 내용 -> base_prompt에 합쳐 최종 프롬프트로 만든다.
|
|
|
|
|
303 |
"""
|
304 |
final_prompt = base_prompt.strip()
|
305 |
|
|
|
351 |
data_uri = f"data:text/html;charset=utf-8;base64,{encoded_html}"
|
352 |
return f"<iframe src=\"{data_uri}\" width=\"100%\" height=\"920px\"></iframe>"
|
353 |
|
|
|
|
|
354 |
def load_json_data():
|
355 |
# 하드코딩된 데이터 반환 (게임 목록)
|
356 |
return [
|
|
|
680 |
deployment_url = f"{project_name}.vercel.app"
|
681 |
|
682 |
time.sleep(5)
|
|
|
683 |
return f"""배포 완료! <a href="https://{deployment_url}" target="_blank" style="color: #1890ff; text-decoration: underline; cursor: pointer;">https://{deployment_url}</a>"""
|
684 |
|
685 |
except Exception as e:
|
|
|
758 |
|
759 |
with ms.Application() as app:
|
760 |
with antd.ConfigProvider():
|
761 |
+
# (1) 상단 설명 박스 제거, 대신 단순 안내 문구
|
762 |
+
gr.Markdown("### [옵션을 선택하면 자동으로 프롬프트에 포함됩니다.]")
|
763 |
+
|
764 |
+
# (2) Collapse에 'title' 대신 Panel을 직접 구성
|
765 |
+
with antd.Collapse(accordion=True, default_active_key=[], ghost=True) as options_panel:
|
766 |
+
# 전체 옵션 제목용 Panel
|
767 |
+
with antd.CollapsePanel(header="옵션 설정 (선택 시 프롬프트에 자동 반영)", key="option_header"):
|
768 |
+
# 세부 패널은 필요시 추가 (현재는 바로 RadioGroup 등을 사용)
|
769 |
+
with antd.Collapse(accordion=True, default_active_key=[]):
|
770 |
+
with antd.CollapsePanel(header="게임 장르", key="genre"):
|
771 |
+
genre_option = antd.RadioGroup(
|
772 |
+
choices=["선택안함", "아케이드", "퍼즐", "액션", "전략", "캐주얼"],
|
773 |
+
default_value="선택안함"
|
774 |
+
)
|
775 |
+
genre_custom = antd.Input(
|
776 |
+
placeholder="장르에 대한 추가 요구사항 (선택)",
|
777 |
+
allow_clear=True,
|
778 |
+
size="small"
|
779 |
+
)
|
780 |
+
|
781 |
+
with antd.CollapsePanel(header="난이도", key="difficulty"):
|
782 |
+
difficulty_option = antd.RadioGroup(
|
783 |
+
choices=["선택안함", "고정", "진행", "선택", "레벨"],
|
784 |
+
default_value="선택안함"
|
785 |
+
)
|
786 |
+
difficulty_custom = antd.Input(
|
787 |
+
placeholder="난이도에 대한 추가 요구사항 (선택)",
|
788 |
+
allow_clear=True,
|
789 |
+
size="small"
|
790 |
+
)
|
791 |
+
|
792 |
+
with antd.CollapsePanel(header="그래픽", key="graphic"):
|
793 |
+
graphic_option = antd.RadioGroup(
|
794 |
+
choices=["선택안함", "미니멀", "픽셀", "카툰", "플랫"],
|
795 |
+
default_value="선택안함"
|
796 |
+
)
|
797 |
+
graphic_custom = antd.Input(
|
798 |
+
placeholder="그래픽 스타일에 대한 추가 요구사항 (선택)",
|
799 |
+
allow_clear=True,
|
800 |
+
size="small"
|
801 |
+
)
|
802 |
+
|
803 |
+
with antd.CollapsePanel(header="게임 메커닉", key="mechanic"):
|
804 |
+
mechanic_option = antd.RadioGroup(
|
805 |
+
choices=["선택안함", "타이밍", "충돌", "타일", "물리"],
|
806 |
+
default_value="선택안함"
|
807 |
+
)
|
808 |
+
mechanic_custom = antd.Input(
|
809 |
+
placeholder="게임 메커닉 추가 요구사항 (선택)",
|
810 |
+
allow_clear=True,
|
811 |
+
size="small"
|
812 |
+
)
|
813 |
+
|
814 |
+
with antd.CollapsePanel(header="게임 관점(뷰)", key="view"):
|
815 |
+
view_option = antd.RadioGroup(
|
816 |
+
choices=["선택안함", "탑다운", "사이드뷰", "아이소메트릭", "1인칭", "고정 화면"],
|
817 |
+
default_value="선택안함"
|
818 |
+
)
|
819 |
+
view_custom = antd.Input(
|
820 |
+
placeholder="게임 뷰에 대한 추가 요구사항 (선택)",
|
821 |
+
allow_clear=True,
|
822 |
+
size="small"
|
823 |
+
)
|
824 |
|
825 |
with antd.Row(gutter=[32, 12]) as layout:
|
826 |
with antd.Col(span=24, md=8):
|
|
|
863 |
def execute_code(query: str):
|
864 |
if not query or query.strip() == '':
|
865 |
return None, gr.update(active_key="empty")
|
|
|
866 |
try:
|
867 |
if '```html' in query and '```' in query:
|
868 |
code = remove_code_block(query)
|
|
|
886 |
outputs=[code_drawer]
|
887 |
)
|
888 |
|
889 |
+
with antd.Drawer(open=False, placement="left", width="750px") as code_drawer:
|
890 |
+
code_output = legacy.Markdown()
|
891 |
+
|
892 |
code_drawer.close(
|
893 |
lambda: gr.update(open=False),
|
894 |
inputs=[],
|
|
|
898 |
historyBtn.click(
|
899 |
history_render,
|
900 |
inputs=[history],
|
901 |
+
outputs=[history_drawer, None]
|
902 |
)
|
903 |
|
904 |
+
with antd.Drawer(open=False, placement="left", width="900px") as history_drawer:
|
905 |
+
history_output = legacy.Chatbot(show_label=False, flushing=False, height=960, elem_classes="history_chatbot")
|
906 |
+
|
907 |
history_drawer.close(
|
908 |
lambda: gr.update(open=False),
|
909 |
inputs=[],
|
|
|
928 |
queue=False
|
929 |
)
|
930 |
|
931 |
+
with antd.Drawer(open=False, placement="right", width="900px", elem_classes="session-drawer") as session_drawer:
|
932 |
+
with antd.Flex(vertical=True, gap="middle"):
|
933 |
+
session_history = gr.HTML(elem_classes="session-history")
|
934 |
+
close_btn = antd.Button("Close", type="default", elem_classes="close-btn")
|
935 |
+
|
936 |
session_drawer.close(
|
937 |
lambda: (gr.update(open=False), gr.HTML("")),
|
938 |
outputs=[session_drawer, session_history]
|
|
|
946 |
btn.click(
|
947 |
demo_instance.generation_code,
|
948 |
inputs=[
|
949 |
+
input_prompt,
|
950 |
+
setting,
|
951 |
history,
|
952 |
genre_option, genre_custom,
|
953 |
difficulty_option, difficulty_custom,
|