File size: 8,797 Bytes
68964c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e8c6de7
 
 
 
 
 
 
 
 
 
 
899c2a1
 
 
68964c2
 
 
e8c6de7
 
 
899c2a1
 
 
e8c6de7
68964c2
e8c6de7
 
 
68964c2
e8c6de7
68964c2
e8c6de7
68964c2
f13a869
 
 
 
 
 
 
 
 
 
 
 
 
 
68964c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899c2a1
68964c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
from dotenv import load_dotenv
load_dotenv()
from fastapi import APIRouter, Request
from pydantic import BaseModel
from datetime import datetime, timezone
from app.core.device_setup import device
from app.core.message_saving import save_message
from app.core.past_conversations import get_past_conversations
from app.core.memory_management import reset_memory, get_last_interaction, update_last_interaction
from app.core.fact_management import get_user_fact, save_user_fact
from app.core.conversation_retrieval import get_similar_conversations
from app.core.feedback_management import apply_feedback_adjustments
from app.core.logging_setup import logger
from app.core.prompts import SYSTEM_PROMPT
from app.core.interaction_trends import get_time_of_day
from app.core.search_utils import needs_web_search, search_duckduckgo
import os
import asyncio

logger.info("Logger imported successfully in chat_hf.py")

HUGGINGFACE_TOKEN = os.getenv("HF_TOKEN", "")

if not HUGGINGFACE_TOKEN:
    raise ValueError("❌ Hugging Face Token (HF_TOKEN) has not been set yet")

headers = {
    "Authorization": f"Bearer {HUGGINGFACE_TOKEN}"
}

# Replace local model loading with Gemini API integration
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")

if not GEMINI_API_KEY:
    raise ValueError("❌ Gemini API Key (GEMINI_API_KEY) has not been set yet")

def query_gemini_api(prompt: str) -> str:
    import requests
    url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
    headers = {"Content-Type": "application/json"}
    payload = {
        "prompt": {"text": prompt},
        "temperature": 0.7,
        "maxOutputTokens": 256
    }

    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()
        data = response.json()
        return data.get("candidates", [{}])[0].get("output", "")
    except requests.exceptions.RequestException as e:
        logger.error(f"🚨 Error querying Gemini API: {e}, Response: {response.text if 'response' in locals() else 'No response'}")
        return "⚠️ An error occurred while generating a response."

# Replace generate_response and query_huggingface with query_gemini_api
def generate_response(prompt_text):
    return query_gemini_api(prompt_text)

# Ensure query_huggingface uses the Gemini API
def query_huggingface(prompt: str) -> str:
    return query_gemini_api(prompt)

def build_clean_prompt(messages):
    """Construct a clean prompt for the AI model."""
    role_map = {
        "system": "System",
        "user": "User",
        "assistant": "Arina"
    }
    prompt = ""
    for msg in messages:
        role = role_map.get(msg["role"], "User")
        prompt += f"{role}: {msg['content'].strip()}\n"
    prompt += "Arina:"
    return prompt

router = APIRouter()

class ChatRequest(BaseModel):
    message: str

@router.post("/chat")
async def chat_with_arina(request: Request, request_body: ChatRequest):
    user_input = request_body.message.strip()
    logger.info(f"πŸ“© User input: {user_input}")
    logger.info("Logger is accessible in the /chat endpoint.")

    # Handle shutdown command
    if user_input.lower() == "arina, shutdown.":
        logger.info("πŸ›‘ Shutdown command received. Shutting down the server.")

        # Trigger a graceful shutdown
        async def shutdown():
            logger.info("πŸ›‘ Initiating server shutdown.")
            request.app.state.should_exit = True  # Signal uvicorn to exit

        asyncio.create_task(shutdown())
        os._exit(0)  # Forcefully exit the entire system
        return {"response": "πŸ›‘ Server is shutting down."}

    if 'logger' not in globals():
        print("DEBUG: Logger is not accessible in the /chat endpoint.")

    # Check if the user's query requires a web search
    if needs_web_search(user_input):
        logger.info(f"🌍 Web search triggered for: {user_input}")
        search_summary, search_links = search_duckduckgo(user_input)

        search_context = f"I found the following information: {search_summary}"
        if search_links:
            search_context += f" (Related links: {', '.join(search_links)})"

        dynamic_prompt = (
            f"User asked: {user_input}\n"
            f"{search_context}\n"
            f"Based on this, please provide a natural, conversational response "
            f"that integrates this information without listing out links verbatim."
        )

        # Initialize messages
        messages = [{"role": "system", "content": SYSTEM_PROMPT}]
        messages = apply_feedback_adjustments(messages)  # Optional: Apply feedback adjustments

        logger.info("🧠 Loading for web search response")
        try:
            prompt_text = "\n".join([msg["content"] for msg in messages])

            arina_reply = query_huggingface(prompt_text).strip()

            if not arina_reply:
                arina_reply = "I'm not sure how to respond to that, but I'm here to help."
        except Exception as e:
            logger.error(f"🚨 Error connecting to web search: {e}")
            arina_reply = "⚠️ Arina is having trouble responding. Try again."

        return {"response": arina_reply}

    # Handle reset command
    if user_input.lower() == "arina, reset.":
        reset_memory()
        return {"response": "βœ… Memory wiped."}

    # Retrieve user-specific data
    user_name = get_user_fact("name")

    # Retrieve past relevant conversations
    history = get_past_conversations(limit=3)
    formatted_history = [{"role": role, "content": msg} for _, role, msg, _ in history]
    relevant_history = get_similar_conversations(user_input, top_n=3)
    formatted_relevant_history = [{"role": "system", "content": f"Previously, the user discussed: {msg}"} for msg in relevant_history]

    # Generate time-aware context
    last_interaction = get_last_interaction()
    current_time_of_day = get_time_of_day()
    most_active_time = get_user_fact("most_active_time") or "unknown"

    time_context = f"Be aware that it is {current_time_of_day}. Adjust the conversation naturally based on this."
    if most_active_time != "unknown":
        time_context += f" The user is usually active in the {most_active_time}. Adjust your tone accordingly."

    if last_interaction:
        # Ensure current datetime is timezone-aware
        time_gap = (datetime.now(timezone.utc) - last_interaction).total_seconds()
        if time_gap > 86400:
            time_context += " The user has returned after a long time. Let them feel welcomed without explicitly mentioning the gap."
        elif time_gap > 43200:
            time_context += f" Since it is {current_time_of_day}, ensure your response flows accordingly."
        elif time_gap > 18000:
            time_context += f" Adapt the conversation for a {current_time_of_day} chat naturally."
        else:
            time_context += " The conversation is active; keep it engaging."

    # Construct messages for the AI model
    system_prompt_adjusted = apply_feedback_adjustments([{"role": "system", "content": SYSTEM_PROMPT}])[0]["content"]
    messages = [{"role": "system", "content": system_prompt_adjusted + "\n\n" + time_context}]
    messages.extend(formatted_history)
    messages.extend(formatted_relevant_history)
    messages.append({"role": "user", "content": user_input})

    # Call the AI model
    logger.info("🧠 Loading for general chat response")

    try:
        prompt_text = build_clean_prompt(messages)

        arina_reply = query_huggingface(prompt_text).strip()

        if not arina_reply:
            logger.warning("⚠️ Empty response!")
            arina_reply = "πŸ€– I'm not sure how to respond to that."
    except Exception as e:
        logger.error(f"🚨 Error connecting: {e}")
        arina_reply = "⚠️ Arina is having trouble responding. Try again."

    # Save conversation to memory
    try:
        save_message(datetime.now(timezone.utc), "global_chat", "user", user_input)
        save_message(datetime.now(timezone.utc), "global_chat", "assistant", arina_reply)
        update_last_interaction()

        # Update most active time based on latest interaction
        current_hour = datetime.now(timezone.utc).hour
        if 6 <= current_hour < 12:
            save_user_fact("most_active_time", "morning")
        elif 12 <= current_hour < 18:
            save_user_fact("most_active_time", "afternoon")
        elif 18 <= current_hour < 24:
            save_user_fact("most_active_time", "evening")
        else:
            save_user_fact("most_active_time", "night")
    except Exception as e:
        logger.error(f"🚨 Error saving message to database: {e}")

    logger.info(f"πŸ’¬ Arina's reply: {arina_reply}")
    return {"response": arina_reply}