Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions AUDIO_FEATURES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# تحسينات التطبيق الصوتي 🎵

## الميزة الجديدة: تسلسل الصوت عند الاتصال 📞

تم إضافة ميزة جديدة رائعة للتطبيق! الآن عندما تضغط على زر "اتصال" سيحدث التالي:

### تسلسل الأحداث:

1. **🔔 صوت رنين (3 ثوانٍ)**
- يتم تشغيل صوت رنين لمدة 3 ثوانٍ
- الزر يصبح أصفر ويظهر "جاري التحضير..."

2. **🗣️ رسالة ترحيبية بالعربية**
- تسمع رسالة تقول: "الآن يمكنك التحدث باللغة العربية"
- تم إنتاج الصوت باستخدام تقنية Text-to-Speech

3. **🎤 تمكين Realtime**
- بعد انتهاء الرسالة يتم تمكين نظام Realtime
- الزر يصبح أحمر ويظهر "إيقاف المحادثة"
- يمكنك الآن التحدث بحرية

### الملفات الصوتية المُنشأة:

- **`ring.wav`**: صوت رنين تم إنتاجه بـ Python باستخدام numpy
- **`arabic_message.mp3`**: رسالة عربية تم إنتاجها بـ Google Text-to-Speech

### التقنيات المستخدمة:

1. **NumPy**: لإنتاج صوت الرنين الرقمي
2. **gTTS (Google Text-to-Speech)**: للرسالة العربية
3. **React Hooks**: لإدارة حالة التسلسل الصوتي
4. **Web Audio API**: لتشغيل الأصوات في المتصفح

### كيفية الاستخدام:

1. افتح التطبيق على `http://localhost:8765`
2. اضغط على زر "اتصال"
3. استمع لصوت الرنين والرسالة
4. ابدأ التحدث بعد انتهاء الرسالة
5. اضغط "إيقاف المحادثة" للإنهاء

### المكونات المُحدثة:

- **useAudioSequence**: Hook جديد للتحكم في تسلسل الأصوات
- **App.tsx**: تحديث لواجهة المستخدم والمنطق
- **StatusMessage**: إضافة حالة جديدة للتحضير
- **audio_generator.py**: أداة إنتاج الأصوات

---

**استمتع بالتجربة الصوتية المحسّنة! 🎉**
Binary file added Voice Ran/Nancy.wav
Binary file not shown.
Binary file added Voice Ran/Ran.mp3
Binary file not shown.
Binary file added Voice Ran/between.wav
Binary file not shown.
84 changes: 71 additions & 13 deletions app/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,90 @@ async def create_app():
voice_choice=os.environ.get("AZURE_OPENAI_REALTIME_VOICE_CHOICE") or "alloy"
)
rtmt.system_message = """
You are a helpful assistant. Only answer questions based on information you searched in the knowledge base, accessible with the 'search' tool.
The user is listening to answers with audio, so it's *super* important that answers are as short as possible, a single sentence if at all possible.
Never read file names or source names or keys out loud.
Always use the following step-by-step instructions to respond:
1. Always use the 'search' tool to check the knowledge base before answering a question.
2. Always use the 'report_grounding' tool to report the source of information from the knowledge base.
3. Produce an answer that's as short as possible. If the answer isn't in the knowledge base, say you don't know.
You are an order-taking assistant at Circles Restaurant.
Always speak in Egyptian Arabic dialect (Masry ‘Aamiya) with a warm and friendly tone.
Keep responses short and focused.

Important rules:

Only answer questions based on information you searched in the knowledge base, accessible with the 'search' tool.

The user is listening to answers with audio, so it's super important that answers are as short as possible, a single sentence if at all possible.

Never switch to English.

Never speak in formal Arabic (Fusha).

Never give long sentences.

Stick strictly to the categories and rules below.

Never read file names, source names, or keys out loud.

If an item is not in the menu → say: "ليس عندي."

If you don’t understand → say: "ممكن توضّح أكتر يا فندم؟"

Always follow these step-by-step instructions when responding:

Always use the 'search' tool to check the knowledge base before answering a question.

Always follow the dialogue flow rules for ordering (see below).

Produce an answer that is as short as possible, one sentence if possible.

If the item or request is not in the menu, respond politely with "ليس عندي."

If the request is unclear, ask for clarification with "ممكن توضّح أكتر يا فندم؟"

Dialogue flow rules:

Opening line (always start with):
"مساء النور يا فندم في مطعم سيركلز.. إزيّك؟ تحب تطلب إيه؟"

Categories: Pizza, Burgers, Other Food, Drinks.

Pizza ordering:

Always ask for size (small, medium, large).

Example: "تحبها حجم إيه؟"

All other items (Burgers, Other Food, Drinks):

Only one size available.

Do not ask about size.

After each order:

Say: "تحب تزود حاجة تانية؟"

If yes → say: "تحب تزود إيه؟"

If no → calculate the total and say:
"الحساب [amount] جنيه.. والأوردر هيكون جاهز بعد نص ساعة."
""".strip()

attach_rag_tools(rtmt,
credentials=search_credential,
search_endpoint=os.environ.get("AZURE_SEARCH_ENDPOINT"),
search_index=os.environ.get("AZURE_SEARCH_INDEX"),
semantic_configuration=os.environ.get("AZURE_SEARCH_SEMANTIC_CONFIGURATION") or None,
identifier_field=os.environ.get("AZURE_SEARCH_IDENTIFIER_FIELD") or "chunk_id",
content_field=os.environ.get("AZURE_SEARCH_CONTENT_FIELD") or "chunk",
embedding_field=os.environ.get("AZURE_SEARCH_EMBEDDING_FIELD") or "text_vector",
title_field=os.environ.get("AZURE_SEARCH_TITLE_FIELD") or "title",
use_vector_query=(os.getenv("AZURE_SEARCH_USE_VECTOR_QUERY", "true") == "true")
semantic_configuration=None, # لا نستخدم البحث الدلالي
identifier_field=os.environ.get("AZURE_SEARCH_IDENTIFIER_FIELD") or "ID",
content_field=os.environ.get("AZURE_SEARCH_CONTENT_FIELD") or "ingredients",
embedding_field="", # لا نستخدم الـ embedding
title_field=os.environ.get("AZURE_SEARCH_TITLE_FIELD") or "Name",
use_vector_query=False # إيقاف البحث الشعاعي
)

rtmt.attach_to_app(app, "/realtime")

current_directory = Path(__file__).parent
app.add_routes([web.get('/', lambda _: web.FileResponse(current_directory / 'static/index.html'))])
app.router.add_static('/', path=current_directory / 'static', name='static')
# إضافة route منفصل للملفات الصوتية
app.router.add_static('/audio', path=current_directory / 'static/audio', name='audio')

return app

Expand Down
43 changes: 27 additions & 16 deletions app/backend/ragtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,37 @@ async def _search_tool(
use_vector_query: bool,
args: Any) -> ToolResult:
print(f"Searching for '{args['query']}' in the knowledge base.")
# Hybrid query using Azure AI Search with (optional) Semantic Ranker
vector_queries = []
if use_vector_query:
vector_queries.append(VectorizableTextQuery(text=args['query'], k_nearest_neighbors=50, fields=embedding_field))
print(f"Using fields: ID={identifier_field}, Content={content_field}")

# Simple text search in Azure AI Search (no vector search, no semantic search)
search_results = await search_client.search(
search_text=args["query"],
query_type="semantic" if semantic_configuration else "simple",
semantic_configuration_name=semantic_configuration,
query_type="simple", # استخدام البحث النصي البسيط فقط
top=5,
vector_queries=vector_queries,
select=", ".join([identifier_field, content_field])
select=f"{identifier_field},Name,{content_field},Price" # تحديد الحقول المطلوبة
)

result = ""
result_count = 0
async for r in search_results:
result += f"[{r[identifier_field]}]: {r[content_field]}\n-----\n"
result_count += 1
print(f"Search result {result_count}: {r}") # طباعة النتيجة الكاملة

# استخدام الحقول الصحيحة
id_field = r.get(identifier_field, f"Item_{result_count}")
name_field = r.get('Name', "Unknown Item")
content_field_value = r.get(content_field, "No description available")
price = r.get('Price', 'سعر غير محدد')

# عرض النتائج بصيغة: [ID]: Name - ingredients (Price جنيه)
result += f"[{id_field}]: {name_field} - {content_field_value} ({price} جنيه)\n-----\n"

if result_count == 0:
print("No search results found!")
result = "ليس عندي."
else:
print(f"Found {result_count} results")

return ToolResult(result, ToolResultDirection.TO_SERVER)

KEY_PATTERN = re.compile(r'^[a-zA-Z0-9_=\-]+$')
Expand All @@ -83,21 +99,16 @@ async def _report_grounding_tool(search_client: SearchClient, identifier_field:
sources = [s for s in args["sources"] if KEY_PATTERN.match(s)]
list = " OR ".join(sources)
print(f"Grounding source: {list}")
# Use search instead of filter to align with how detailt integrated vectorization indexes
# are generated, where chunk_id is searchable with a keyword tokenizer, not filterable
# Use search instead of filter to align with how the index is structured
search_results = await search_client.search(search_text=list,
search_fields=[identifier_field],
select=[identifier_field, title_field, content_field],
top=len(sources),
query_type="full")

# If your index has a key field that's filterable but not searchable and with the keyword analyzer, you can
# use a filter instead (and you can remove the regex check above, just ensure you escape single quotes)
# search_results = await search_client.search(filter=f"search.in(chunk_id, '{list}')", select=["chunk_id", "title", "chunk"])

docs = []
async for r in search_results:
docs.append({"chunk_id": r[identifier_field], "title": r[title_field], "chunk": r[content_field]})
docs.append({"ID": r[identifier_field], "Name": r[title_field], "ingredients": r[content_field]})
return ToolResult({"sources": docs}, ToolResultDirection.TO_CLIENT)

def attach_rag_tools(rtmt: RTMiddleTier,
Expand Down
Binary file modified app/backend/requirements.txt
Binary file not shown.
79 changes: 79 additions & 0 deletions app/backend/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import asyncio
import os
from dotenv import load_dotenv
from azure.search.documents.aio import SearchClient
from azure.core.credentials import AzureKeyCredential

# تحديد مسار ملف .env
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
load_dotenv(dotenv_path)

print(f"Loading .env from: {dotenv_path}")

async def test_azure_search():
# تحقق من المتغيرات
service_endpoint = os.getenv('AZURE_SEARCH_ENDPOINT')
api_key = os.getenv('AZURE_SEARCH_API_KEY')
index_name = os.getenv('AZURE_SEARCH_INDEX')

print(f"Service endpoint: {service_endpoint}")
print(f"Index name: {index_name}")
print(f"API key: {'***' + api_key[-4:] if api_key else 'None'}")

if not all([service_endpoint, api_key, index_name]):
print("❌ Missing required environment variables!")
return

# إنشاء العميل
search_client = SearchClient(
endpoint=service_endpoint,
index_name=index_name,
credential=AzureKeyCredential(api_key)
)

try:
print("\n--- Testing searches ---")

# اختبارات مختلفة
test_queries = [
"بيتزا",
"pizza",
"برجر",
"burger",
"دجاج",
"chicken",
"*", # البحث عن جميع النتائج
]

for query in test_queries:
print(f"\n🔍 البحث عن: '{query}'")

search_results = await search_client.search(
search_text=query,
query_type="simple",
top=3,
select="ID,Name,ingredients,Price"
)

result_count = 0
async for result in search_results:
result_count += 1
print(f" النتيجة {result_count}:")
print(f" ID: {result.get('ID', 'N/A')}")
print(f" Name: {result.get('Name', 'N/A')}")
print(f" ingredients: {result.get('ingredients', 'N/A')}")
print(f" Price: {result.get('Price', 'N/A')}")
print(" ---")

if result_count == 0:
print(f" ❌ لا توجد نتائج للبحث عن '{query}'")
else:
print(f" ✅ وجدت {result_count} نتائج")

except Exception as e:
print(f"❌ خطأ في البحث: {e}")
finally:
await search_client.close()

if __name__ == "__main__":
asyncio.run(test_azure_search())
Loading