Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| import json | |
| import time | |
| from datetime import datetime | |
| # 모듈 임포트 | |
| from modules.image_analyzer import analyze_image | |
| from modules.question_generator import generate_questions | |
| from modules.persona_generator import generate_persona | |
| from modules.chat_engine import start_conversation, process_message | |
| from modules.data_manager import save_persona, load_persona, list_personas, save_conversation | |
| from modules.gemini_handler import get_persona_enhancement | |
| # 디렉토리 확인 | |
| try: | |
| for dir_path in [ | |
| "data/user_personas", | |
| "data/conversation_logs", | |
| "assets/examples" | |
| ]: | |
| os.makedirs(dir_path, exist_ok=True) | |
| except Exception as e: | |
| print(f"초기 디렉토리 생성 중 오류: {str(e)}") | |
| print("허깅페이스 환경에서는 일부 파일 저장 기능이 제한될 수 있습니다.") | |
| # 기본 성격 특성 범위 | |
| trait_range = {"minimum": 0, "maximum": 100, "step": 5, "value": 50} | |
| # UI 테마 및 스타일 설정 | |
| theme = gr.themes.Soft( | |
| primary_hue="indigo", | |
| secondary_hue="blue", | |
| ).set( | |
| button_primary_background_fill="*primary_500", | |
| button_primary_background_fill_hover="*primary_600", | |
| button_primary_text_color="white", | |
| block_title_text_color="*primary_800", | |
| block_label_text_size="sm" | |
| ) | |
| # 메인 애플리케이션 | |
| with gr.Blocks(title="사물 페르소나 생성기", theme=theme, css="styles/custom.css") as app: | |
| gr.Markdown( | |
| """ | |
| # 🧸 물魂 (물건의 혼) - 사물 페르소나 생성기 | |
| 일상 사물에 개성과 성격을 부여하여 대화할 수 있는 페르소나 생성 및 테스트 도구입니다. | |
| """ | |
| ) | |
| # 전역 상태 관리 | |
| current_persona = gr.State({}) | |
| conversation_history = gr.State([]) | |
| session_start_time = gr.State(None) | |
| with gr.Tabs() as tabs: | |
| # 페르소나 생성 탭 | |
| with gr.Tab("페르소나 생성"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| gr.Markdown("## 사물 정보") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### 사물 이미지 (선택사항)") | |
| object_image = gr.File( | |
| file_types=["image"], | |
| file_count="single", | |
| type="filepath", | |
| elem_id="object_file_uploader" | |
| ) | |
| image_analysis_btn = gr.Button("이미지 분석하기") | |
| with gr.Row(): | |
| object_name = gr.Textbox( | |
| label="사물 이름", | |
| placeholder="예: 할아버지의 낡은 안락의자" | |
| ) | |
| object_type = gr.Dropdown( | |
| choices=[ | |
| "전자기기", "가구", "주방용품", | |
| "의류/액세서리", "책/문구류", | |
| "음악 기구", "장난감", "기타" | |
| ], | |
| label="사물 유형" | |
| ) | |
| object_age = gr.Textbox( | |
| label="사물 나이/사용 기간", | |
| placeholder="예: 15년" | |
| ) | |
| object_description = gr.Textbox( | |
| label="사물 설명", | |
| placeholder="물체의 외형, 특징, 용도 등을 설명해주세요.", | |
| lines=3 | |
| ) | |
| image_analysis_result = gr.JSON( | |
| label="이미지 분석 결과", | |
| visible=False | |
| ) | |
| with gr.Column(scale=3): | |
| gr.Markdown("## 성격 특성") | |
| with gr.Row(): | |
| with gr.Column(): | |
| warmth = gr.Slider( | |
| label="온기", | |
| info="따뜻함, 친절함, 선의의 정도", | |
| **trait_range | |
| ) | |
| competence = gr.Slider( | |
| label="능력", | |
| info="효율성, 기술, 지능의 정도", | |
| **trait_range | |
| ) | |
| trustworthiness = gr.Slider( | |
| label="신뢰성", | |
| info="일관성, 정직함, 안정성의 정도", | |
| **trait_range | |
| ) | |
| with gr.Column(): | |
| friendliness = gr.Slider( | |
| label="친화성", | |
| info="사교성, 개방성, 접근성의 정도", | |
| **trait_range | |
| ) | |
| creativity = gr.Slider( | |
| label="창의성", | |
| info="독창성, 상상력, 혁신성의 정도", | |
| **trait_range | |
| ) | |
| humor = gr.Slider( | |
| label="유머감각", | |
| info="재치, 위트, 유머 표현 정도", | |
| **trait_range | |
| ) | |
| gr.Markdown("## 매력적 결함") | |
| flaws = gr.CheckboxGroup( | |
| choices=[ | |
| "때때로 너무 조심스러움", | |
| "가끔 과하게 열정적", | |
| "약간의 완벽주의", | |
| "때로는 우울한 생각에 빠짐", | |
| "간혹 산만해짐", | |
| "약간의 고집", | |
| "때로는 지나치게 솔직함", | |
| "가끔 불안해함", | |
| "종종 과거에 집착함", | |
| "가끔 너무 이상적임" | |
| ], | |
| label="매력적인 결함 (최대 2개 선택)", | |
| ) | |
| gr.Markdown("## 소통 방식") | |
| with gr.Row(): | |
| with gr.Column(): | |
| communication_style = gr.Radio( | |
| choices=[ | |
| "활발하고 에너지 넘치는", | |
| "차분하고 사려깊은", | |
| "위트있고 재치있는", | |
| "따뜻하고 공감적인", | |
| "논리적이고 분석적인" | |
| ], | |
| label="대화 스타일", | |
| value="따뜻하고 공감적인" | |
| ) | |
| humor_style = gr.Dropdown( | |
| choices=[ | |
| "재치있는 말장난", | |
| "상황적 유머", | |
| "자기 비하적 유머", | |
| "가벼운 농담", | |
| "블랙 유머", | |
| "유머 거의 없음" | |
| ], | |
| multiselect=True, | |
| label="유머 유형" | |
| ) | |
| with gr.Column(): | |
| speech_pattern = gr.Textbox( | |
| label="말투 패턴 예시", | |
| placeholder="캐릭터의 특징적인 말투나 표현을 입력하세요", | |
| lines=2 | |
| ) | |
| interests = gr.Textbox( | |
| label="관심사 (쉼표로 구분)", | |
| placeholder="음악, 요리, 여행, 역사..." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("## 관계 성향") | |
| with gr.Row(): | |
| attachment_style = gr.Radio( | |
| choices=["안정형", "불안형", "회피형", "혼란형"], | |
| label="애착 스타일", | |
| value="안정형" | |
| ) | |
| relationship_depth = gr.Radio( | |
| choices=["얕은", "중간", "깊은"], | |
| label="관계 깊이 선호도", | |
| value="중간" | |
| ) | |
| initial_attitude = gr.Radio( | |
| choices=["수줍은", "중립적", "친근한", "열정적", "조심스러운"], | |
| label="초기 태도", | |
| value="친근한" | |
| ) | |
| with gr.Column(): | |
| backstory = gr.Textbox( | |
| label="배경 이야기", | |
| placeholder="이 사물의 역사, 경험, 소유자와의 관계 등을 간략히 서술하세요", | |
| lines=4 | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| template_selector = gr.Dropdown( | |
| choices=[ | |
| "템플릿 없음", | |
| "스마트폰 - 열정적 조력자", | |
| "오래된 안락의자 - 지혜로운 관찰자", | |
| "커피머신 - 활기찬 에너자이저", | |
| "오래된 책 - 사려깊은 학자", | |
| "가죽 가방 - 신뢰할 수 있는 동반자" | |
| ], | |
| label="템플릿 선택", | |
| value="템플릿 없음" | |
| ) | |
| load_template_btn = gr.Button("템플릿 불러오기") | |
| with gr.Column(): | |
| create_btn = gr.Button("페르소나 생성하기", variant="primary") | |
| # 생성된 페르소나 출력 | |
| generated_persona = gr.JSON(label="생성된 페르소나") | |
| # 이벤트 핸들러 | |
| image_analysis_btn.click( | |
| fn=analyze_image, | |
| inputs=[object_image], | |
| outputs=[image_analysis_result, warmth, competence, trustworthiness, | |
| friendliness, creativity, humor, object_type, object_description] | |
| ) | |
| create_btn.click( | |
| fn=generate_persona, | |
| inputs=[ | |
| object_name, object_type, object_age, object_description, | |
| warmth, competence, trustworthiness, friendliness, creativity, humor, | |
| flaws, communication_style, humor_style, speech_pattern, interests, | |
| attachment_style, relationship_depth, initial_attitude, backstory, | |
| image_analysis_result | |
| ], | |
| outputs=[generated_persona, current_persona] | |
| ) | |
| # 템플릿 로드 기능 추가 예정 | |
| # 대화 테스트 탭 | |
| with gr.Tab("대화 테스트"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| chat_display = gr.Chatbot( | |
| label="대화 내용", | |
| height=500, | |
| avatar_images=(None, "assets/icons/persona_avatar.png") | |
| ) | |
| with gr.Row(): | |
| user_message = gr.Textbox( | |
| label="메시지 입력", | |
| placeholder="메시지를 입력하세요...", | |
| lines=2 | |
| ) | |
| send_btn = gr.Button("전송", variant="primary") | |
| with gr.Column(scale=1): | |
| gr.Markdown("## 현재 페르소나") | |
| persona_info = gr.Markdown("페르소나를 먼저 생성하거나 불러오세요.") | |
| start_chat_btn = gr.Button("대화 시작하기") | |
| load_persona_btn = gr.Button("저장된 페르소나 불러오기") | |
| gr.Markdown("## 대화 통계") | |
| chat_stats = gr.Markdown("대화가 시작되지 않았습니다.") | |
| export_chat_btn = gr.Button("대화 내용 저장") | |
| export_result = gr.Textbox(label="저장 결과", visible=False) | |
| # 이벤트 핸들러 | |
| start_chat_btn.click( | |
| fn=start_conversation, | |
| inputs=[current_persona], | |
| outputs=[chat_display, conversation_history, session_start_time, chat_stats, persona_info] | |
| ) | |
| send_btn.click( | |
| fn=process_message, | |
| inputs=[user_message, conversation_history, current_persona, session_start_time], | |
| outputs=[chat_display, conversation_history, user_message, chat_stats] | |
| ) | |
| # Enter 키로 메시지 전송 | |
| user_message.submit( | |
| fn=process_message, | |
| inputs=[user_message, conversation_history, current_persona, session_start_time], | |
| outputs=[chat_display, conversation_history, user_message, chat_stats] | |
| ) | |
| # 페르소나 라이브러리 탭 | |
| with gr.Tab("페르소나 라이브러리"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| refresh_library_btn = gr.Button("라이브러리 새로고침") | |
| personas_table = gr.Dataframe( | |
| headers=["이름", "유형", "생성일", "파일명"], | |
| datatype=["str", "str", "str", "str"], | |
| label="저장된 페르소나 목록" | |
| ) | |
| with gr.Column(): | |
| selected_persona_json = gr.JSON(label="선택된 페르소나 정보") | |
| load_to_editor_btn = gr.Button("에디터로 불러오기") | |
| load_to_chat_btn = gr.Button("대화 테스트로 불러오기") | |
| # 이벤트 핸들러 | |
| refresh_library_btn.click( | |
| fn=list_personas, | |
| inputs=[], | |
| outputs=[personas_table] | |
| ) | |
| # 선택한 페르소나 로드 기능 추가 예정 | |
| # 앱 실행 | |
| if __name__ == "__main__": | |
| app.launch() |