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

class XylariaChat:
    def __init__(self):
        # Securely load HuggingFace and Firebase tokens from Hugging Face Space Secrets
        self.hf_token = os.environ.get("HF_TOKEN")
        firebase_cred_json = os.environ.get("FIREBASE_SERVICE_ACCOUNT")
        
        if not self.hf_token or not firebase_cred_json:
            raise ValueError("Required secrets (HF_TOKEN or FIREBASE_SERVICE_ACCOUNT) not found")
        
        # Initialize Firebase
        try:
            # Convert the JSON string to a temporary credentials file
            import tempfile
            import json
            
            with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as temp_cred_file:
                json.dump(json.loads(firebase_cred_json), 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()
            
            # Remove the temporary credentials file
            os.unlink(temp_cred_file.name)
        except Exception as e:
            print(f"Firebase initialization error: {e}")
            self.db = None
        
        # Initialize the inference client
        self.client = InferenceClient(
            model="Qwen/QwQ-32B-Preview", 
            api_key=self.hf_token
        )
        
        # Initialize conversation history
        self.conversation_history = []

    def signup(self, username, email, password):
        """User signup method"""
        try:
            # Create user in Firebase Authentication
            user = auth.create_user(
                email=email,
                password=password,
                display_name=username
            )
            
            # Store additional user info in Firestore
            if self.db:
                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"""
        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):
        # 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;
            }
            .chatbot .user-message {
                background-color: #2563eb;
                color: white;
            }
            .chatbot .bot-message {
                background-color: #1f2937;
                color: #f3f4f6;
                box-shadow: 0 1px 3px 0 rgba(255, 255, 255, 0.1);
            }
            .chatbot-input-row {
                background-color: #1f2937;
                color: #f3f4f6;
            }
            .send-button {
                background-color: #2563eb;
                color: white;
            }
        }
        
        @media (prefers-color-scheme: light) {
            .gradio-container {
                font-family: 'Inter', sans-serif;
                background-color: #f3f4f6;
                color: #1f2937;
            }
            .chatbot .message {
                max-width: 80%;
                border-radius: 1rem;
                padding: 0.75rem;
                margin-bottom: 0.5rem;
            }
            .chatbot .user-message {
                background-color: #3b82f6;
                color: white;
                align-self: flex-end;
            }
            .chatbot .bot-message {
                background-color: white;
                color: #1f2937;
                align-self: flex-start;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
            }
            .chatbot-input-row {
                background-color: white;
                border-radius: 9999px;
                padding: 0.5rem 1rem;
            }
            .send-button {
                background-color: #3b82f6;
                color: white;
                border-radius: 9999px;
                padding: 0.5rem;
            }
        }
        """

        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, 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
            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
            
            # 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,
                    likeable=True
                )
                
                # Input row with minimalist design
                with gr.Row():
                    txt = gr.Textbox(
                        show_label=False, 
                        placeholder="Type your message...", 
                        container=False,
                        scale=4,
                        container_css_class="chatbot-input-row"
                    )
                    btn = gr.Button("Send", css_class="send-button", 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 conversation history
            clear.click(
                fn=lambda: None, 
                inputs=None, 
                outputs=[chatbot],
                queue=False
            )
        
        return demo

# Launch the interface
def main():
    chat = XylariaChat()
    interface = chat.create_interface()
    interface.launch(
        share=True,  # Optional: create a public link
        debug=True   # Show detailed errors
    )

if __name__ == "__main__":
    main()