=3.0.0 → .env RENAMED
File without changes
.gitignore DELETED
@@ -1,7 +0,0 @@
1
- .env
2
- __pycache__/
3
- *.pyc
4
- .venv/
5
- venv/
6
- .python-version
7
- .DS_Store
 
 
 
 
 
 
 
 
Dockerfile CHANGED
@@ -4,17 +4,14 @@ FROM python:3.11-slim
4
  # Set working directory
5
  WORKDIR /app
6
 
7
- # Set environment variables for Hugging Face Spaces (can be overridden for local development)
8
  ENV PYTHONDONTWRITEBYTECODE=1 \
9
  PYTHONUNBUFFERED=1 \
10
- PORT=7860 \
11
- HF_HOME=/app/cache \
12
- TRANSFORMERS_CACHE=/app/cache
13
 
14
- # Install system dependencies including curl for health check
15
  RUN apt-get update && apt-get install -y \
16
  gcc \
17
- curl \
18
  && rm -rf /var/lib/apt/lists/*
19
 
20
  # Copy requirements first for better caching
@@ -24,18 +21,20 @@ COPY requirements.txt .
24
  RUN pip install --no-cache-dir --upgrade pip && \
25
  pip install --no-cache-dir -r requirements.txt
26
 
27
- # Copy all application code
28
- COPY . .
29
 
30
- # Create cache directory for Hugging Face models
31
- RUN mkdir -p /app/cache && chmod 777 /app/cache
 
 
32
 
33
- # Expose port (default 8000 for local, can be set to 7860 for Hugging Face Spaces)
34
- EXPOSE ${PORT}
35
 
36
  # Health check
37
  HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
38
- CMD curl -f http://localhost:${PORT}/ || exit 1
39
 
40
- # Run the application (uses PORT environment variable)
41
- CMD sh -c "uvicorn main:app --host 0.0.0.0 --port ${PORT}"
 
4
  # Set working directory
5
  WORKDIR /app
6
 
7
+ # Set environment variables
8
  ENV PYTHONDONTWRITEBYTECODE=1 \
9
  PYTHONUNBUFFERED=1 \
10
+ PORT=7860
 
 
11
 
12
+ # Install system dependencies
13
  RUN apt-get update && apt-get install -y \
14
  gcc \
 
15
  && rm -rf /var/lib/apt/lists/*
16
 
17
  # Copy requirements first for better caching
 
21
  RUN pip install --no-cache-dir --upgrade pip && \
22
  pip install --no-cache-dir -r requirements.txt
23
 
24
+ # Copy application code
25
+ COPY app.py .
26
 
27
+ # Create non-root user for security
28
+ RUN useradd --create-home --shell /bin/bash app && \
29
+ chown -R app:app /app
30
+ USER app
31
 
32
+ # Expose port
33
+ EXPOSE 7860
34
 
35
  # Health check
36
  HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
37
+ CMD curl -f http://localhost:7860/ || exit 1
38
 
39
+ # Run the application
40
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,36 +1,14 @@
1
  ---
2
- title: Customer Service AI
3
- emoji: 🤖
4
- colorFrom: blue
5
- colorTo: purple
6
  sdk: docker
7
- sdk_version: "latest"
8
- app_file: main.py
9
- pinned: false
 
10
  ---
 
11
 
12
- # Customer Service AI Backend
13
-
14
- FastAPI backend for Hadhramout Bank AI customer service system.
15
 
16
- ## Features
17
- - Telegram webhook integration
18
- - AI-powered responses using Hugging Face Inference API
19
- - Database integration with Supabase
20
- - Vector search with Pinecone
21
-
22
- ## API Endpoints
23
- - `GET /` - Health check
24
- - `POST /webhook` - Telegram webhook endpoint
25
- - `POST /test` - Test webhook functionality
26
- - `GET /dns-test` - DNS connectivity test
27
- - `GET /ai-test` - AI response test
28
-
29
- ## Deployment
30
- This application is deployed on Hugging Face Spaces using Docker.
31
 
32
- ## Local Development
33
- ```bash
34
- docker build -t customer-service .
35
- docker run -p 8000:8000 customer-service
36
- ```
 
1
  ---
2
+ title: customer_service
 
 
 
3
  sdk: docker
4
+ emoji: 🚀
5
+ colorFrom: blue
6
+ colorTo: blue
7
+ app_file: app.py
8
  ---
9
+ # customer_service
10
 
 
 
 
11
 
12
+ ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
__pycache__/ai_service.cpython-313.pyc DELETED
Binary file (3.15 kB)
 
__pycache__/app.cpython-313.pyc DELETED
Binary file (3.29 kB)
 
__pycache__/config.cpython-313.pyc DELETED
Binary file (1.93 kB)
 
__pycache__/database.cpython-313.pyc DELETED
Binary file (10.2 kB)
 
__pycache__/main.cpython-313.pyc DELETED
Binary file (1.6 kB)
 
__pycache__/telegram_handlers.cpython-313.pyc DELETED
Binary file (2.74 kB)
 
__pycache__/utils.cpython-313.pyc DELETED
Binary file (1.08 kB)
 
ai_service.py DELETED
@@ -1,110 +0,0 @@
1
- import re
2
- import json
3
- import os
4
- from config import pc, index, EMBED_MODEL, hf_client, PROMPT, HF_MODEL
5
- from database import db_manager
6
-
7
-
8
- MODEL_NAME = HF_MODEL
9
-
10
- def clean_ai_response(text: str):
11
- if not text: return ""
12
- text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)
13
- text = re.sub(r'<br\s*/?>', '\n', text)
14
- text = re.sub(r'<[^>]+>', '', text)
15
- text = re.sub(r'^\|.*\|\s*$', '', text, flags=re.MULTILINE)
16
- text = re.sub(r'^[\s|:-]+$', '', text, flags=re.MULTILINE)
17
- text = re.sub(r'^#{1,6}\s*', '', text, flags=re.MULTILINE)
18
- text = re.sub(r'\n{3,}', '\n\n', text)
19
- return text.strip()
20
-
21
- async def search_bank_knowledge(query: str):
22
- query_embedding = pc.inference.embed(
23
- model=EMBED_MODEL,
24
- inputs=[query],
25
- parameters={"input_type": "query"}
26
- )
27
-
28
- search_results = index.query(
29
- vector=query_embedding[0].values,
30
- top_k=3,
31
- include_metadata=True
32
- )
33
-
34
- return "\n".join([res.metadata['original_text'] for res in search_results.matches])
35
-
36
- TOOLS = [
37
- {
38
- "type": "function",
39
- "function": {
40
- "name": "search_bank_knowledge",
41
- "description": "Use this tool to search the official Hadhramout Bank profile for accurate information about services, organizational structure, capital, and policies.",
42
- "parameters": {
43
- "type": "object",
44
- "properties": {
45
- "query": {
46
- "type": "string",
47
- "description": "The search query (e.g., 'What is Hadhramout Bank capital?' or 'individual services')."
48
- }
49
- },
50
- "required": ["query"]
51
- }
52
- }
53
- }
54
- ]
55
-
56
- async def get_ai_response(user_query: str, telegram_id: int):
57
- conversation_history = []
58
- if db_manager:
59
- raw_history = db_manager.get_conversation_history(telegram_id, limit=6)
60
- raw_history.reverse()
61
- for msg in raw_history:
62
- if msg.get('message_text'):
63
- role = "user" if msg['message_type'] == 'user' else "assistant"
64
- conversation_history.append({"role": role, "content": msg['message_text']})
65
-
66
- messages = [{"role": "system", "content": PROMPT}] + conversation_history + [{"role": "user", "content": user_query}]
67
-
68
-
69
- import asyncio
70
- loop = asyncio.get_event_loop()
71
-
72
-
73
- def call_hf(msgs):
74
- return hf_client.chat.completions.create(
75
- model=MODEL_NAME,
76
- messages=msgs,
77
- tools=TOOLS,
78
- tool_choice="auto",
79
- temperature=0.1,
80
- max_tokens=800
81
- )
82
-
83
- completion = await loop.run_in_executor(None, lambda: call_hf(messages))
84
- response_message = completion.choices[0].message
85
-
86
- # Handle tool call if model requests it
87
- if response_message.tool_calls:
88
- tool_call = response_message.tool_calls[0]
89
- args = json.loads(tool_call.function.arguments)
90
- tool_result = await search_bank_knowledge(args["query"])
91
-
92
- messages.append(response_message)
93
- messages.append({
94
- "role": "tool",
95
- "tool_call_id": tool_call.id,
96
- "content": tool_result
97
- })
98
-
99
- completion = await loop.run_in_executor(None, lambda: call_hf(messages))
100
- response_message = completion.choices[0].message
101
-
102
- final_response = clean_ai_response(response_message.content if response_message.content else "")
103
- print(f"--- AI Raw Response: {repr(response_message.content)} ---")
104
- print(f"--- AI Final Response: {repr(final_response)} ---")
105
-
106
- if db_manager:
107
- db_manager.save_message(telegram_id, user_query, "user")
108
- db_manager.save_message(telegram_id, final_response, "assistant")
109
-
110
- return final_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from fastapi import FastAPI, Request
3
+ from pinecone import Pinecone
4
+ from groq import Groq
5
+ import httpx
6
+ import re
7
+
8
+ # 1. Configuration & Clients
9
+ # Use Hugging Face Secrets for these!
10
+ PINECONE_API_KEY = os.environ.get("PINECONE_API_KEY")
11
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
12
+ TELEGRAM_TOKEN = os.environ.get("TELEGRAM_TOKEN")
13
+ TELEGRAM_URL = f"https://149.154.167.220/bot{TELEGRAM_TOKEN}/sendMessage"
14
+
15
+ EMBED_MODEL= os.environ.get("EMBED_MODEL")
16
+ GROQ_MODEL = os.environ.get("GROQ_MODEL")
17
+ PROMPT = os.environ.get("PROMPT")
18
+
19
+ pc = Pinecone(api_key=PINECONE_API_KEY)
20
+ index = pc.Index("customerserviceindex")
21
+ groq_client = Groq(api_key=GROQ_API_KEY)
22
+
23
+ app = FastAPI()
24
+
25
+ def clean_ai_response(text: str):
26
+ # إزالة كل ما بين وسمي <think> و </think> بما في ذلك الوسوم نفسها
27
+ cleaned_text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)
28
+ return cleaned_text.strip()
29
+
30
+ # 2. The Core AI Logic
31
+ async def get_ai_response(user_query: str):
32
+ # Vectorize query using Pinecone Inference
33
+ query_embedding = pc.inference.embed(
34
+ model=EMBED_MODEL,
35
+ inputs=[user_query],
36
+ parameters={"input_type": "query"}
37
+ )
38
+
39
+ # Search Pinecone for Bank Context
40
+ search_results = index.query(
41
+ vector=query_embedding[0].values,
42
+ top_k=3,
43
+ include_metadata=True
44
+ )
45
+
46
+ retrieved_context = "\n".join([res.metadata['original_text'] for res in search_results.matches])
47
+
48
+
49
+ prompt = f"""
50
+ {PROMPT}
51
+
52
+ Message:{user_query}
53
+
54
+ Retrieved Context:{retrieved_context}
55
+
56
+ Final Answer:
57
+ """
58
+
59
+ completion = groq_client.chat.completions.create(
60
+ messages=[{"role": "user", "content": prompt}],
61
+ model=GROQ_MODEL,
62
+ temperature=0.1,
63
+ max_completion_tokens=800,
64
+ top_p=0.9,
65
+ )
66
+ ai_response = completion.choices[0].message.content
67
+ return clean_ai_response(ai_response)
68
+
69
+ # 3. The Webhook Endpoint
70
+ @app.post("/webhook")
71
+ async def telegram_webhook(request: Request):
72
+ data = await request.json()
73
+
74
+ if "message" in data:
75
+ chat_id = data["message"]["chat"]["id"]
76
+ user_text = data["message"].get("text", "")
77
+
78
+ if user_text:
79
+ # Get the intelligent response
80
+ ai_answer = await get_ai_response(user_text)
81
+ # Send back to Telegram
82
+ async with httpx.AsyncClient(verify=False) as client:
83
+ await client.post(
84
+ TELEGRAM_URL,
85
+ headers={"Host": "api.telegram.org"},
86
+ json={
87
+ "chat_id": chat_id,
88
+ "text": ai_answer,
89
+ "parse_mode": "Markdown"
90
+ }
91
+ )
92
+
93
+ return {"status": "ok"}
94
+
95
+ @app.get("/")
96
+ async def root():
97
+ return {"message": "Hadhramout Bank AI Backend is Live"}
98
+
99
+ @app.post("/test")
100
+ async def test_webhook(request: Request):
101
+ data = await request.json()
102
+ response = await get_ai_response(data["message"]["text"])
103
+ return {"response": response}
104
+
105
+ import socket
106
+
107
+ @app.get("/dns-test")
108
+ async def dns_test():
109
+ try:
110
+ ip = socket.gethostbyname("api.telegram.org")
111
+ return {"resolved_ip": ip}
112
+ except Exception as e:
113
+ return {"error": str(e)}
config.py DELETED
@@ -1,46 +0,0 @@
1
- import os
2
- from pinecone import Pinecone
3
- from openai import OpenAI
4
- from dotenv import load_dotenv
5
-
6
- # Load environment variables from .env file
7
- load_dotenv()
8
-
9
- # Environment Variables
10
- PINECONE_API_KEY = os.environ.get("PINECONE_API_KEY")
11
- HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HF_API_KEY")
12
- TELEGRAM_TOKEN = os.environ.get("TELEGRAM_TOKEN")
13
- SUPABASE_URL = os.environ.get("SUPABASE_URL")
14
- SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
15
- TELEGRAM_DOMAIN = os.environ.get("TELEGRAM_DOMAIN", "https://api.telegram.org").rstrip("/")
16
-
17
- # Only create TELEGRAM_URL if token exists
18
- TELEGRAM_URL = f"{TELEGRAM_DOMAIN}/bot{TELEGRAM_TOKEN}/sendMessage" if TELEGRAM_TOKEN and TELEGRAM_DOMAIN else None
19
-
20
- EMBED_MODEL = os.environ.get("EMBED_MODEL", "multilingual-e5-large")
21
- HF_MODEL = os.environ.get(
22
- "HF_MODEL",
23
- "dphn/Dolphin-Mistral-24B-Venice-Edition",
24
- )
25
- PROMPT = os.environ.get("PROMPT")
26
-
27
- # Initialize clients only if API keys are available
28
- pc = None
29
- if PINECONE_API_KEY:
30
- pc = Pinecone(api_key=PINECONE_API_KEY)
31
-
32
- hf_client = None
33
- if HF_TOKEN:
34
- try:
35
- hf_client = OpenAI(
36
- base_url="https://router.huggingface.co/v1",
37
- api_key=HF_TOKEN,
38
- )
39
- except Exception as e:
40
- print(f"Warning: Failed to initialize Hugging Face OpenAI client: {e}")
41
- hf_client = None
42
-
43
- # Initialize index only if Pinecone client is available
44
- index = None
45
- if pc:
46
- index = pc.Index("customerserviceindex")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
database.py DELETED
@@ -1,202 +0,0 @@
1
- import os
2
- from datetime import datetime
3
- from typing import List, Dict, Optional
4
- from supabase import create_client, Client
5
- import logging
6
- from config import SUPABASE_URL, SUPABASE_KEY
7
-
8
-
9
- class DatabaseManager:
10
- def __init__(self, supabase_url: str = SUPABASE_URL, supabase_key: str = SUPABASE_KEY):
11
- if not supabase_url or not supabase_key:
12
- raise ValueError("SUPABASE_URL and SUPABASE_KEY must be set in environment variables")
13
-
14
- self.supabase: Client = create_client(supabase_url, supabase_key)
15
- self.logger = logging.getLogger(__name__)
16
-
17
- def create_or_update_user(self, telegram_id: int, username: str = None,
18
- first_name: str = None, last_name: str = None):
19
- """Create or update user information"""
20
- try:
21
- # Check if user exists
22
- existing_user = self.supabase.table("users").select("id").eq("telegram_id", telegram_id).execute()
23
-
24
- user_data = {
25
- "telegram_id": telegram_id,
26
- "username": username,
27
- "first_name": first_name,
28
- "last_name": last_name,
29
- "updated_at": datetime.utcnow().isoformat()
30
- }
31
-
32
- if existing_user.data:
33
- # Update existing user
34
- result = self.supabase.table("users").update(user_data).eq("telegram_id", telegram_id).execute()
35
- else:
36
- # Create new user
37
- user_data["created_at"] = datetime.utcnow().isoformat()
38
- result = self.supabase.table("users").insert(user_data).execute()
39
-
40
- return result.data[0] if result.data else None
41
-
42
- except Exception as e:
43
- self.logger.error(f"Error creating/updating user: {e}")
44
- return None
45
-
46
- def save_message(self, telegram_id: int, message_text: str, message_type: str):
47
- """Save a message to the database"""
48
- try:
49
- # Ensure user exists
50
- self.create_or_update_user(telegram_id)
51
-
52
- # Save message
53
- message_data = {
54
- "telegram_id": telegram_id,
55
- "message_text": message_text,
56
- "message_type": message_type,
57
- "created_at": datetime.utcnow().isoformat()
58
- }
59
-
60
- result = self.supabase.table("messages").insert(message_data).execute()
61
-
62
- # Ensure active session exists
63
- self._ensure_active_session(telegram_id)
64
-
65
- return result.data[0] if result.data else None
66
-
67
- except Exception as e:
68
- self.logger.error(f"Error saving message: {e}")
69
- return None
70
-
71
- def get_conversation_history(self, telegram_id: int, limit: int = 10) -> List[Dict]:
72
- """Get conversation history for a user"""
73
- try:
74
- result = (self.supabase.table("messages")
75
- .select("message_text, message_type, created_at")
76
- .eq("telegram_id", telegram_id)
77
- .order("created_at", desc=True)
78
- .limit(limit)
79
- .execute())
80
-
81
- return result.data if result.data else []
82
-
83
- except Exception as e:
84
- self.logger.error(f"Error getting conversation history: {e}")
85
- return []
86
-
87
- def get_formatted_history(self, telegram_id: int, limit: int = 10) -> str:
88
- """Get formatted conversation history for Groq"""
89
- history = self.get_conversation_history(telegram_id, limit)
90
-
91
- if not history:
92
- return ""
93
-
94
- # Reverse to get chronological order
95
- history.reverse()
96
-
97
- formatted_history = "Previous conversation:\n"
98
- for msg in history:
99
- role = "User" if msg['message_type'] == 'user' else "Assistant"
100
- formatted_history += f"{role}: {msg['message_text']}\n"
101
-
102
- return formatted_history
103
-
104
- def _ensure_active_session(self, telegram_id: int):
105
- """Ensure an active session exists for the user"""
106
- try:
107
- # Check for active session
108
- active_session = (self.supabase.table("conversation_sessions")
109
- .select("id")
110
- .eq("telegram_id", telegram_id)
111
- .is_("session_end", "null")
112
- .execute())
113
-
114
- if not active_session.data:
115
- # Create new session
116
- session_data = {
117
- "telegram_id": telegram_id,
118
- "session_start": datetime.utcnow().isoformat(),
119
- "created_at": datetime.utcnow().isoformat()
120
- }
121
- self.supabase.table("conversation_sessions").insert(session_data).execute()
122
-
123
- except Exception as e:
124
- self.logger.error(f"Error ensuring active session: {e}")
125
-
126
- def start_new_session(self, telegram_id: int):
127
- """Start a new conversation session"""
128
- try:
129
- # End previous sessions
130
- self.supabase.table("conversation_sessions").update({
131
- "session_end": datetime.utcnow().isoformat()
132
- }).eq("telegram_id", telegram_id).is_("session_end", "null").execute()
133
-
134
- # Start new session
135
- session_data = {
136
- "telegram_id": telegram_id,
137
- "session_start": datetime.utcnow().isoformat(),
138
- "created_at": datetime.utcnow().isoformat()
139
- }
140
- result = self.supabase.table("conversation_sessions").insert(session_data).execute()
141
-
142
- return result.data[0] if result.data else None
143
-
144
- except Exception as e:
145
- self.logger.error(f"Error starting new session: {e}")
146
- return None
147
-
148
- def get_user_stats(self, telegram_id: int) -> Dict:
149
- """Get user conversation statistics"""
150
- try:
151
- # Get message counts
152
- message_stats = (self.supabase.table("messages")
153
- .select("message_type")
154
- .eq("telegram_id", telegram_id)
155
- .execute())
156
-
157
- if not message_stats.data:
158
- return {
159
- "total_messages": 0,
160
- "user_messages": 0,
161
- "assistant_messages": 0,
162
- "first_message": None,
163
- "last_message": None
164
- }
165
-
166
- total_messages = len(message_stats.data)
167
- user_messages = len([m for m in message_stats.data if m['message_type'] == 'user'])
168
- assistant_messages = len([m for m in message_stats.data if m['message_type'] == 'assistant'])
169
-
170
- # Get first and last message timestamps
171
- timestamps = [m['created_at'] for m in message_stats.data]
172
- first_message = min(timestamps) if timestamps else None
173
- last_message = max(timestamps) if timestamps else None
174
-
175
- return {
176
- "total_messages": total_messages,
177
- "user_messages": user_messages,
178
- "assistant_messages": assistant_messages,
179
- "first_message": first_message,
180
- "last_message": last_message
181
- }
182
-
183
- except Exception as e:
184
- self.logger.error(f"Error getting user stats: {e}")
185
- return {
186
- "total_messages": 0,
187
- "user_messages": 0,
188
- "assistant_messages": 0,
189
- "first_message": None,
190
- "last_message": None
191
- }
192
-
193
- # Global database instance
194
- try:
195
- db_manager = DatabaseManager()
196
- except ValueError as e:
197
- print(f"Database initialization failed: {e}")
198
- print("Please set SUPABASE_URL and SUPABASE_KEY environment variables")
199
- db_manager = None
200
- except Exception as e:
201
- print(f"Unexpected database error: {e}")
202
- db_manager = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
database_schema.sql DELETED
@@ -1,56 +0,0 @@
1
- -- Conversation History Database Schema
2
- -- SQLite Database Structure
3
-
4
- -- Users table to store user information
5
- CREATE TABLE IF NOT EXISTS users (
6
- id INTEGER PRIMARY KEY AUTOINCREMENT,
7
- chat_id BIGINT UNIQUE NOT NULL,
8
- username VARCHAR(255),
9
- first_name VARCHAR(255),
10
- last_name VARCHAR(255),
11
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
12
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
13
- );
14
-
15
- -- Messages table to store conversation history
16
- CREATE TABLE IF NOT EXISTS messages (
17
- id INTEGER PRIMARY KEY AUTOINCREMENT,
18
- chat_id BIGINT NOT NULL,
19
- message_text TEXT NOT NULL,
20
- message_type VARCHAR(20) NOT NULL CHECK (message_type IN ('user', 'assistant')),
21
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
22
- FOREIGN KEY (chat_id) REFERENCES users(chat_id) ON DELETE CASCADE
23
- );
24
-
25
- -- Conversation sessions to group messages by session
26
- CREATE TABLE IF NOT EXISTS conversation_sessions (
27
- id INTEGER PRIMARY KEY AUTOINCREMENT,
28
- chat_id BIGINT NOT NULL,
29
- session_start TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
30
- session_end TIMESTAMP NULL,
31
- message_count INTEGER DEFAULT 0,
32
- FOREIGN KEY (chat_id) REFERENCES users(chat_id) ON DELETE CASCADE
33
- );
34
-
35
- -- Indexes for better performance
36
- CREATE INDEX IF NOT EXISTS idx_messages_chat_id_timestamp ON messages(chat_id, timestamp);
37
- CREATE INDEX IF NOT EXISTS idx_users_chat_id ON users(chat_id);
38
- CREATE INDEX IF NOT EXISTS idx_sessions_chat_id ON conversation_sessions(chat_id);
39
-
40
- -- Trigger to update user's updated_at timestamp
41
- CREATE TRIGGER IF NOT EXISTS update_user_timestamp
42
- AFTER INSERT ON messages
43
- FOR EACH ROW
44
- BEGIN
45
- UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE chat_id = NEW.chat_id;
46
- END;
47
-
48
- -- Trigger to update session message count
49
- CREATE TRIGGER IF NOT EXISTS update_session_count
50
- AFTER INSERT ON messages
51
- FOR EACH ROW
52
- BEGIN
53
- UPDATE conversation_sessions
54
- SET message_count = message_count + 1
55
- WHERE chat_id = NEW.chat_id AND session_end IS NULL;
56
- END;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py DELETED
@@ -1,25 +0,0 @@
1
- from fastapi import FastAPI
2
- from telegram_handlers import telegram_webhook, WebhookData
3
- from utils import dns_test, test_ai_response
4
-
5
- app = FastAPI()
6
-
7
- @app.get("/")
8
- async def root():
9
- return {"message": "Hadhramout Bank AI Backend is Live"}
10
-
11
- @app.post("/webhook")
12
- async def webhook(data: WebhookData):
13
- return await telegram_webhook(data)
14
-
15
- # @app.post("/test")
16
- # async def test(data: WebhookData):
17
- # return await test_webhook(data)
18
-
19
- @app.get("/dns-test")
20
- async def dns():
21
- return await dns_test()
22
-
23
- @app.get("/ai-test")
24
- async def ai():
25
- return await test_ai_response()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,7 +1,6 @@
1
  fastapi
2
  uvicorn
3
  pinecone
 
4
  httpx
5
  python-dotenv
6
- supabase
7
- openai
 
1
  fastapi
2
  uvicorn
3
  pinecone
4
+ groq
5
  httpx
6
  python-dotenv
 
 
rest.http DELETED
@@ -1,21 +0,0 @@
1
- GET https://codeboker-customer-service.hf.space/ai-test HTTP/1.1
2
- Content-Type: application/json
3
-
4
- {
5
- "message": {
6
- "chat": {"id": 123},
7
- "text": "هاي"
8
- }
9
- }
10
- ###
11
- GET https://codeboker-customer-service.hf.space/ai-test HTTP/1.1
12
-
13
- ###
14
- POST https://codeboker-customer-service.hf.space/webhook HTTP/1.1
15
- Content-Type: application/json
16
- {
17
- "message": {
18
- "chat": {"id": 123},
19
- "text": "السلام عليكم وؤحمة الله اريد ان استعلم عنلاالخدمات الالكترونية"
20
- }
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
supabase_schema.sql DELETED
@@ -1,90 +0,0 @@
1
- -- Supabase Database Schema for Conversation History
2
- -- Run this in your Supabase SQL Editor
3
-
4
- -- Enable UUID extension
5
- CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
6
-
7
- -- Users table to store user information
8
- CREATE TABLE IF NOT EXISTS users (
9
- id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
10
- telegram_id BIGINT UNIQUE NOT NULL,
11
- username VARCHAR(255),
12
- first_name VARCHAR(255),
13
- last_name VARCHAR(255),
14
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
16
- );
17
-
18
- -- Messages table to store conversation history
19
- CREATE TABLE IF NOT EXISTS messages (
20
- id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
21
- telegram_id BIGINT NOT NULL,
22
- message_text TEXT NOT NULL,
23
- message_type VARCHAR(20) NOT NULL CHECK (message_type IN ('user', 'assistant')),
24
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
25
- FOREIGN KEY (telegram_id) REFERENCES users(telegram_id) ON DELETE CASCADE
26
- );
27
-
28
- -- Conversation sessions to group messages by session
29
- CREATE TABLE IF NOT EXISTS conversation_sessions (
30
- id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
31
- telegram_id BIGINT NOT NULL,
32
- session_start TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
33
- session_end TIMESTAMP WITH TIME ZONE,
34
- message_count INTEGER DEFAULT 0,
35
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
36
- FOREIGN KEY (telegram_id) REFERENCES users(telegram_id) ON DELETE CASCADE
37
- );
38
-
39
- -- Indexes for better performance
40
- CREATE INDEX IF NOT EXISTS idx_messages_telegram_id_created_at ON messages(telegram_id, created_at);
41
- CREATE INDEX IF NOT EXISTS idx_users_telegram_id ON users(telegram_id);
42
- CREATE INDEX IF NOT EXISTS idx_sessions_telegram_id ON conversation_sessions(telegram_id);
43
-
44
- -- Function to update updated_at timestamp
45
- CREATE OR REPLACE FUNCTION update_updated_at_column()
46
- RETURNS TRIGGER AS $$
47
- BEGIN
48
- NEW.updated_at = NOW();
49
- RETURN NEW;
50
- END;
51
- $$ language 'plpgsql';
52
-
53
- -- Trigger to update user's updated_at timestamp
54
- CREATE TRIGGER update_user_updated_at
55
- BEFORE UPDATE ON users
56
- FOR EACH ROW
57
- EXECUTE FUNCTION update_updated_at_column();
58
-
59
- -- Trigger to update session message count
60
- CREATE OR REPLACE FUNCTION update_session_count()
61
- RETURNS TRIGGER AS $$
62
- BEGIN
63
- UPDATE conversation_sessions
64
- SET message_count = message_count + 1
65
- WHERE telegram_id = NEW.telegram_id AND session_end IS NULL;
66
- RETURN NEW;
67
- END;
68
- $$ language 'plpgsql';
69
-
70
- CREATE TRIGGER update_session_message_count
71
- AFTER INSERT ON messages
72
- FOR EACH ROW
73
- EXECUTE FUNCTION update_session_count();
74
-
75
- -- Row Level Security (RLS) policies
76
- ALTER TABLE users ENABLE ROW LEVEL SECURITY;
77
- ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
78
- ALTER TABLE conversation_sessions ENABLE ROW LEVEL SECURITY;
79
-
80
- -- Policy for users table (allow all operations for now)
81
- CREATE POLICY "Enable all operations for users" ON users
82
- FOR ALL USING (true);
83
-
84
- -- Policy for messages table (allow all operations for now)
85
- CREATE POLICY "Enable all operations for messages" ON messages
86
- FOR ALL USING (true);
87
-
88
- -- Policy for conversation_sessions table (allow all operations for now)
89
- CREATE POLICY "Enable all operations for conversation_sessions" ON conversation_sessions
90
- FOR ALL USING (true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
telegram_handlers.py DELETED
@@ -1,149 +0,0 @@
1
- from pydantic import BaseModel
2
- import httpx
3
- import json
4
- from config import TELEGRAM_URL
5
- from ai_service import get_ai_response
6
- from database import db_manager
7
-
8
- TELEGRAM_IP = "149.154.167.220"
9
- MAX_TELEGRAM_MESSAGE_LENGTH = 4096
10
-
11
-
12
- def _sanitize_telegram_text(text: str) -> str:
13
- if text is None:
14
- return ""
15
-
16
- normalized = str(text).replace("\r\n", "\n").replace("\r", "\n")
17
-
18
- # Remove control/surrogate chars that Telegram can reject.
19
- cleaned = "".join(
20
- ch
21
- for ch in normalized
22
- if (ch in ("\n", "\t") or ord(ch) >= 32) and not (0xD800 <= ord(ch) <= 0xDFFF)
23
- )
24
- return cleaned.strip()
25
-
26
-
27
- def _split_telegram_message(text: str, max_length: int = MAX_TELEGRAM_MESSAGE_LENGTH):
28
- if len(text) <= max_length:
29
- return [text]
30
-
31
- parts = []
32
- remaining = text
33
-
34
- while len(remaining) > max_length:
35
- split_at = remaining.rfind("\n", 0, max_length)
36
- if split_at == -1:
37
- split_at = remaining.rfind(" ", 0, max_length)
38
- if split_at == -1:
39
- split_at = max_length
40
-
41
- part = remaining[:split_at].strip()
42
- if not part:
43
- part = remaining[:max_length]
44
- split_at = max_length
45
-
46
- parts.append(part)
47
- remaining = remaining[split_at:].strip()
48
-
49
- if remaining:
50
- parts.append(remaining)
51
-
52
- return parts
53
-
54
- class ChatInfo(BaseModel):
55
- id: int
56
- username: str = None
57
- first_name: str = None
58
- last_name: str = None
59
-
60
- class Message(BaseModel):
61
- chat: ChatInfo
62
- text: str = ""
63
-
64
- class WebhookData(BaseModel):
65
- message: Message = None
66
-
67
- async def telegram_webhook(data: WebhookData):
68
- try:
69
- if not data.message or not data.message.text:
70
- return {"status": "ok"}
71
-
72
- telegram_id = data.message.chat.id
73
- user_text = data.message.text
74
-
75
- username = data.message.chat.username
76
- first_name = data.message.chat.first_name
77
-
78
- print(f"--- Processing message from {first_name} ---")
79
-
80
- if db_manager:
81
- db_manager.create_or_update_user(telegram_id, username, first_name, data.message.chat.last_name)
82
-
83
-
84
- ai_answer = await get_ai_response(user_text, telegram_id)
85
-
86
-
87
-
88
- if TELEGRAM_URL:
89
- try:
90
- from config import TELEGRAM_TOKEN
91
-
92
- async with httpx.AsyncClient(timeout=40.0, verify=False, follow_redirects=True) as client:
93
- prepared_text = _sanitize_telegram_text(
94
- ai_answer or "Sorry, I couldn't generate a response. Please try again."
95
- )
96
- if not prepared_text:
97
- prepared_text = "Sorry, I couldn't generate a response. Please try again."
98
-
99
- message_parts = _split_telegram_message(prepared_text, MAX_TELEGRAM_MESSAGE_LENGTH)
100
-
101
- for idx, part in enumerate(message_parts, start=1):
102
- payload = {
103
- "chat_id": telegram_id,
104
- "text": part,
105
- }
106
-
107
- print(
108
- f"--- Sending Telegram message part {idx}/{len(message_parts)} "
109
- f"(chars={len(part)}) ---"
110
- )
111
-
112
- try:
113
- # Use form data for maximal compatibility with Telegram endpoint parsers.
114
- response = await client.post(TELEGRAM_URL, data=payload)
115
- except Exception:
116
- print(f"--- DNS Failed. Forcing Direct IP Routing to {TELEGRAM_IP} ---")
117
-
118
- forced_ip_url = f"https://{TELEGRAM_IP}/bot{TELEGRAM_TOKEN}/sendMessage"
119
- json_body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
120
- headers = {
121
- "Host": "api.telegram.org",
122
- "Content-Type": "application/json; charset=utf-8",
123
- "Content-Length": str(len(json_body)),
124
- }
125
-
126
- response = await client.post(
127
- forced_ip_url,
128
- content=json_body,
129
- headers=headers,
130
- )
131
-
132
- if response.status_code != 200:
133
- print(f"--- Telegram payload rejected (part {idx}) ---")
134
- print(f"--- Payload: {payload} ---")
135
- print(
136
- f"--- Telegram Rejected Request: "
137
- f"{response.status_code} - {response.text} ---"
138
- )
139
- break
140
- else:
141
- print("--- Success: All Telegram message parts delivered ---")
142
-
143
- except Exception as send_error:
144
- print(f"--- Emergency: Network Blockage Detected: {str(send_error)} ---")
145
-
146
- return {"status": "ok"}
147
- except Exception as e:
148
- print(f"Error in webhook: {str(e)}")
149
- return {"status": "error", "message": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils.py DELETED
@@ -1,16 +0,0 @@
1
- import socket
2
-
3
- async def dns_test():
4
- try:
5
- ip = socket.gethostbyname("api.telegram.org")
6
- return {"resolved_ip": ip}
7
- except Exception as e:
8
- return {"error": str(e)}
9
-
10
- async def test_ai_response():
11
- try:
12
- from ai_service import get_ai_response
13
- response = await get_ai_response("عن ماذا كنت اسال", 12)
14
- return {"response": response}
15
- except Exception as e:
16
- return {"error": str(e)}