File size: 13,466 Bytes
de7999b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f910d32
de7999b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a7be99
de7999b
3a7be99
de7999b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a7be99
de7999b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515dd18
de7999b
 
 
 
515dd18
de7999b
 
 
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
import gradio as gr
import json
import os
import requests
import sys
from gradio.themes.utils import colors

# === Environment Setup ===
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
if not GROQ_API_KEY:
    print("⚠️ GROQ_API_KEY not set. Set it in Hugging Face Secrets.")
    GROQ_API_KEY = "gsk-fake-for-testing"  # Fallback for local testing

# === File Definitions for Persistence ===
HISTORY_FILE_TEMPLATE = "chat_history_{}.json"
USER_PROFILE_FILE = "user_profiles.json"

# --- Personalized History Management ---
def get_history_filename(user_id):
    return HISTORY_FILE_TEMPLATE.format(user_id)

def save_history(history, user_id):
    filename = get_history_filename(user_id)
    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(history, f, ensure_ascii=False, indent=2)

def load_history(history, user_id):
    filename = get_history_filename(user_id)
    if os.path.exists(filename):
        with open(filename, 'r', encoding='utf-8') as f:
            return json.load(f)
    return []

def clear_history_file(user_id):
    if not isinstance(user_id, str):  # Ensure user_id is a valid string
        print(f"❌ Invalid user_id format: {user_id}")
        user_id = "anonymous"  # Fallback to prevent file errors

    filename = get_history_filename(user_id)  # Generates correct file name
    print(f"πŸ—‘ Clearing chat history for {user_id}, file: {filename}")
    
    try:
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump([], f, ensure_ascii=False, indent=2)
    except Exception as e:
        print(f"❌ Failed to clear chat history: {e}")
        
# === GROQ API Config ===
GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions"
MODEL_NAME = "llama3-8b-8192"

def query_groq(message, history):
    if not GROQ_API_KEY or not GROQ_API_KEY.startswith("gsk_"):
        print("🚫 GROQ_API_KEY is missing or invalid.", file=sys.stderr)
        return "GROQ_API_KEY is missing or invalid. Please check Hugging Face Secrets."
    
    headers = {
        "Authorization": f"Bearer {GROQ_API_KEY}",
        "Content-Type": "application/json"
    }
    payload = {
        "model": MODEL_NAME,
        "messages": history,
        "temperature": 0.7
    }
    try:
        response = requests.post(GROQ_API_URL, headers=headers, json=payload)
        if response.status_code == 200:
            return response.json()["choices"][0]["message"]["content"].strip()
        else:
            return f"❌ GROQ API Error {response.status_code}: {response.text}"
    except Exception as e:
        return f"⚠️ Failed to call GROQ API: {str(e)}"

# === Chatbot Response Function ===
def respond_chat(message, history, user_id, profile):
    if history is None or not history:
        history = load_history(user_id, profile)  # Load user-specific past history
    
    user_profile = update_user_profile(user_id, message)

    system_message = {
        "role": "system",
        "content": f"You are a friendly travel assistant named Sky. User profile: {user_profile}"
    }

    # Include conversation history in API request
    messages_for_api = history + [system_message, {"role": "user", "content": message}]

    response_text = query_groq(message, messages_for_api)

    history.append({"role": "user", "content": message})
    history.append({"role": "assistant", "content": response_text})

    save_history(history, user_id)  # Save chat history after each interaction

    return history, history  # Persist chat history in Gradio UI

# === User Profile Management ===
def save_user_profile(user_id, profile_data):
    profiles = load_user_profiles()
    profiles[user_id] = profile_data
    with open(USER_PROFILE_FILE, 'w', encoding='utf-8') as f:
        json.dump(profiles, f, ensure_ascii=False, indent=2)

def load_user_profiles():
    if os.path.exists(USER_PROFILE_FILE):
        with open(USER_PROFILE_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {}

def update_user_profile(user_id, user_msg):
    profiles = load_user_profiles()
    profile = profiles.get(user_id, {
        "name": user_id,
        "preferences": {},
        "past_trips": [],
        "travel_style": "Unknown"
    })
    
    keywords = ["beach", "mountain", "budget", "luxury", "adventure", "relaxation"]
    for word in keywords:
        if word in user_msg.lower():
            profile["preferences"][word] = True
    
    save_user_profile(user_id, profile)
    return profile

# === Function to Update Preferences from Sidebar ===
def update_preferences(budget, language, destination, current_location, days, traveling_alone, diet):
    user_id = "anonymous"
    
    profiles = load_user_profiles()
    profile = profiles.get(user_id, {
        "name": user_id,
        "preferences": {},
        "past_trips": [],
        "travel_style": "Unknown"
    })
    
    profile["budget"] = budget
    profile["language"] = language
    profile["destination"] = destination
    profile["travel_days"] = days
    profile["traveling_alone"] = traveling_alone
    profile["current_location"] = current_location
    profile["dietary_restrictions"] = diet
    
    save_user_profile(user_id, profile)
    
    return f"Travel preferences updated for user {user_id}."


# === Function to Clear Chat History ===
def clear_chat_history(user_id):
    print(f"πŸ—‘ Clearing chat history for user: {user_id}")  # Debugging
    clear_history_file(user_id)  # Delete chat history file
    return [], []  # Reset displayed chatbot conversation






# Function to generate a receipt for budget tab
def generate_receipt(expenses):
    categories = {
        "Food": 0,
        "Transportation": 0,
        "Accommodation": 0,
        "Other": 0
    }
    total_cost = 0

    for item in expenses:
        category = item.get("category", "Other")
        amount = item.get("amount", 0)
        categories[category] += amount
        total_cost += amount

    receipt = "**Receipt:**\n\n"
    for category, amount in categories.items():
        receipt += f"{category}: ${amount}\n"
    receipt += f"\n**Total Cost:** ${total_cost}\n"
    
    return receipt

# Function to generate an itinerary based on number of days and solo travel
def create_itinerary(days, traveling_alone):
    itinerary = "**Your Trip Itinerary:**\n\n"
    for day in range(1, days + 1):
        if traveling_alone == "Yes":
            itinerary += f"Day {day}: Explore local attractions, enjoy a quiet meal, and relax.\n"
        else:
            itinerary += f"Day {day}: Group tour, shared meals, and adventure activities.\n"
    
    return itinerary


#Theme stuff
travelwise_purple = colors.Color(
    name="travelwise_purple",
    c50="#f4f0f8",
    c100="#e3d9ee",
    c200="#c7b3dc",
    c300="#ab8dca",
    c400="#8f67b8",
    c500="#947EB0",  # main color
    c600="#7e5f97",
    c700="#5e476f",
    c800="#3f2f47",
    c900="#1f1820",
    c950="#0f0c10"
)

light_neutral = colors.Color(
    name="light_neutral",
    c50="#ffffff",
    c100="#f9f9f9",
    c200="#f3f3f3",
    c300="#eaeaea",
    c400="#d9d9d9",
    c500="#c0c0c0",
    c600="#a7a7a7",
    c700="#8e8e8e",
    c800="#757575",
    c900="#5c5c5c",
    c950="#3c3c3c"
)

my_theme = gr.themes.Soft(
    primary_hue= travelwise_purple,
    neutral_hue= light_neutral,  # optional
).set(
    body_background_fill="#f1eeccff",
    button_secondary_background_fill="#ABA8A6",
    button_primary_text_color="#004643"
)


def generate_receipt(expenses):
    categories = {
        "Food": 0,
        "Transportation": 0,
        "Accommodation": 0,
        "Other": 0
    }
    total_cost = 0
    for item in expenses:
        category = item.get("category", "Other")
        amount = item.get("amount", 0)
        # If a category is not predefined, accumulate it under 'Other'
        if category not in categories:
            categories["Other"] += amount
        else:
            categories[category] += amount
        total_cost += amount
    receipt = "**Receipt:**\n\n"
    for cat, amt in categories.items():
        receipt += f"{cat}: ${amt}\n"
    receipt += f"\n**Total Cost:** ${total_cost}\n"
    return receipt

def parse_expenses(user_input):
    """
    Parses a string of expenses in the format:
    "Food - 20, Transportation - 15, Accommodation - 100"
    into a list of dictionaries required by generate_receipt.
    """
    expenses = []
    if not user_input.strip():
        return "⚠️ Please enter some expenses."
    entries = user_input.split(",")
    for entry in entries:
        try:
            category, amount = entry.strip().split("-")
            expenses.append({
                "category": category.strip().title(),
                "amount": float(amount.strip())
            })
        except ValueError:
            return ("⚠️ Please ensure each expense is entered in the format: "
                    "Category - Amount (e.g., Food - 20, Transportation - 15)")
    return generate_receipt(expenses)
# Gradio UI
# === Gradio UI Setup ===
# === Gradio UI Setup ===
with gr.Blocks(theme=my_theme) as demo:
    
    chat_history = gr.State([])  # Maintain chat history across interactions
    
    # πŸ”Ή Add `user_id_input` to store user identity across sessions πŸ”Ή
    user_id_input = gr.Textbox(value="anonymous", interactive=False, visible=False)  

    with gr.Tabs():
        with gr.TabItem("Chatbot"):
            with gr.Row():
                with gr.Column(scale=2):
                    gr.Image(
                        value="logo.png",
                        width=400,
                        show_label=False,
                        show_download_button=False,
                        container=False,
                        show_share_button=False,
                        interactive=False
                    )
                    gr.Markdown("Plan your perfect trip with ease! Travelwise helps you customize itineraries, explore budgeting options, and get personalized tipsβ€”all based on your travel style and preferences. Whether you're going solo or in a group, relaxing or adventuring, the bot adapts to your needs in real time.")
            with gr.Row():
                with gr.Column(scale=2):
                    gr.Markdown("## Chatbot")
                    chatbot = gr.Chatbot(type="messages")  # Display chat conversation
                    chat_input = gr.Textbox(label="Your Message")  # User input field
                    chat_button = gr.Button("Send Message")  # Send message button
                    clear_history_btn = gr.Button("Clear Chat History")  # Reset conversation button
                with gr.Column(scale=1):
                    gr.Markdown("### ✈️ Travel Preferences")
                    budget = gr.Textbox(label="πŸ’° Budget")
                    language = gr.Dropdown(
                        label="🌐 Language",
                        choices=["English", "Korean", "Japanese", "French", "Spanish", "Chinese", "Italian"]
                    )
                    destination = gr.Textbox(label="πŸ—ΊοΈ Travel Location")
                    current_location = gr.Textbox(label="πŸ“ Current Location")
                    days = gr.Slider(minimum=1, maximum=200, step=1, label="πŸ“† Days Traveling")
                    traveling_alone = gr.Dropdown(label="Traveling Alone?", choices=["Yes", "No"], value="No")
                    diet = gr.Textbox(label="πŸ₯— Dietary Restrictions")
                    pref_button = gr.Button("Update Preferences")
                    pref_confirmation = gr.Textbox(label="Preference Update Confirmation")

        with gr.TabItem("Budget"):
            gr.Markdown("### Enter your expenses in the format: `Category - Amount`, separated by commas.")
            expense_input = gr.Textbox(
                placeholder="Food - 20, Transportation - 15, Accommodation - 100",
                label="Expenses"
            )
            generate_button = gr.Button("Generate Receipt")
            receipt_output = gr.Markdown()

            generate_button.click(
                fn=parse_expenses,
                inputs=[expense_input],
                outputs=[receipt_output]
            )

        with gr.TabItem("Itinerary"):
            days_input = gr.Slider(1, 14, step=1, label="Number of Days")
            solo_input = gr.Dropdown(["Yes", "No"], label="Traveling Alone?")
            generate_itinerary_button = gr.Button("Generate Itinerary")
            itinerary_output = gr.Markdown()

            generate_itinerary_button.click(
                fn=create_itinerary,
                inputs=[days_input, solo_input],
                outputs=[itinerary_output]
            )

    # πŸ”Ή Fix: Ensure user ID is passed correctly πŸ”Ή
    chat_button.click(
        fn=respond_chat, 
        inputs=[chat_input, chat_history, user_id_input],  # βœ… Ensure correct user ID is used
        outputs=[chatbot, chat_history]
    )

    # πŸ”Ή Fix: Correctly clear history by passing `user_id_input` πŸ”Ή
    clear_history_btn.click(
        fn=clear_chat_history,
        inputs=[user_id_input],  # βœ… Pass stored user ID correctly
        outputs=[chatbot, chat_history]
    )
    # testing with token
    # Bind the sidebar button for updating travel preferences.
    pref_button.click(
        fn=update_preferences,
        inputs=[budget, language, destination, current_location, days, traveling_alone, diet],
        outputs=pref_confirmation 
    )

demo.launch(debug=True)