Spaces:
Sleeping
Sleeping
shamimjony1000
commited on
Commit
•
24dab17
1
Parent(s):
83fca7e
Upload 9 files
Browse files- app.py +6 -0
- database.py +97 -0
- gemini.py +84 -0
- memory.py +148 -0
- requests.db +0 -0
- requirements.txt +7 -0
- text_to_speech.py +11 -0
- ui.py +273 -0
- voice.py +50 -0
app.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from ui import create_ui
|
3 |
+
|
4 |
+
if __name__ == "__main__":
|
5 |
+
app = create_ui()
|
6 |
+
app.launch()
|
database.py
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sqlite3
|
2 |
+
from datetime import datetime
|
3 |
+
import time
|
4 |
+
from contextlib import contextmanager
|
5 |
+
|
6 |
+
class Database:
|
7 |
+
def __init__(self, db_name="requests.db"):
|
8 |
+
self.db_name = db_name
|
9 |
+
self.max_retries = 3
|
10 |
+
self.retry_delay = 1
|
11 |
+
self.initialize_database()
|
12 |
+
|
13 |
+
@contextmanager
|
14 |
+
def get_connection(self):
|
15 |
+
conn = sqlite3.connect(self.db_name)
|
16 |
+
try:
|
17 |
+
yield conn
|
18 |
+
finally:
|
19 |
+
conn.close()
|
20 |
+
|
21 |
+
def initialize_database(self):
|
22 |
+
for attempt in range(self.max_retries):
|
23 |
+
try:
|
24 |
+
with self.get_connection() as conn:
|
25 |
+
conn.execute('PRAGMA encoding="UTF-8"')
|
26 |
+
cursor = conn.cursor()
|
27 |
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='requests'")
|
28 |
+
if not cursor.fetchone():
|
29 |
+
self.create_table(conn)
|
30 |
+
else:
|
31 |
+
cursor.execute('PRAGMA table_info(requests)')
|
32 |
+
columns = [col[1] for col in cursor.fetchall()]
|
33 |
+
required_columns = ['id', 'timestamp', 'project_number', 'project_name', 'amount', 'reason', 'original_text']
|
34 |
+
|
35 |
+
if not all(col in columns for col in required_columns):
|
36 |
+
cursor.execute('ALTER TABLE requests RENAME TO requests_old')
|
37 |
+
self.create_table(conn)
|
38 |
+
cursor.execute('''
|
39 |
+
INSERT INTO requests (timestamp, project_number, project_name, amount, reason)
|
40 |
+
SELECT timestamp, project_number, project_name, amount, reason
|
41 |
+
FROM requests_old
|
42 |
+
''')
|
43 |
+
cursor.execute('DROP TABLE requests_old')
|
44 |
+
conn.commit()
|
45 |
+
return
|
46 |
+
except sqlite3.OperationalError as e:
|
47 |
+
if attempt < self.max_retries - 1:
|
48 |
+
time.sleep(self.retry_delay)
|
49 |
+
continue
|
50 |
+
raise Exception(f"Could not initialize database after {self.max_retries} attempts: {str(e)}")
|
51 |
+
|
52 |
+
def create_table(self, conn):
|
53 |
+
cursor = conn.cursor()
|
54 |
+
cursor.execute('''
|
55 |
+
CREATE TABLE IF NOT EXISTS requests (
|
56 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
57 |
+
timestamp DATETIME,
|
58 |
+
project_number TEXT,
|
59 |
+
project_name TEXT,
|
60 |
+
amount REAL,
|
61 |
+
reason TEXT,
|
62 |
+
original_text TEXT
|
63 |
+
)
|
64 |
+
''')
|
65 |
+
conn.commit()
|
66 |
+
|
67 |
+
def add_request(self, project_number, project_name, amount, reason, original_text=""):
|
68 |
+
for attempt in range(self.max_retries):
|
69 |
+
try:
|
70 |
+
with self.get_connection() as conn:
|
71 |
+
cursor = conn.cursor()
|
72 |
+
cursor.execute('''
|
73 |
+
INSERT INTO requests (timestamp, project_number, project_name, amount, reason, original_text)
|
74 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
75 |
+
''', (datetime.now(), project_number, project_name, amount, reason, original_text))
|
76 |
+
conn.commit()
|
77 |
+
return
|
78 |
+
except sqlite3.OperationalError as e:
|
79 |
+
if attempt < self.max_retries - 1:
|
80 |
+
time.sleep(self.retry_delay)
|
81 |
+
continue
|
82 |
+
raise Exception(f"Could not add request after {self.max_retries} attempts: {str(e)}")
|
83 |
+
|
84 |
+
def get_all_requests(self):
|
85 |
+
for attempt in range(self.max_retries):
|
86 |
+
try:
|
87 |
+
with self.get_connection() as conn:
|
88 |
+
cursor = conn.cursor()
|
89 |
+
cursor.execute('SELECT * FROM requests ORDER BY timestamp DESC')
|
90 |
+
columns = [description[0] for description in cursor.description]
|
91 |
+
results = cursor.fetchall()
|
92 |
+
return [dict(zip(columns, row)) for row in results]
|
93 |
+
except sqlite3.OperationalError as e:
|
94 |
+
if attempt < self.max_retries - 1:
|
95 |
+
time.sleep(self.retry_delay)
|
96 |
+
continue
|
97 |
+
raise Exception(f"Could not fetch requests after {self.max_retries} attempts: {str(e)}")
|
gemini.py
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import google.generativeai as genai
|
2 |
+
import os
|
3 |
+
import json
|
4 |
+
import re
|
5 |
+
|
6 |
+
class GeminiProcessor:
|
7 |
+
def __init__(self):
|
8 |
+
api_key = "AIzaSyCLyDgZNcE_v4wLMFF8SoimKga9bbLSun0"
|
9 |
+
if not api_key:
|
10 |
+
raise ValueError("GOOGLE_API_KEY not found in environment variables")
|
11 |
+
genai.configure(api_key=api_key)
|
12 |
+
self.model = genai.GenerativeModel('gemini-pro')
|
13 |
+
self.config = genai.GenerationConfig(temperature=0)
|
14 |
+
|
15 |
+
def is_arabic(self, text):
|
16 |
+
arabic_pattern = re.compile('[\u0600-\u06FF]')
|
17 |
+
return bool(arabic_pattern.search(text))
|
18 |
+
|
19 |
+
def translate_arabic_to_english(self, text):
|
20 |
+
prompt = f"""
|
21 |
+
Translate the following Arabic text to English. If the text is mixed (Arabic and English),
|
22 |
+
translate only the Arabic parts and keep the English parts as is.
|
23 |
+
Keep numbers in their original format.
|
24 |
+
|
25 |
+
Text to translate: {text}
|
26 |
+
"""
|
27 |
+
try:
|
28 |
+
response = self.model.generate_content(prompt)
|
29 |
+
return response.text.strip()
|
30 |
+
except Exception as e:
|
31 |
+
print(f"Translation error: {e}")
|
32 |
+
return text
|
33 |
+
|
34 |
+
def extract_request_details(self, text, context=""):
|
35 |
+
full_text = f"{context} {text}".strip()
|
36 |
+
is_arabic_input = self.is_arabic(full_text)
|
37 |
+
|
38 |
+
if is_arabic_input:
|
39 |
+
translated_text = self.translate_arabic_to_english(full_text)
|
40 |
+
processing_text = translated_text
|
41 |
+
else:
|
42 |
+
processing_text = full_text
|
43 |
+
|
44 |
+
prompt = f"""
|
45 |
+
Extract the following information from this text and previous context.
|
46 |
+
The input has been translated from Arabic if it contained Arabic text.
|
47 |
+
|
48 |
+
If any information is missing, leave it empty.
|
49 |
+
Format the response exactly as a JSON object with these keys:
|
50 |
+
{{
|
51 |
+
"project_number": "extracted number or empty string",
|
52 |
+
"project_name": "extracted name or empty string",
|
53 |
+
"amount": extracted number or 0,
|
54 |
+
"reason": "extracted reason or empty string",
|
55 |
+
"missing_fields": ["list of missing required fields"],
|
56 |
+
"original_text": "the original input text"
|
57 |
+
}}
|
58 |
+
|
59 |
+
##No preamble## Response in VALID JSON ONLY##
|
60 |
+
|
61 |
+
Text to analyze: {processing_text}
|
62 |
+
"""
|
63 |
+
|
64 |
+
try:
|
65 |
+
response = self.model.generate_content(prompt, generation_config=self.config)
|
66 |
+
result = json.loads(response.text)
|
67 |
+
|
68 |
+
required_keys = ['project_number', 'project_name', 'amount', 'reason', 'missing_fields']
|
69 |
+
if not all(key in result for key in required_keys):
|
70 |
+
raise ValueError("Missing required keys in response")
|
71 |
+
|
72 |
+
result['amount'] = float(result.get('amount', 0))
|
73 |
+
result['original_text'] = full_text
|
74 |
+
|
75 |
+
if is_arabic_input:
|
76 |
+
result['translated_text'] = processing_text
|
77 |
+
|
78 |
+
return result
|
79 |
+
except json.JSONDecodeError as e:
|
80 |
+
print(f"JSON parsing error: {e}")
|
81 |
+
return None
|
82 |
+
except Exception as e:
|
83 |
+
print(f"Error processing request: {e}")
|
84 |
+
return None
|
memory.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime, timedelta
|
2 |
+
import json
|
3 |
+
from gtts import gTTS
|
4 |
+
import io
|
5 |
+
|
6 |
+
class MemoryHandler:
|
7 |
+
def __init__(self):
|
8 |
+
self.conversation_history = []
|
9 |
+
self.max_history = 5
|
10 |
+
self.context_timeout = timedelta(minutes=2)
|
11 |
+
self.last_interaction_time = None
|
12 |
+
self.partial_info = {
|
13 |
+
'project_number': None,
|
14 |
+
'project_name': None,
|
15 |
+
'amount': None,
|
16 |
+
'reason': None,
|
17 |
+
'timestamp': None
|
18 |
+
}
|
19 |
+
self.confidence_scores = {
|
20 |
+
'project_number': 0.0,
|
21 |
+
'project_name': 0.0,
|
22 |
+
'amount': 0.0,
|
23 |
+
'reason': 0.0
|
24 |
+
}
|
25 |
+
|
26 |
+
def add_interaction(self, text: str, extracted_info: dict = None) -> None:
|
27 |
+
current_time = datetime.now()
|
28 |
+
|
29 |
+
if self.last_interaction_time and \
|
30 |
+
(current_time - self.last_interaction_time) > self.context_timeout:
|
31 |
+
self.clear_partial_info()
|
32 |
+
|
33 |
+
if text:
|
34 |
+
self.conversation_history.append({
|
35 |
+
'text': text,
|
36 |
+
'timestamp': current_time.isoformat(),
|
37 |
+
'extracted_info': extracted_info
|
38 |
+
})
|
39 |
+
if len(self.conversation_history) > self.max_history:
|
40 |
+
self.conversation_history.pop(0)
|
41 |
+
|
42 |
+
if extracted_info:
|
43 |
+
self._update_partial_info(extracted_info, current_time)
|
44 |
+
|
45 |
+
self.last_interaction_time = current_time
|
46 |
+
|
47 |
+
def _update_partial_info(self, extracted_info: dict, current_time: datetime) -> None:
|
48 |
+
for key in self.partial_info:
|
49 |
+
if key in extracted_info and extracted_info[key]:
|
50 |
+
new_value = extracted_info[key]
|
51 |
+
current_value = self.partial_info[key]
|
52 |
+
|
53 |
+
if (current_value is None or
|
54 |
+
extracted_info.get(f'{key}_confidence', 0.5) >
|
55 |
+
self.confidence_scores.get(key, 0)):
|
56 |
+
self.partial_info[key] = new_value
|
57 |
+
self.confidence_scores[key] = extracted_info.get(f'{key}_confidence', 0.5)
|
58 |
+
|
59 |
+
self.partial_info['timestamp'] = current_time
|
60 |
+
|
61 |
+
def get_context(self) -> str:
|
62 |
+
context_parts = []
|
63 |
+
|
64 |
+
for entry in self.conversation_history:
|
65 |
+
timestamp = datetime.fromisoformat(entry['timestamp']).strftime('%H:%M:%S')
|
66 |
+
context_parts.append(f"[{timestamp}] {entry['text']}")
|
67 |
+
|
68 |
+
context = " ".join(context_parts)
|
69 |
+
|
70 |
+
partial_context = []
|
71 |
+
for key, value in self.partial_info.items():
|
72 |
+
if value and key != 'timestamp':
|
73 |
+
confidence = self.confidence_scores.get(key, 0)
|
74 |
+
partial_context.append(f"{key}: {value} (confidence: {confidence:.2f})")
|
75 |
+
|
76 |
+
if partial_context:
|
77 |
+
context += "\nPartial information: " + ", ".join(partial_context)
|
78 |
+
|
79 |
+
return context
|
80 |
+
|
81 |
+
def get_partial_info(self) -> dict:
|
82 |
+
info = {k: v for k, v in self.partial_info.items()
|
83 |
+
if k != 'timestamp' and v is not None}
|
84 |
+
info['confidence_scores'] = self.confidence_scores
|
85 |
+
return info
|
86 |
+
|
87 |
+
def merge_partial_info(self, new_info: dict) -> None:
|
88 |
+
for key in self.partial_info:
|
89 |
+
if key in new_info and new_info[key] is not None:
|
90 |
+
new_confidence = new_info.get(f'{key}_confidence', 0.5)
|
91 |
+
if (self.partial_info[key] is None or
|
92 |
+
new_confidence > self.confidence_scores.get(key, 0)):
|
93 |
+
self.partial_info[key] = new_info[key]
|
94 |
+
self.confidence_scores[key] = new_confidence
|
95 |
+
|
96 |
+
def clear_partial_info(self) -> None:
|
97 |
+
self.partial_info = {
|
98 |
+
'project_number': None,
|
99 |
+
'project_name': None,
|
100 |
+
'amount': None,
|
101 |
+
'reason': None,
|
102 |
+
'timestamp': None
|
103 |
+
}
|
104 |
+
self.confidence_scores = {
|
105 |
+
'project_number': 0.0,
|
106 |
+
'project_name': 0.0,
|
107 |
+
'amount': 0.0,
|
108 |
+
'reason': 0.0
|
109 |
+
}
|
110 |
+
|
111 |
+
def clear_memory(self) -> None:
|
112 |
+
self.conversation_history = []
|
113 |
+
self.clear_partial_info()
|
114 |
+
self.last_interaction_time = None
|
115 |
+
return "Memory cleared!"
|
116 |
+
|
117 |
+
def get_missing_fields(self) -> list:
|
118 |
+
missing = []
|
119 |
+
confidence_threshold = 0.5
|
120 |
+
|
121 |
+
for field in ['project_number', 'project_name', 'amount', 'reason']:
|
122 |
+
if (self.partial_info.get(field) is None or
|
123 |
+
self.confidence_scores.get(field, 0) < confidence_threshold):
|
124 |
+
missing.append(field)
|
125 |
+
return missing
|
126 |
+
|
127 |
+
def get_prompt_for_missing_info(self) -> str:
|
128 |
+
missing = self.get_missing_fields()
|
129 |
+
if not missing:
|
130 |
+
return "All required information has been provided with sufficient confidence."
|
131 |
+
|
132 |
+
current_info = self.get_partial_info()
|
133 |
+
prompt = "Current information:\n"
|
134 |
+
|
135 |
+
for key, value in current_info.items():
|
136 |
+
if key != 'confidence_scores' and value is not None:
|
137 |
+
confidence = self.confidence_scores.get(key, 0)
|
138 |
+
prompt += f"- {key}: {value} (confidence: {confidence:.2f})\n"
|
139 |
+
|
140 |
+
prompt += "\nPlease provide or clarify the following information:\n"
|
141 |
+
for field in missing:
|
142 |
+
current_confidence = self.confidence_scores.get(field, 0)
|
143 |
+
if current_confidence > 0:
|
144 |
+
prompt += f"- {field} (current confidence: {current_confidence:.2f}, needs improvement)\n"
|
145 |
+
else:
|
146 |
+
prompt += f"- {field} (missing)\n"
|
147 |
+
|
148 |
+
return prompt
|
requests.db
ADDED
Binary file (12.3 kB). View file
|
|
requirements.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
pandas
|
3 |
+
google-generativeai
|
4 |
+
SpeechRecognition
|
5 |
+
pydub
|
6 |
+
gTTS
|
7 |
+
python-dotenv
|
text_to_speech.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from gtts import gTTS
|
2 |
+
import io
|
3 |
+
|
4 |
+
def play_text(text: str) -> tuple[str, str]:
|
5 |
+
try:
|
6 |
+
tts = gTTS(text=text, lang='en')
|
7 |
+
audio_path = "temp_audio.mp3"
|
8 |
+
tts.save(audio_path)
|
9 |
+
return audio_path, None
|
10 |
+
except Exception as e:
|
11 |
+
return None, f"Error generating audio: {str(e)}"
|
ui.py
ADDED
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
+
from database import Database
|
4 |
+
from voice import VoiceHandler
|
5 |
+
from gemini import GeminiProcessor
|
6 |
+
from memory import MemoryHandler
|
7 |
+
from text_to_speech import play_text
|
8 |
+
|
9 |
+
def create_ui():
|
10 |
+
# Initialize components
|
11 |
+
db = Database()
|
12 |
+
voice_handler = VoiceHandler()
|
13 |
+
gemini_processor = GeminiProcessor()
|
14 |
+
memory_handler = MemoryHandler()
|
15 |
+
|
16 |
+
def validate_request(project_number, project_name, amount, reason):
|
17 |
+
if not project_number or not project_name or not amount or not reason:
|
18 |
+
missing_fields = []
|
19 |
+
if not project_number: missing_fields.append("project number")
|
20 |
+
if not project_name: missing_fields.append("project name")
|
21 |
+
if not amount: missing_fields.append("amount")
|
22 |
+
if not reason: missing_fields.append("reason")
|
23 |
+
return False, f"Please provide: {', '.join(missing_fields)}"
|
24 |
+
return True, ""
|
25 |
+
|
26 |
+
def process_text_input(text, language):
|
27 |
+
if not text:
|
28 |
+
return "Please enter some text first.", None, None, None, None
|
29 |
+
|
30 |
+
context = memory_handler.get_context()
|
31 |
+
details = gemini_processor.extract_request_details(text, context)
|
32 |
+
|
33 |
+
if not details:
|
34 |
+
return "Could not extract request details. Please try again.", None, None, None, None
|
35 |
+
|
36 |
+
memory_handler.add_interaction(text, details)
|
37 |
+
partial_info = memory_handler.get_partial_info()
|
38 |
+
|
39 |
+
return (
|
40 |
+
f"Text processed! {memory_handler.get_prompt_for_missing_info()}",
|
41 |
+
partial_info.get('project_number', ''),
|
42 |
+
partial_info.get('project_name', ''),
|
43 |
+
partial_info.get('amount', 0),
|
44 |
+
partial_info.get('reason', '')
|
45 |
+
)
|
46 |
+
|
47 |
+
def process_voice_input(audio_path, language):
|
48 |
+
if not audio_path:
|
49 |
+
return "No audio detected.", None, None, None, None
|
50 |
+
|
51 |
+
voice_text = voice_handler.process_audio_file(audio_path, language)
|
52 |
+
if voice_text.startswith("Error:"):
|
53 |
+
return voice_text, None, None, None, None
|
54 |
+
|
55 |
+
context = memory_handler.get_context()
|
56 |
+
details = gemini_processor.extract_request_details(voice_text, context)
|
57 |
+
|
58 |
+
if not details:
|
59 |
+
return "Could not extract request details. Please try again.", None, None, None, None
|
60 |
+
|
61 |
+
memory_handler.add_interaction(voice_text, details)
|
62 |
+
partial_info = memory_handler.get_partial_info()
|
63 |
+
|
64 |
+
return (
|
65 |
+
f"Voice processed! You said: {voice_text}\n\n{memory_handler.get_prompt_for_missing_info()}",
|
66 |
+
partial_info.get('project_number', ''),
|
67 |
+
partial_info.get('project_name', ''),
|
68 |
+
partial_info.get('amount', 0),
|
69 |
+
partial_info.get('reason', '')
|
70 |
+
)
|
71 |
+
|
72 |
+
def confirm_submission(project_number, project_name, amount, reason):
|
73 |
+
is_valid, message = validate_request(project_number, project_name, amount, reason)
|
74 |
+
if not is_valid:
|
75 |
+
return (
|
76 |
+
message, # confirmation_output
|
77 |
+
None, # confirmation_audio
|
78 |
+
gr.update(interactive=False), # submit_btn
|
79 |
+
gr.update(interactive=True), # confirm_btn
|
80 |
+
gr.update(interactive=True), # project_number
|
81 |
+
gr.update(interactive=True), # project_name
|
82 |
+
gr.update(interactive=True), # amount
|
83 |
+
gr.update(interactive=True) # reason
|
84 |
+
)
|
85 |
+
|
86 |
+
confirmation_text = f"Sir please ensure before submit project number: {project_number}, project name: {project_name}, amount: {amount} riyals, reason for request: {reason} are ok"
|
87 |
+
audio_path, error = play_text(confirmation_text)
|
88 |
+
|
89 |
+
if error:
|
90 |
+
return (
|
91 |
+
error, # confirmation_output
|
92 |
+
None, # confirmation_audio
|
93 |
+
gr.update(interactive=False), # submit_btn
|
94 |
+
gr.update(interactive=True), # confirm_btn
|
95 |
+
gr.update(interactive=True), # project_number
|
96 |
+
gr.update(interactive=True), # project_name
|
97 |
+
gr.update(interactive=True), # amount
|
98 |
+
gr.update(interactive=True) # reason
|
99 |
+
)
|
100 |
+
|
101 |
+
return (
|
102 |
+
"Please confirm the details you heard.", # confirmation_output
|
103 |
+
audio_path, # confirmation_audio
|
104 |
+
gr.update(interactive=True), # submit_btn
|
105 |
+
gr.update(interactive=False), # confirm_btn
|
106 |
+
gr.update(interactive=False), # project_number
|
107 |
+
gr.update(interactive=False), # project_name
|
108 |
+
gr.update(interactive=False), # amount
|
109 |
+
gr.update(interactive=False) # reason
|
110 |
+
)
|
111 |
+
|
112 |
+
def submit_request(project_number, project_name, amount, reason):
|
113 |
+
is_valid, message = validate_request(project_number, project_name, amount, reason)
|
114 |
+
|
115 |
+
if not is_valid:
|
116 |
+
return message, None
|
117 |
+
|
118 |
+
try:
|
119 |
+
db.add_request(project_number, project_name, float(amount), reason)
|
120 |
+
memory_handler.clear_memory()
|
121 |
+
return "Request successfully added!", get_requests_df()
|
122 |
+
except Exception as e:
|
123 |
+
return f"Error saving request: {str(e)}", None
|
124 |
+
|
125 |
+
def get_requests_df():
|
126 |
+
try:
|
127 |
+
requests = db.get_all_requests()
|
128 |
+
if requests:
|
129 |
+
df = pd.DataFrame(requests)
|
130 |
+
columns = ['timestamp', 'project_number', 'project_name', 'amount', 'reason']
|
131 |
+
df = df[columns]
|
132 |
+
headers = df.columns.tolist()
|
133 |
+
data = df.values.tolist()
|
134 |
+
return {"headers": headers, "data": data}
|
135 |
+
return {"headers": ['timestamp', 'project_number', 'project_name', 'amount', 'reason'], "data": []}
|
136 |
+
except Exception as e:
|
137 |
+
print(f"Error getting requests: {str(e)}")
|
138 |
+
return {"headers": ['timestamp', 'project_number', 'project_name', 'amount', 'reason'], "data": []}
|
139 |
+
|
140 |
+
def reset_form():
|
141 |
+
return (
|
142 |
+
gr.update(value=""), # project_number
|
143 |
+
gr.update(value=""), # project_name
|
144 |
+
gr.update(value=None), # amount
|
145 |
+
gr.update(value=""), # reason
|
146 |
+
gr.update(value=""), # confirmation_output
|
147 |
+
gr.update(value=None), # confirmation_audio
|
148 |
+
gr.update(interactive=False), # submit_btn
|
149 |
+
gr.update(interactive=True), # confirm_btn
|
150 |
+
gr.update(interactive=True), # project_number
|
151 |
+
gr.update(interactive=True), # project_name
|
152 |
+
gr.update(interactive=True), # amount
|
153 |
+
gr.update(interactive=True), # reason
|
154 |
+
gr.update(value=""), # text_input
|
155 |
+
gr.update(value=None), # audio_input
|
156 |
+
gr.update(value="") # process_output
|
157 |
+
)
|
158 |
+
|
159 |
+
# Create UI layout
|
160 |
+
with gr.Blocks(title="AI Agent Money Request System") as app:
|
161 |
+
gr.Markdown("# AI Agent Money Request System")
|
162 |
+
|
163 |
+
with gr.Tab("Input"):
|
164 |
+
language = gr.Dropdown(
|
165 |
+
choices=["English", "Arabic", "Mixed (Arabic/English)"],
|
166 |
+
value="English",
|
167 |
+
label="Select Language"
|
168 |
+
)
|
169 |
+
|
170 |
+
with gr.Tab("Voice Input"):
|
171 |
+
audio_input = gr.Audio(
|
172 |
+
label="Voice Input",
|
173 |
+
type="filepath",
|
174 |
+
sources=["microphone"]
|
175 |
+
)
|
176 |
+
voice_process_btn = gr.Button("Process Voice")
|
177 |
+
|
178 |
+
with gr.Tab("Text Input"):
|
179 |
+
text_input = gr.Textbox(
|
180 |
+
lines=3,
|
181 |
+
placeholder="Enter your request here...",
|
182 |
+
label="Text Input"
|
183 |
+
)
|
184 |
+
text_process_btn = gr.Button("Process Text")
|
185 |
+
|
186 |
+
process_output = gr.Textbox(label="Processing Result")
|
187 |
+
|
188 |
+
with gr.Group():
|
189 |
+
project_number = gr.Textbox(label="Project Number")
|
190 |
+
project_name = gr.Textbox(label="Project Name")
|
191 |
+
amount = gr.Number(label="Amount (in riyals)")
|
192 |
+
reason = gr.Textbox(label="Reason for Request")
|
193 |
+
|
194 |
+
with gr.Row():
|
195 |
+
confirm_btn = gr.Button("Confirm Details", variant="secondary")
|
196 |
+
submit_btn = gr.Button("Submit Request", variant="primary", interactive=False)
|
197 |
+
|
198 |
+
confirmation_output = gr.Textbox(label="Confirmation Message")
|
199 |
+
confirmation_audio = gr.Audio(label="Confirmation Audio", type="filepath")
|
200 |
+
|
201 |
+
result_text = gr.Textbox(label="Submission Result")
|
202 |
+
|
203 |
+
with gr.Tab("Existing Requests"):
|
204 |
+
requests_table = gr.DataFrame(
|
205 |
+
headers=["Timestamp", "Project Number", "Project Name", "Amount", "Reason"],
|
206 |
+
label="Existing Requests"
|
207 |
+
)
|
208 |
+
refresh_btn = gr.Button("Refresh")
|
209 |
+
|
210 |
+
# Event handlers
|
211 |
+
text_process_btn.click(
|
212 |
+
process_text_input,
|
213 |
+
inputs=[text_input, language],
|
214 |
+
outputs=[process_output, project_number, project_name, amount, reason]
|
215 |
+
)
|
216 |
+
|
217 |
+
voice_process_btn.click(
|
218 |
+
process_voice_input,
|
219 |
+
inputs=[audio_input, language],
|
220 |
+
outputs=[process_output, project_number, project_name, amount, reason]
|
221 |
+
)
|
222 |
+
|
223 |
+
# Confirm button handler with proper submit button and form field state management
|
224 |
+
confirm_btn.click(
|
225 |
+
confirm_submission,
|
226 |
+
inputs=[project_number, project_name, amount, reason],
|
227 |
+
outputs=[
|
228 |
+
confirmation_output,
|
229 |
+
confirmation_audio,
|
230 |
+
submit_btn,
|
231 |
+
confirm_btn,
|
232 |
+
project_number,
|
233 |
+
project_name,
|
234 |
+
amount,
|
235 |
+
reason
|
236 |
+
]
|
237 |
+
)
|
238 |
+
|
239 |
+
# Submit button handler with form reset
|
240 |
+
submit_btn.click(
|
241 |
+
submit_request,
|
242 |
+
inputs=[project_number, project_name, amount, reason],
|
243 |
+
outputs=[result_text, requests_table]
|
244 |
+
).then(
|
245 |
+
reset_form,
|
246 |
+
outputs=[
|
247 |
+
project_number,
|
248 |
+
project_name,
|
249 |
+
amount,
|
250 |
+
reason,
|
251 |
+
confirmation_output,
|
252 |
+
confirmation_audio,
|
253 |
+
submit_btn,
|
254 |
+
confirm_btn,
|
255 |
+
project_number,
|
256 |
+
project_name,
|
257 |
+
amount,
|
258 |
+
reason,
|
259 |
+
text_input,
|
260 |
+
audio_input,
|
261 |
+
process_output
|
262 |
+
]
|
263 |
+
)
|
264 |
+
|
265 |
+
refresh_btn.click(
|
266 |
+
lambda: get_requests_df(),
|
267 |
+
outputs=[requests_table]
|
268 |
+
)
|
269 |
+
|
270 |
+
# Initialize requests table
|
271 |
+
requests_table.value = get_requests_df()
|
272 |
+
|
273 |
+
return app
|
voice.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import speech_recognition as sr
|
2 |
+
import os
|
3 |
+
from pydub import AudioSegment
|
4 |
+
import tempfile
|
5 |
+
|
6 |
+
class VoiceHandler:
|
7 |
+
def __init__(self):
|
8 |
+
self.recognizer = sr.Recognizer()
|
9 |
+
self.recognizer.energy_threshold = 20000
|
10 |
+
self.recognizer.dynamic_energy_threshold = False
|
11 |
+
self.recognizer.pause_threshold = 0.8
|
12 |
+
|
13 |
+
def process_audio_file(self, audio_path: str, language: str) -> str:
|
14 |
+
try:
|
15 |
+
if not audio_path.endswith('.wav'):
|
16 |
+
audio = AudioSegment.from_file(audio_path)
|
17 |
+
temp_wav = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
|
18 |
+
audio.export(temp_wav.name, format='wav')
|
19 |
+
audio_path = temp_wav.name
|
20 |
+
|
21 |
+
with sr.AudioFile(audio_path) as source:
|
22 |
+
audio = self.recognizer.record(source)
|
23 |
+
|
24 |
+
if language == "Arabic":
|
25 |
+
return self.recognizer.recognize_google(audio, language="ar-SA")
|
26 |
+
elif language == "Mixed (Arabic/English)":
|
27 |
+
try:
|
28 |
+
return self.recognizer.recognize_google(audio, language="ar-SA")
|
29 |
+
except sr.UnknownValueError:
|
30 |
+
return self.recognizer.recognize_google(audio, language="en-US")
|
31 |
+
else: # English
|
32 |
+
return self.recognizer.recognize_google(audio, language="en-US")
|
33 |
+
|
34 |
+
except sr.RequestError as e:
|
35 |
+
return f"Error: Could not request results from speech service: {str(e)}"
|
36 |
+
except sr.UnknownValueError:
|
37 |
+
return "Error: Could not understand audio. Please speak clearly and try again."
|
38 |
+
except Exception as e:
|
39 |
+
return f"Error: {str(e)}"
|
40 |
+
finally:
|
41 |
+
if 'temp_wav' in locals():
|
42 |
+
os.unlink(temp_wav.name)
|
43 |
+
|
44 |
+
def check_microphone_access(self) -> bool:
|
45 |
+
try:
|
46 |
+
with sr.Microphone() as source:
|
47 |
+
self.recognizer.adjust_for_ambient_noise(source, duration=0.1)
|
48 |
+
return True
|
49 |
+
except (OSError, AttributeError, sr.RequestError):
|
50 |
+
return False
|