Update app.py
Browse files
app.py
CHANGED
@@ -577,7 +577,7 @@ def safe_state_update(state, updates):
|
|
577 |
def create_interface():
|
578 |
import base64
|
579 |
|
580 |
-
# initial_state 정의
|
581 |
initial_state = {
|
582 |
"user_name": "",
|
583 |
"baseline_features": None,
|
@@ -585,63 +585,106 @@ def create_interface():
|
|
585 |
"wish": None,
|
586 |
"final_prompt": "",
|
587 |
"image_path": None,
|
588 |
-
"current_tab": 0
|
|
|
589 |
}
|
590 |
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
|
|
604 |
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
display: block !important;
|
619 |
}
|
620 |
}
|
621 |
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
max-width: 800px;
|
628 |
-
margin: 0 auto;
|
629 |
-
}
|
630 |
-
.mobile-logo { display: none !important; }
|
631 |
-
.desktop-logo {
|
632 |
-
width: 100%;
|
633 |
-
height: auto;
|
634 |
-
max-width: 800px;
|
635 |
-
margin: 0 auto;
|
636 |
-
display: block;
|
637 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
638 |
}
|
639 |
|
640 |
-
/*
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
645 |
}
|
646 |
"""
|
647 |
|
@@ -712,35 +755,33 @@ def create_interface():
|
|
712 |
💫 이 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며,
|
713 |
온천천 온천장역에서 장전역까지 걸으며 더 깊은 체험이 가능합니다.
|
714 |
""")
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
721 |
interactive=False,
|
722 |
-
|
723 |
-
|
724 |
)
|
725 |
-
with gr.Column():
|
726 |
-
reflection_input = gr.Textbox(
|
727 |
-
label="지금 이 순간의 감상을 자유롭게 적어보세요",
|
728 |
-
lines=3,
|
729 |
-
max_lines=5
|
730 |
-
)
|
731 |
-
save_btn = gr.Button("감상 저장하기", variant="secondary")
|
732 |
-
reflections_display = gr.Dataframe(
|
733 |
-
headers=["시간", "감상", "감정 분석"],
|
734 |
-
label="기록된 감상들",
|
735 |
-
value=[], # 초기값은 빈 리스트
|
736 |
-
interactive=False,
|
737 |
-
wrap=True,
|
738 |
-
row_count=(5, "dynamic") # 동적으로 행 수 조정
|
739 |
-
)
|
740 |
|
741 |
# 기원 탭
|
742 |
with gr.TabItem("기원") as tab_wish:
|
743 |
gr.Markdown("## 기원 - 소원을 전해보세요")
|
|
|
|
|
|
|
|
|
744 |
with gr.Row():
|
745 |
with gr.Column():
|
746 |
voice_input = gr.Audio(
|
@@ -944,7 +985,29 @@ def create_interface():
|
|
944 |
except Exception as e:
|
945 |
print(f"Error saving wish: {e}")
|
946 |
return "오류가 발생했습니다.", []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
947 |
|
|
|
948 |
# 이벤트 연결
|
949 |
name_submit_btn.click(
|
950 |
fn=handle_name_submit,
|
@@ -985,9 +1048,9 @@ def create_interface():
|
|
985 |
)
|
986 |
|
987 |
analyze_btn.click(
|
988 |
-
fn=
|
989 |
inputs=[voice_input, state],
|
990 |
-
outputs=[state, transcribed_text, voice_emotion, text_emotion, final_prompt]
|
991 |
)
|
992 |
|
993 |
generate_btn.click(
|
@@ -1004,16 +1067,83 @@ def create_interface():
|
|
1004 |
return app
|
1005 |
|
1006 |
if __name__ == "__main__":
|
1007 |
-
#
|
1008 |
-
|
1009 |
-
|
1010 |
-
|
1011 |
-
|
1012 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1013 |
|
1014 |
-
# Gradio
|
1015 |
demo = create_interface()
|
1016 |
-
demo.launch(
|
1017 |
server_name="0.0.0.0",
|
1018 |
server_port=7860,
|
1019 |
share=True,
|
|
|
577 |
def create_interface():
|
578 |
import base64
|
579 |
|
580 |
+
# initial_state 정의
|
581 |
initial_state = {
|
582 |
"user_name": "",
|
583 |
"baseline_features": None,
|
|
|
585 |
"wish": None,
|
586 |
"final_prompt": "",
|
587 |
"image_path": None,
|
588 |
+
"current_tab": 0,
|
589 |
+
"audio_playing": False # 오디오 상태 추가
|
590 |
}
|
591 |
|
592 |
+
# HTML5 Audio Player 템플릿
|
593 |
+
AUDIO_PLAYER_HTML = """
|
594 |
+
<div class="audio-player-container">
|
595 |
+
<audio id="mainAudio" preload="auto" style="width: 100%;">
|
596 |
+
<source src="/assets/main_music.mp3" type="audio/mp3">
|
597 |
+
Your browser does not support the audio element.
|
598 |
+
</audio>
|
599 |
+
<button id="playButton" onclick="togglePlay()" class="custom-audio-button">
|
600 |
+
재생/일시정지
|
601 |
+
</button>
|
602 |
+
</div>
|
603 |
+
<script>
|
604 |
+
let audioElement = document.getElementById('mainAudio');
|
605 |
+
let isPlaying = false;
|
606 |
|
607 |
+
function togglePlay() {
|
608 |
+
if (!isPlaying) {
|
609 |
+
audioElement.play()
|
610 |
+
.then(() => {
|
611 |
+
isPlaying = true;
|
612 |
+
})
|
613 |
+
.catch(error => {
|
614 |
+
console.error("Audio playback failed:", error);
|
615 |
+
alert("음악 재생에 실패했습니다. 다시 시도해주세요.");
|
616 |
+
});
|
617 |
+
} else {
|
618 |
+
audioElement.pause();
|
619 |
+
isPlaying = false;
|
|
|
620 |
}
|
621 |
}
|
622 |
|
623 |
+
// 페이지 벗어날 때 오디오 정지
|
624 |
+
window.addEventListener('beforeunload', function() {
|
625 |
+
if (audioElement) {
|
626 |
+
audioElement.pause();
|
627 |
+
audioElement.currentTime = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
628 |
}
|
629 |
+
});
|
630 |
+
|
631 |
+
// 오디오 로딩 에러 처리
|
632 |
+
audioElement.addEventListener('error', function(e) {
|
633 |
+
console.error("Audio error:", e);
|
634 |
+
alert("음악 파일을 불러오는데 실패했습니다. 페이지를 새로고침해주세요.");
|
635 |
+
});
|
636 |
+
</script>
|
637 |
+
<style>
|
638 |
+
.custom-audio-button {
|
639 |
+
width: 100%;
|
640 |
+
padding: 10px;
|
641 |
+
margin: 10px 0;
|
642 |
+
background-color: #4a90e2;
|
643 |
+
color: white;
|
644 |
+
border: none;
|
645 |
+
border-radius: 5px;
|
646 |
+
cursor: pointer;
|
647 |
+
font-size: 16px;
|
648 |
+
}
|
649 |
+
.custom-audio-button:active {
|
650 |
+
background-color: #357abd;
|
651 |
+
}
|
652 |
+
.audio-player-container {
|
653 |
+
margin: 20px 0;
|
654 |
+
width: 100%;
|
655 |
+
}
|
656 |
+
</style>
|
657 |
+
"""
|
658 |
+
|
659 |
+
css = """
|
660 |
+
/* 기존 CSS 유지 */
|
661 |
+
.gradio-container {
|
662 |
+
max-width: 100% !important;
|
663 |
+
padding: 0 !important;
|
664 |
+
}
|
665 |
+
|
666 |
+
.audio-container {
|
667 |
+
margin: 20px 0;
|
668 |
+
width: 100%;
|
669 |
}
|
670 |
|
671 |
+
/* 모바일 최적화 */
|
672 |
+
@media (max-width: 600px) {
|
673 |
+
.gradio-button {
|
674 |
+
min-height: 44px !important; /* iOS 터치 타겟 사이즈 */
|
675 |
+
margin: 10px 0 !important;
|
676 |
+
}
|
677 |
+
|
678 |
+
.container {
|
679 |
+
padding: 10px !important;
|
680 |
+
}
|
681 |
+
|
682 |
+
/* 입력 필드 최적화 */
|
683 |
+
input[type="text"],
|
684 |
+
textarea {
|
685 |
+
font-size: 16px !important; /* iOS auto-zoom 방지 */
|
686 |
+
padding: 12px !important;
|
687 |
+
}
|
688 |
}
|
689 |
"""
|
690 |
|
|
|
755 |
💫 이 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며,
|
756 |
온천천 온천장역에서 장전역까지 걸으며 더 깊은 체험이 가능합니다.
|
757 |
""")
|
758 |
+
|
759 |
+
# 커스텀 오디오 플레이어로 교체
|
760 |
+
gr.HTML(AUDIO_PLAYER_HTML)
|
761 |
+
|
762 |
+
with gr.Column():
|
763 |
+
reflection_input = gr.Textbox(
|
764 |
+
label="지금 이 순간의 감상을 자유롭게 적어보세요",
|
765 |
+
lines=3,
|
766 |
+
max_lines=5
|
767 |
+
)
|
768 |
+
save_btn = gr.Button("감상 저장하기", variant="secondary")
|
769 |
+
reflections_display = gr.Dataframe(
|
770 |
+
headers=["시간", "감상", "감정 분석"],
|
771 |
+
label="기록된 감상들",
|
772 |
+
value=[],
|
773 |
interactive=False,
|
774 |
+
wrap=True,
|
775 |
+
row_count=(5, "dynamic")
|
776 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
777 |
|
778 |
# 기원 탭
|
779 |
with gr.TabItem("기원") as tab_wish:
|
780 |
gr.Markdown("## 기원 - 소원을 전해보세요")
|
781 |
+
|
782 |
+
# 상태 표시 추가
|
783 |
+
processing_status = gr.Markdown("", visible=False)
|
784 |
+
|
785 |
with gr.Row():
|
786 |
with gr.Column():
|
787 |
voice_input = gr.Audio(
|
|
|
985 |
except Exception as e:
|
986 |
print(f"Error saving wish: {e}")
|
987 |
return "오류가 발생했습니다.", []
|
988 |
+
|
989 |
+
def safe_analyze_voice(audio_data, state):
|
990 |
+
"""음성 분석 함수에 안전장치 추가"""
|
991 |
+
if audio_data is None:
|
992 |
+
return state, "음성을 먼저 녹음해주세요.", "", "", "", gr.update(visible=False)
|
993 |
+
|
994 |
+
try:
|
995 |
+
processing_status.update(value="분석 중입니다...", visible=True)
|
996 |
+
result = analyze_voice(audio_data, state)
|
997 |
+
processing_status.update(value="", visible=False)
|
998 |
+
return (*result, gr.update(visible=False))
|
999 |
+
except Exception as e:
|
1000 |
+
print(f"Voice analysis error: {str(e)}")
|
1001 |
+
return (
|
1002 |
+
state,
|
1003 |
+
"음성 분석 중 오류가 발생했습니다. 다시 시도해주세요.",
|
1004 |
+
"",
|
1005 |
+
"",
|
1006 |
+
"",
|
1007 |
+
gr.update(visible=False)
|
1008 |
+
)
|
1009 |
|
1010 |
+
|
1011 |
# 이벤트 연결
|
1012 |
name_submit_btn.click(
|
1013 |
fn=handle_name_submit,
|
|
|
1048 |
)
|
1049 |
|
1050 |
analyze_btn.click(
|
1051 |
+
fn=safe_analyze_voice,
|
1052 |
inputs=[voice_input, state],
|
1053 |
+
outputs=[state, transcribed_text, voice_emotion, text_emotion, final_prompt, processing_status]
|
1054 |
)
|
1055 |
|
1056 |
generate_btn.click(
|
|
|
1067 |
return app
|
1068 |
|
1069 |
if __name__ == "__main__":
|
1070 |
+
# 서비스 워커 캐시 설정 강화
|
1071 |
+
sw_content = """
|
1072 |
+
const CACHE_NAME = 'digital-gutpan-v2';
|
1073 |
+
const urlsToCache = [
|
1074 |
+
'/',
|
1075 |
+
'/assets/main_music.mp3',
|
1076 |
+
'/static/icons/icon-72x72.png',
|
1077 |
+
'/static/icons/icon-96x96.png',
|
1078 |
+
'/static/icons/icon-128x128.png',
|
1079 |
+
'/static/icons/icon-144x144.png',
|
1080 |
+
'/static/icons/icon-152x152.png',
|
1081 |
+
'/static/icons/icon-192x192.png',
|
1082 |
+
'/static/icons/icon-384x384.png',
|
1083 |
+
'/static/icons/icon-512x512.png'
|
1084 |
+
];
|
1085 |
+
|
1086 |
+
self.addEventListener('install', event => {
|
1087 |
+
event.waitUntil(
|
1088 |
+
caches.open(CACHE_NAME)
|
1089 |
+
.then(cache => {
|
1090 |
+
console.log('Opened cache');
|
1091 |
+
return cache.addAll(urlsToCache);
|
1092 |
+
})
|
1093 |
+
.then(() => self.skipWaiting())
|
1094 |
+
);
|
1095 |
+
});
|
1096 |
+
|
1097 |
+
self.addEventListener('activate', event => {
|
1098 |
+
event.waitUntil(
|
1099 |
+
caches.keys().then(cacheNames => {
|
1100 |
+
return Promise.all(
|
1101 |
+
cacheNames.map(cacheName => {
|
1102 |
+
if (cacheName !== CACHE_NAME) {
|
1103 |
+
return caches.delete(cacheName);
|
1104 |
+
}
|
1105 |
+
})
|
1106 |
+
);
|
1107 |
+
}).then(() => self.clients.claim())
|
1108 |
+
);
|
1109 |
+
});
|
1110 |
+
|
1111 |
+
self.addEventListener('fetch', event => {
|
1112 |
+
event.respondWith(
|
1113 |
+
caches.match(event.request)
|
1114 |
+
.then(response => {
|
1115 |
+
if (response) {
|
1116 |
+
return response;
|
1117 |
+
}
|
1118 |
+
|
1119 |
+
return fetch(event.request).then(
|
1120 |
+
response => {
|
1121 |
+
if(!response || response.status !== 200 || response.type !== 'basic') {
|
1122 |
+
return response;
|
1123 |
+
}
|
1124 |
+
|
1125 |
+
const responseToCache = response.clone();
|
1126 |
+
|
1127 |
+
caches.open(CACHE_NAME)
|
1128 |
+
.then(cache => {
|
1129 |
+
cache.put(event.request, responseToCache);
|
1130 |
+
});
|
1131 |
+
|
1132 |
+
return response;
|
1133 |
+
}
|
1134 |
+
);
|
1135 |
+
})
|
1136 |
+
);
|
1137 |
+
});
|
1138 |
+
"""
|
1139 |
+
|
1140 |
+
# 서비스 워커 파일 생성
|
1141 |
+
with open('static/service-worker.js', 'w') as f:
|
1142 |
+
f.write(sw_content)
|
1143 |
|
1144 |
+
# Gradio 앱 실행
|
1145 |
demo = create_interface()
|
1146 |
+
demo.queue(concurrency_count=4).launch(
|
1147 |
server_name="0.0.0.0",
|
1148 |
server_port=7860,
|
1149 |
share=True,
|