File size: 10,310 Bytes
24342ea
a184be7
65a6bd0
a806d95
 
 
dd67f43
 
e1ff28f
a184be7
 
dd67f43
a806d95
 
 
bf2bb14
 
 
a806d95
bf2bb14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a184be7
bf2bb14
 
 
 
 
 
 
 
 
 
24342ea
a806d95
a184be7
9f69ff9
a806d95
 
bf2bb14
 
 
a806d95
 
 
 
 
 
 
 
 
bf2bb14
 
 
 
 
 
a806d95
 
 
 
9f69ff9
a806d95
 
bf2bb14
 
 
a806d95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24342ea
a806d95
bf2bb14
 
 
 
a806d95
a184be7
a806d95
 
 
 
a184be7
24342ea
a806d95
 
9f69ff9
 
a184be7
9f69ff9
a184be7
 
 
 
 
 
9f69ff9
 
 
a184be7
 
 
 
a806d95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a184be7
a806d95
 
 
 
 
 
 
 
9f69ff9
bf2bb14
a184be7
 
 
9f69ff9
a184be7
 
9f69ff9
 
bf2bb14
 
 
 
 
 
 
 
 
 
 
a184be7
a806d95
 
9f69ff9
a806d95
caf6b1d
9f69ff9
a806d95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9f69ff9
 
 
bf2bb14
 
9f69ff9
 
a806d95
9f69ff9
 
 
 
bf2bb14
9f69ff9
bf2bb14
9f69ff9
a806d95
9f69ff9
a806d95
 
 
 
 
 
 
 
 
 
 
 
 
a184be7
9f69ff9
 
 
 
 
 
 
 
 
 
 
dd67f43
a184be7
dd67f43
24342ea
bf2bb14
dd67f43
bf2bb14
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
import os
import gradio as gr
from huggingface_hub import InferenceClient
import firebase_admin
from firebase_admin import credentials, firestore, auth
import uuid
import tempfile
import json

class XylariaChat:
    def __init__(self):
        # Securely load HuggingFace and Firebase tokens from environment variables
        self.hf_token = os.environ.get("HF_TOKEN")
        firebase_cred_json = os.environ.get("FIREBASE_SERVICE_ACCOUNT")
        
        # Optional: Add more robust error handling for missing environment variables
        if not self.hf_token:
            print("Warning: HuggingFace token not found. Some functionality may be limited.")
        
        # Firebase initialization
        self.db = None
        if firebase_cred_json:
            try:
                # Convert the JSON string to a temporary credentials file
                with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as temp_cred_file:
                    # Add error handling for JSON parsing
                    try:
                        firebase_creds = json.loads(firebase_cred_json)
                        json.dump(firebase_creds, temp_cred_file)
                        temp_cred_file.close()
                        
                        firebase_cred = credentials.Certificate(temp_cred_file.name)
                        firebase_admin.initialize_app(firebase_cred)
                        self.db = firestore.client()
                    except json.JSONDecodeError:
                        print("Error: Invalid Firebase credentials JSON")
                    
                # Remove the temporary credentials file
                os.unlink(temp_cred_file.name)
            except Exception as e:
                print(f"Firebase initialization error: {e}")
        
        # Initialize the inference client (only if token is available)
        self.client = None
        if self.hf_token:
            try:
                self.client = InferenceClient(
                    model="Qwen/QwQ-32B-Preview", 
                    api_key=self.hf_token
                )
            except Exception as e:
                print(f"Inference client initialization error: {e}")
        
        # Initialize conversation history
        self.conversation_history = []

    def signup(self, username, email, password):
        """User signup method"""
        if not self.db:
            return "Firebase is not initialized. Signup unavailable."
        
        try:
            # Create user in Firebase Authentication
            user = auth.create_user(
                email=email,
                password=password,
                display_name=username
            )
            
            # Store additional user info in Firestore
            user_ref = self.db.collection('users').document(user.uid)
            user_ref.set({
                'username': username,
                'email': email,
                'created_at': firestore.SERVER_TIMESTAMP
            })
            
            return f"Successfully created account for {username}"
        except Exception as e:
            return f"Signup error: {str(e)}"

    def login(self, email, password):
        """User login method"""
        if not self.db:
            return "Firebase is not initialized. Login unavailable."
        
        try:
            # Authenticate user with Firebase
            user = auth.get_user_by_email(email)
            
            # In a real-world scenario, you'd use Firebase Auth's sign-in method
            # This is a simplified version for demonstration
            return f"Login successful for {user.display_name}"
        except Exception as e:
            return f"Login error: {str(e)}"

    def save_message(self, user_id, message, sender):
        """Save message to Firestore"""
        if not self.db:
            return
        
        try:
            messages_ref = self.db.collection('conversations').document(user_id)
            messages_ref.collection('messages').add({
                'text': message,
                'sender': sender,
                'timestamp': firestore.SERVER_TIMESTAMP
            })
        except Exception as e:
            print(f"Error saving message: {e}")

    def get_response(self, user_input, chat_history):
        # Check if client is initialized
        if not self.client:
            return "AI response is currently unavailable."
        
        # Prepare messages with conversation context
        messages = [
            {"role": "system", "content": """You are Xylaria 1.4 Senoa, an AI assistant developed by SK MD Saad Amin. 
            - You're NOT MADE BY OPENAI OR ANY OTHER INSTITUTION
            - Be friendly and chatty, use emoji sometimes."""},
            *[{"role": "user" if msg[0] else "assistant", "content": msg[1]} for msg in chat_history]
        ]
        
        # Add current user input
        messages.append({"role": "user", "content": user_input})
        
        # Generate response with streaming
        try:
            stream = self.client.chat.completions.create(
                messages=messages,
                temperature=0.5,
                max_tokens=10240,
                top_p=0.7,
                stream=True
            )
            
            return stream
        
        except Exception as e:
            return f"Error generating response: {str(e)}"

    def create_interface(self):
        # Dynamic CSS for OS theme detection
        custom_css = """
        /* OS Theme Detection */
        @media (prefers-color-scheme: dark) {
            .gradio-container {
                background-color: #121212;
                color: #ffffff;
            }
        }
        
        @media (prefers-color-scheme: light) {
            .gradio-container {
                font-family: 'Inter', sans-serif;
                background-color: #f3f4f6;
                color: #1f2937;
            }
        }
        """

        def streaming_response(message, chat_history):
            # Unique user ID (in a real app, you'd use actual user authentication)
            user_id = str(uuid.uuid4())
            
            # Save user message to Firestore
            self.save_message(user_id, message, 'user')
            
            # Get AI response stream
            response_stream = self.get_response(message, chat_history)
            
            # If it's an error or string, return immediately
            if isinstance(response_stream, str):
                return "", chat_history + [[message, response_stream]]
            
            # Prepare for streaming response
            full_response = ""
            updated_history = chat_history + [[message, ""]]
            
            # Streaming output
            try:
                for chunk in response_stream:
                    if chunk.choices[0].delta.content:
                        chunk_content = chunk.choices[0].delta.content
                        full_response += chunk_content
                        
                        # Update the last message in chat history with partial response
                        updated_history[-1][1] = full_response
                        yield "", updated_history
            except Exception as e:
                return "", updated_history + [["", f"Error in response: {str(e)}"]]
            
            # Save bot message to Firestore
            self.save_message(user_id, full_response, 'bot')
            
            return "", updated_history

        with gr.Blocks(theme='soft', css=custom_css) as demo:
            # Authentication Tab
            with gr.Tabs():
                with gr.TabItem("Login"):
                    with gr.Column():
                        login_email = gr.Textbox(label="Email")
                        login_password = gr.Textbox(label="Password", type="password")
                        login_btn = gr.Button("Login")
                        login_output = gr.Textbox(label="Login Status")
                
                with gr.TabItem("Signup"):
                    with gr.Column():
                        signup_username = gr.Textbox(label="Username")
                        signup_email = gr.Textbox(label="Email")
                        signup_password = gr.Textbox(label="Password", type="password")
                        signup_btn = gr.Button("Sign Up")
                        signup_output = gr.Textbox(label="Signup Status")
            
            # Chat Interface
            with gr.Column(scale=1):
                chatbot = gr.Chatbot(
                    label="Xylaria 1.4 Senoa",
                    height=500,
                    show_copy_button=True,
                    type="messages"  # Added to resolve deprecation warning
                )
                
                # Input row with minimalist design
                with gr.Row():
                    txt = gr.Textbox(
                        show_label=False, 
                        placeholder="Type your message...", 
                        scale=4
                    )
                    btn = gr.Button("Send", scale=1)
                
                # Clear conversation button
                clear = gr.Button("Clear Conversation")
            
            # Authentication Event Handlers
            login_btn.click(
                fn=self.login, 
                inputs=[login_email, login_password], 
                outputs=[login_output]
            )
            
            signup_btn.click(
                fn=self.signup, 
                inputs=[signup_username, signup_email, signup_password], 
                outputs=[signup_output]
            )
            
            # Submit functionality with streaming
            btn.click(
                fn=streaming_response, 
                inputs=[txt, chatbot], 
                outputs=[txt, chatbot]
            )
            txt.submit(
                fn=streaming_response, 
                inputs=[txt, chatbot], 
                outputs=[txt, chatbot]
            )
            clear.click(lambda: None, None, chatbot)

        return demo

# Note: Actual launch should be done with proper environment variable setup
xylaria_chat = XylariaChat()
xylaria_chat.create_interface().launch(share=True)