Spaces:
Sleeping
Sleeping
FauziIsyrinApridal
commited on
Commit
Β·
d461eb9
1
Parent(s):
37841e6
update replay
Browse files- app/chat.py +82 -68
app/chat.py
CHANGED
@@ -6,25 +6,28 @@ import asyncio
|
|
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 |
-
#
|
|
|
|
|
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,6 +37,9 @@ def save_feedback_to_supabase(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,57 +47,59 @@ def initialize_session_state():
|
|
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 '
|
53 |
-
st.session_state['
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
56 |
async def generate_audio_edge(text, path, voice="id-ID-GadisNeural"):
|
57 |
communicate = edge_tts.Communicate(text, voice=voice)
|
58 |
await communicate.save(path)
|
59 |
|
60 |
-
|
61 |
-
def text_to_speech(text):
|
62 |
cache_dir = "cache_tts"
|
63 |
os.makedirs(cache_dir, exist_ok=True)
|
64 |
filename = f"{md5(text.encode()).hexdigest()}.mp3"
|
65 |
-
|
66 |
|
|
|
|
|
67 |
if not os.path.exists(path):
|
68 |
try:
|
69 |
-
# β
Utama: gTTS
|
70 |
tts = gtts.gTTS(text, lang="id")
|
71 |
tts.save(path)
|
72 |
except Exception as e:
|
73 |
print(f"[gTTS gagal] {e}")
|
74 |
try:
|
75 |
-
# β
Cadangan: edge-tts
|
76 |
asyncio.run(generate_audio_edge(text, path))
|
77 |
except Exception as e2:
|
78 |
-
print(f"[Edge-TTS
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
88 |
<source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3">
|
89 |
</audio>
|
90 |
-
"""
|
91 |
-
|
92 |
-
|
93 |
-
return ""
|
94 |
|
|
|
|
|
|
|
95 |
def conversation_chat(query, chain, history):
|
96 |
result = chain({"question": query, "chat_history": history})
|
97 |
history.append((query, result["answer"]))
|
@@ -99,21 +107,15 @@ def conversation_chat(query, chain, history):
|
|
99 |
|
100 |
def display_chat_history(chain):
|
101 |
reply_container = st.container()
|
102 |
-
|
103 |
user_input_obj = st.chat_input("Masukkan pertanyaan", key="chat_input_field")
|
104 |
|
105 |
col2, col3 = st.columns([1, 1])
|
106 |
-
|
107 |
-
# Tombol TTS Aktif / Nonaktif
|
108 |
with col2:
|
109 |
-
if st.button("π
|
110 |
-
key="toggle_tts",
|
111 |
-
help="Aktifkan/Nonaktifkan Text-to-Speech",
|
112 |
-
use_container_width=True):
|
113 |
st.session_state['should_speak'] = not st.session_state['should_speak']
|
114 |
st.experimental_rerun()
|
115 |
|
116 |
-
# Tombol Input Suara
|
117 |
with col3:
|
118 |
stt_text = speech_to_text(
|
119 |
start_prompt="π€ Input Suara",
|
@@ -124,12 +126,10 @@ def display_chat_history(chain):
|
|
124 |
use_container_width=True,
|
125 |
)
|
126 |
|
127 |
-
# Jika ada STT
|
128 |
if stt_text:
|
129 |
st.session_state.input_text = stt_text
|
130 |
st.experimental_rerun()
|
131 |
|
132 |
-
# Ambil input user
|
133 |
user_input = user_input_obj or st.session_state.get("input_text", "")
|
134 |
|
135 |
if user_input:
|
@@ -140,34 +140,48 @@ def display_chat_history(chain):
|
|
140 |
st.session_state['generated'].append(output)
|
141 |
st.session_state.input_text = ""
|
142 |
|
143 |
-
#
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
|
|
148 |
|
149 |
-
# Tampilkan
|
150 |
if st.session_state['generated']:
|
151 |
with reply_container:
|
152 |
for i in range(len(st.session_state['generated'])):
|
153 |
-
|
154 |
-
message(st.session_state["
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
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 |
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 'tts_cache' not in st.session_state:
|
55 |
+
st.session_state['tts_cache'] = {}
|
56 |
+
if 'last_tts' not in st.session_state:
|
57 |
+
st.session_state['last_tts'] = None
|
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 |
+
def get_tts_path(text):
|
|
|
67 |
cache_dir = "cache_tts"
|
68 |
os.makedirs(cache_dir, exist_ok=True)
|
69 |
filename = f"{md5(text.encode()).hexdigest()}.mp3"
|
70 |
+
return os.path.join(cache_dir, filename)
|
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 |
+
return None
|
85 |
+
return path
|
86 |
+
|
87 |
+
def play_audio_ui(path, autoplay=False):
|
88 |
+
with open(path, "rb") as audio_file:
|
89 |
+
audio_base64 = base64.b64encode(audio_file.read()).decode()
|
90 |
+
autoplay_attr = "autoplay" if autoplay else ""
|
91 |
+
st.markdown(
|
92 |
+
f"""
|
93 |
+
<audio {autoplay_attr} style="display:none;">
|
94 |
<source src="data:audio/mp3;base64,{audio_base64}" type="audio/mp3">
|
95 |
</audio>
|
96 |
+
""",
|
97 |
+
unsafe_allow_html=True
|
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 |
|
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("π Aktifkan" if st.session_state['should_speak'] else "π Nonaktifkan",
|
115 |
+
key="toggle_tts", use_container_width=True):
|
|
|
|
|
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 |
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 |
st.session_state['generated'].append(output)
|
141 |
st.session_state.input_text = ""
|
142 |
|
143 |
+
# Cache TTS
|
144 |
+
tts_path = text_to_speech(output)
|
145 |
+
if tts_path:
|
146 |
+
st.session_state['tts_cache'][output] = tts_path
|
147 |
+
if st.session_state['should_speak']:
|
148 |
+
st.session_state['last_tts'] = output
|
149 |
|
150 |
+
# Tampilkan chat + tombol icon kecil
|
151 |
if st.session_state['generated']:
|
152 |
with reply_container:
|
153 |
for i in range(len(st.session_state['generated'])):
|
154 |
+
# Bubble user
|
155 |
+
message(st.session_state["past"][i], is_user=True, key=f"{i}_user", avatar_style="no-avatar")
|
156 |
+
|
157 |
+
# Bubble bot + icon play kecil
|
158 |
+
bot_text = st.session_state["generated"][i]
|
159 |
+
message(bot_text, key=f"{i}", avatar_style="no-avatar")
|
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
|