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,
|