Spaces:
Running
Running
FauziIsyrinApridal
commited on
Commit
Β·
56bc549
1
Parent(s):
d461eb9
..
Browse files- app/chat.py +55 -82
app/chat.py
CHANGED
@@ -6,28 +6,25 @@ import asyncio
|
|
6 |
import edge_tts
|
7 |
import gtts
|
8 |
from hashlib import md5
|
|
|
|
|
9 |
import os
|
10 |
import glob
|
11 |
import time
|
12 |
from dotenv import load_dotenv
|
13 |
-
from app.db import supabase
|
14 |
|
15 |
load_dotenv()
|
16 |
|
17 |
-
#
|
18 |
-
# Pembersihan cache audio lama
|
19 |
-
# ----------------------------
|
20 |
def clean_old_cache(tts_dir="cache_tts", max_age_hours=12):
|
21 |
now = time.time()
|
22 |
for f in glob.glob(os.path.join(tts_dir, "*.mp3")):
|
23 |
if os.stat(f).st_mtime < now - max_age_hours * 3600:
|
24 |
os.remove(f)
|
25 |
|
|
|
26 |
clean_old_cache()
|
27 |
|
28 |
-
# ----------------------------
|
29 |
-
# Simpan feedback ke Supabase
|
30 |
-
# ----------------------------
|
31 |
def save_feedback_to_supabase(feedback_text):
|
32 |
try:
|
33 |
data = {"message": feedback_text}
|
@@ -37,9 +34,6 @@ def save_feedback_to_supabase(feedback_text):
|
|
37 |
st.error(f"Gagal menyimpan feedback: {e}")
|
38 |
return False
|
39 |
|
40 |
-
# ----------------------------
|
41 |
-
# State awal
|
42 |
-
# ----------------------------
|
43 |
def initialize_session_state():
|
44 |
if 'history' not in st.session_state:
|
45 |
st.session_state['history'] = []
|
@@ -47,59 +41,59 @@ def initialize_session_state():
|
|
47 |
st.session_state['generated'] = ["Halo! Saya bisa membantu anda menjawab pertanyaan seputar Politeknik Negeri Padang!"]
|
48 |
if 'past' not in st.session_state:
|
49 |
st.session_state['past'] = ["Hai! π"]
|
|
|
|
|
|
|
|
|
50 |
if 'should_speak' not in st.session_state:
|
51 |
st.session_state['should_speak'] = True
|
52 |
if 'input_text' not in st.session_state:
|
53 |
st.session_state['input_text'] = ""
|
54 |
-
if '
|
55 |
-
st.session_state['
|
56 |
-
if '
|
57 |
-
st.session_state['
|
58 |
-
|
59 |
-
#
|
60 |
-
# TTS gTTS β Edge fallback
|
61 |
-
# ----------------------------
|
62 |
async def generate_audio_edge(text, path, voice="id-ID-GadisNeural"):
|
63 |
communicate = edge_tts.Communicate(text, voice=voice)
|
64 |
await communicate.save(path)
|
65 |
|
66 |
-
|
|
|
67 |
cache_dir = "cache_tts"
|
68 |
os.makedirs(cache_dir, exist_ok=True)
|
69 |
filename = f"{md5(text.encode()).hexdigest()}.mp3"
|
70 |
-
|
71 |
|
72 |
-
def text_to_speech(text):
|
73 |
-
path = get_tts_path(text)
|
74 |
if not os.path.exists(path):
|
75 |
try:
|
|
|
76 |
tts = gtts.gTTS(text, lang="id")
|
77 |
tts.save(path)
|
78 |
except Exception as e:
|
79 |
print(f"[gTTS gagal] {e}")
|
80 |
try:
|
|
|
81 |
asyncio.run(generate_audio_edge(text, path))
|
82 |
except Exception as e2:
|
83 |
-
print(f"[Edge-TTS gagal] {e2}")
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
<audio {autoplay_attr} style="display:none;">
|
94 |
<source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3">
|
95 |
</audio>
|
96 |
-
"""
|
97 |
-
|
98 |
-
|
|
|
99 |
|
100 |
-
# ----------------------------
|
101 |
-
# Chat & TTS Bubble
|
102 |
-
# ----------------------------
|
103 |
def conversation_chat(query, chain, history):
|
104 |
result = chain({"question": query, "chat_history": history})
|
105 |
history.append((query, result["answer"]))
|
@@ -107,15 +101,21 @@ def conversation_chat(query, chain, history):
|
|
107 |
|
108 |
def display_chat_history(chain):
|
109 |
reply_container = st.container()
|
|
|
110 |
user_input_obj = st.chat_input("Masukkan pertanyaan", key="chat_input_field")
|
111 |
|
112 |
col2, col3 = st.columns([1, 1])
|
|
|
|
|
113 |
with col2:
|
114 |
-
if st.button("π
|
115 |
-
key="toggle_tts",
|
|
|
|
|
116 |
st.session_state['should_speak'] = not st.session_state['should_speak']
|
117 |
st.experimental_rerun()
|
118 |
|
|
|
119 |
with col3:
|
120 |
stt_text = speech_to_text(
|
121 |
start_prompt="π€ Input Suara",
|
@@ -126,10 +126,12 @@ def display_chat_history(chain):
|
|
126 |
use_container_width=True,
|
127 |
)
|
128 |
|
|
|
129 |
if stt_text:
|
130 |
st.session_state.input_text = stt_text
|
131 |
st.experimental_rerun()
|
132 |
|
|
|
133 |
user_input = user_input_obj or st.session_state.get("input_text", "")
|
134 |
|
135 |
if user_input:
|
@@ -140,48 +142,19 @@ def display_chat_history(chain):
|
|
140 |
st.session_state['generated'].append(output)
|
141 |
st.session_state.input_text = ""
|
142 |
|
143 |
-
#
|
144 |
-
|
145 |
-
|
146 |
-
st.session_state['
|
147 |
-
if st.session_state['should_speak']:
|
148 |
-
st.session_state['last_tts'] = output
|
149 |
|
150 |
-
# Tampilkan
|
151 |
if st.session_state['generated']:
|
152 |
with reply_container:
|
153 |
for i in range(len(st.session_state['generated'])):
|
154 |
-
|
155 |
-
message(st.session_state["
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
if bot_text in st.session_state['tts_cache']:
|
162 |
-
audio_id = f"audio_{i}"
|
163 |
-
audio_path = st.session_state['tts_cache'][bot_text]
|
164 |
-
with open(audio_path, "rb") as audio_file:
|
165 |
-
audio_base64 = base64.b64encode(audio_file.read()).decode()
|
166 |
-
|
167 |
-
st.markdown(
|
168 |
-
f"""
|
169 |
-
<div style="text-align:right; margin-top:-30px; margin-bottom:10px;">
|
170 |
-
<button onclick="var audio = document.getElementById('{audio_id}'); audio.play();"
|
171 |
-
style="background:none; border:none; cursor:pointer; font-size:18px;">
|
172 |
-
π
|
173 |
-
</button>
|
174 |
-
<audio id="{audio_id}">
|
175 |
-
<source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3">
|
176 |
-
</audio>
|
177 |
-
</div>
|
178 |
-
""",
|
179 |
-
unsafe_allow_html=True
|
180 |
-
)
|
181 |
-
|
182 |
-
# Auto-play untuk jawaban baru
|
183 |
-
if st.session_state['last_tts']:
|
184 |
-
last_text = st.session_state['last_tts']
|
185 |
-
if last_text in st.session_state['tts_cache']:
|
186 |
-
play_audio_ui(st.session_state['tts_cache'][last_text], autoplay=True)
|
187 |
-
st.session_state['last_tts'] = None
|
|
|
6 |
import edge_tts
|
7 |
import gtts
|
8 |
from hashlib import md5
|
9 |
+
from io import BytesIO
|
10 |
+
from app.db import supabase
|
11 |
import os
|
12 |
import glob
|
13 |
import time
|
14 |
from dotenv import load_dotenv
|
|
|
15 |
|
16 |
load_dotenv()
|
17 |
|
18 |
+
# Bersihkan cache audio lama (opsional)
|
|
|
|
|
19 |
def clean_old_cache(tts_dir="cache_tts", max_age_hours=12):
|
20 |
now = time.time()
|
21 |
for f in glob.glob(os.path.join(tts_dir, "*.mp3")):
|
22 |
if os.stat(f).st_mtime < now - max_age_hours * 3600:
|
23 |
os.remove(f)
|
24 |
|
25 |
+
# Jalankan pembersihan saat startup
|
26 |
clean_old_cache()
|
27 |
|
|
|
|
|
|
|
28 |
def save_feedback_to_supabase(feedback_text):
|
29 |
try:
|
30 |
data = {"message": feedback_text}
|
|
|
34 |
st.error(f"Gagal menyimpan feedback: {e}")
|
35 |
return False
|
36 |
|
|
|
|
|
|
|
37 |
def initialize_session_state():
|
38 |
if 'history' not in st.session_state:
|
39 |
st.session_state['history'] = []
|
|
|
41 |
st.session_state['generated'] = ["Halo! Saya bisa membantu anda menjawab pertanyaan seputar Politeknik Negeri Padang!"]
|
42 |
if 'past' not in st.session_state:
|
43 |
st.session_state['past'] = ["Hai! π"]
|
44 |
+
if 'data_len' not in st.session_state:
|
45 |
+
st.session_state['data_len'] = 0
|
46 |
+
if 'vector_store' not in st.session_state:
|
47 |
+
st.session_state['vector_store'] = None
|
48 |
if 'should_speak' not in st.session_state:
|
49 |
st.session_state['should_speak'] = True
|
50 |
if 'input_text' not in st.session_state:
|
51 |
st.session_state['input_text'] = ""
|
52 |
+
if 'tts_output' not in st.session_state:
|
53 |
+
st.session_state['tts_output'] = ""
|
54 |
+
if 'tts_played' not in st.session_state:
|
55 |
+
st.session_state['tts_played'] = True # default True supaya tidak main saat awal
|
56 |
+
|
57 |
+
# edge-tts fallback (cadangan)
|
|
|
|
|
58 |
async def generate_audio_edge(text, path, voice="id-ID-GadisNeural"):
|
59 |
communicate = edge_tts.Communicate(text, voice=voice)
|
60 |
await communicate.save(path)
|
61 |
|
62 |
+
# fungsi utama TTS dengan fallback
|
63 |
+
def text_to_speech(text):
|
64 |
cache_dir = "cache_tts"
|
65 |
os.makedirs(cache_dir, exist_ok=True)
|
66 |
filename = f"{md5(text.encode()).hexdigest()}.mp3"
|
67 |
+
path = os.path.join(cache_dir, filename)
|
68 |
|
|
|
|
|
69 |
if not os.path.exists(path):
|
70 |
try:
|
71 |
+
# β
Utama: gTTS
|
72 |
tts = gtts.gTTS(text, lang="id")
|
73 |
tts.save(path)
|
74 |
except Exception as e:
|
75 |
print(f"[gTTS gagal] {e}")
|
76 |
try:
|
77 |
+
# β
Cadangan: edge-tts
|
78 |
asyncio.run(generate_audio_edge(text, path))
|
79 |
except Exception as e2:
|
80 |
+
print(f"[Edge-TTS juga gagal] {e2}")
|
81 |
+
st.warning("π Gagal membuat audio TTS.")
|
82 |
+
return ""
|
83 |
+
|
84 |
+
try:
|
85 |
+
with open(path, "rb") as audio_file:
|
86 |
+
audio_base64 = base64.b64encode(audio_file.read()).decode()
|
87 |
+
|
88 |
+
return f"""
|
89 |
+
<audio autoplay>
|
|
|
90 |
<source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3">
|
91 |
</audio>
|
92 |
+
"""
|
93 |
+
except Exception as e:
|
94 |
+
print(f"[Error saat membaca audio] {e}")
|
95 |
+
return ""
|
96 |
|
|
|
|
|
|
|
97 |
def conversation_chat(query, chain, history):
|
98 |
result = chain({"question": query, "chat_history": history})
|
99 |
history.append((query, result["answer"]))
|
|
|
101 |
|
102 |
def display_chat_history(chain):
|
103 |
reply_container = st.container()
|
104 |
+
|
105 |
user_input_obj = st.chat_input("Masukkan pertanyaan", key="chat_input_field")
|
106 |
|
107 |
col2, col3 = st.columns([1, 1])
|
108 |
+
|
109 |
+
# Tombol TTS Aktif / Nonaktif
|
110 |
with col2:
|
111 |
+
if st.button("π Text-to-Speech Aktif" if st.session_state['should_speak'] else "π Text-to-Speech Nonaktif",
|
112 |
+
key="toggle_tts",
|
113 |
+
help="Aktifkan/Nonaktifkan Text-to-Speech",
|
114 |
+
use_container_width=True):
|
115 |
st.session_state['should_speak'] = not st.session_state['should_speak']
|
116 |
st.experimental_rerun()
|
117 |
|
118 |
+
# Tombol Input Suara
|
119 |
with col3:
|
120 |
stt_text = speech_to_text(
|
121 |
start_prompt="π€ Input Suara",
|
|
|
126 |
use_container_width=True,
|
127 |
)
|
128 |
|
129 |
+
# Jika ada STT
|
130 |
if stt_text:
|
131 |
st.session_state.input_text = stt_text
|
132 |
st.experimental_rerun()
|
133 |
|
134 |
+
# Ambil input user
|
135 |
user_input = user_input_obj or st.session_state.get("input_text", "")
|
136 |
|
137 |
if user_input:
|
|
|
142 |
st.session_state['generated'].append(output)
|
143 |
st.session_state.input_text = ""
|
144 |
|
145 |
+
# Reset flag supaya TTS siap memutar lagi
|
146 |
+
if st.session_state['should_speak'] and output:
|
147 |
+
st.session_state['tts_output'] = output
|
148 |
+
st.session_state['tts_played'] = False
|
|
|
|
|
149 |
|
150 |
+
# Tampilkan Riwayat Chat
|
151 |
if st.session_state['generated']:
|
152 |
with reply_container:
|
153 |
for i in range(len(st.session_state['generated'])):
|
154 |
+
message(st.session_state["past"][i], is_user=True, key=str(i) + '_user', avatar_style="no-avatar")
|
155 |
+
message(st.session_state["generated"][i], key=str(i), avatar_style="no-avatar")
|
156 |
+
|
157 |
+
# Pemutaran TTS
|
158 |
+
if st.session_state.get('tts_output') and not st.session_state.get('tts_played'):
|
159 |
+
st.markdown(text_to_speech(st.session_state['tts_output']), unsafe_allow_html=True)
|
160 |
+
st.session_state['tts_played'] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|