File size: 18,311 Bytes
deb090d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
import React, { useState, useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { PaperAirplaneIcon, StopIcon } from '@heroicons/react/24/solid';
import MessageBubble from './MessageBubble';
import TypingIndicator from './TypingIndicator';
import FileUploader from './FileUploader';
import { sendMessage, sendMessageStream } from '../services/api';
import toast from 'react-hot-toast';

const ChatInterface = ({ conversationId, conversations, setConversations, darkMode }) => {
  const [message, setMessage] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [showFileUploader, setShowFileUploader] = useState(false);
  const messagesEndRef = useRef(null);
  const textareaRef = useRef(null);

  const currentConversation = conversations.find(conv => conv.id === conversationId);
  const messages = currentConversation?.messages || [];

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!message.trim() || isLoading) return;

    const userMessage = {
      id: Date.now().toString(),
      role: 'user',
      content: message.trim(),
      timestamp: new Date(),
    };

    // Add user message immediately
    setConversations(prev => prev.map(conv => 
      conv.id === conversationId 
        ? { 
            ...conv, 
            messages: [...conv.messages, userMessage],
            title: conv.messages.length === 0 ? message.slice(0, 50) + '...' : conv.title
          }
        : conv
    ));

    setMessage('');
    setIsLoading(true);
    const assistantMessageId = (Date.now() + 1).toString();

    try {
      let fullResponse = '';

      // Add a placeholder for the assistant's message
      setConversations(prev => prev.map(conv =>
        conv.id === conversationId
          ? { ...conv, messages: [...conv.messages, { id: assistantMessageId, role: 'assistant', content: '', timestamp: new Date() }] }
          : conv
      ));

      await sendMessageStream(message.trim(), (chunk) => {
        fullResponse += chunk;
        setConversations(prev => prev.map(conv =>
          conv.id === conversationId
            ? {
                ...conv,
                messages: conv.messages.map(msg =>
                  msg.id === assistantMessageId
                    ? { ...msg, content: fullResponse }
                    : msg
                ),
              }
            : conv
        ));
      });

    } catch (error) {
      toast.error('Failed to send message. Please try again.');
      console.error('Error sending message:', error);
      // Optional: remove placeholder on error
      setConversations(prev => prev.map(conv =>
        conv.id === conversationId
          ? { ...conv, messages: conv.messages.filter(msg => msg.id !== assistantMessageId) }
          : conv
      ));
    } finally {
      setIsLoading(false);
    }
  };

  const handleKeyDown = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      handleSubmit(e);
    }
  };

  const adjustTextareaHeight = () => {
    const textarea = textareaRef.current;
    if (textarea) {
      textarea.style.height = 'auto';
      textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
    }
  };

  useEffect(() => {
    adjustTextareaHeight();
  }, [message]);

  return (
    <div className="flex flex-col h-screen">
      {/* Messages Container */}
      <div className="flex-1 overflow-y-auto px-4 py-6">
        <div className="max-w-3xl mx-auto">
          {/* Empty State */}
          {messages.length === 0 && !isLoading && (
            <motion.div
              initial={{ opacity: 0, y: 20 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ duration: 0.6 }}
              className="flex flex-col items-center justify-center min-h-[60vh] text-center"
            >
              {/* CA Assistant Avatar */}
              <motion.div
                initial={{ scale: 0.8 }}
                animate={{ scale: 1 }}
                transition={{ duration: 0.5, delay: 0.2 }}
                className={`w-20 h-20 rounded-full flex items-center justify-center mb-6 ${
                  darkMode 
                    ? 'bg-gradient-to-br from-primary-600 to-purple-600' 
                    : 'bg-gradient-to-br from-primary-500 to-purple-500'
                } shadow-lg`}
              >
                <svg className="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 
                    d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
                </svg>
              </motion.div>

              {/* Welcome Message */}
              <motion.h2 
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                transition={{ delay: 0.4 }}
                className="text-2xl md:text-3xl font-bold mb-3 gradient-text"
              >
                Hello! I'm your CA Study Assistant
              </motion.h2>
              
              <motion.p 
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                transition={{ delay: 0.5 }}
                className={`text-lg mb-8 ${darkMode ? 'text-gray-300' : 'text-gray-600'}`}
              >
                I'm here to help you with accounting, finance, taxation, and auditing concepts. 
                Ask me anything or upload your study materials!
              </motion.p>

              {/* Quick Start Suggestions */}
              <motion.div
                initial={{ opacity: 0, y: 20 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ delay: 0.6 }}
                className="w-full max-w-2xl"
              >
                <h3 className={`text-sm font-semibold mb-4 ${
                  darkMode ? 'text-gray-400' : 'text-gray-500'
                }`}>
                  Try asking me about:
                </h3>
                
                <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                  {[
                    { icon: "πŸ“Š", text: "Financial statement analysis", query: "Explain financial statement analysis" },
                    { icon: "πŸ’°", text: "Depreciation methods", query: "What are different depreciation methods?" },
                    { icon: "🏦", text: "Working capital management", query: "Explain working capital management" },
                    { icon: "πŸ“ˆ", text: "Ratio analysis", query: "How to perform ratio analysis?" },
                    { icon: "πŸ“‹", text: "Auditing procedures", query: "What are key auditing procedures?" },
                    { icon: "πŸ’Ό", text: "Tax planning strategies", query: "Explain tax planning strategies" }
                  ].map((suggestion, index) => (
                    <motion.button
                      key={index}
                      initial={{ opacity: 0, x: -20 }}
                      animate={{ opacity: 1, x: 0 }}
                      transition={{ delay: 0.7 + index * 0.1 }}
                      whileHover={{ scale: 1.02, y: -2 }}
                      whileTap={{ scale: 0.98 }}
                      onClick={() => setMessage(suggestion.query)}
                      className={`flex items-center p-4 rounded-xl text-left transition-all ${
                        darkMode
                          ? 'bg-gray-800 hover:bg-gray-700 border-gray-700 text-gray-300'
                          : 'bg-gray-50 hover:bg-gray-100 border-gray-200 text-gray-700'
                      } border hover:border-primary-300 hover:shadow-md`}
                    >
                      <span className="text-2xl mr-3">{suggestion.icon}</span>
                      <span className="font-medium">{suggestion.text}</span>
                    </motion.button>
                  ))}
                </div>
              </motion.div>

              {/* Upload Reminder */}
              <motion.div
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                transition={{ delay: 1.2 }}
                className={`mt-8 p-4 rounded-xl ${
                  darkMode 
                    ? 'bg-primary-900/20 border-primary-700/30' 
                    : 'bg-primary-50 border-primary-200'
                } border`}
              >
                <div className="flex items-center justify-center">
                  <svg className={`w-5 h-5 mr-2 ${
                    darkMode ? 'text-primary-400' : 'text-primary-600'
                  }`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 
                      d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                  </svg>
                  <span className={`text-sm ${
                    darkMode ? 'text-primary-300' : 'text-primary-700'
                  }`}>
                    πŸ’‘ Upload your study materials for more specific and detailed answers
                  </span>
                </div>
              </motion.div>
            </motion.div>
          )}

          {/* Messages */}
          <AnimatePresence>
            {messages.map((msg, index) => (
              <MessageBubble
                key={msg.id}
                message={msg}
                darkMode={darkMode}
                isLast={index === messages.length - 1}
              />
            ))}
          </AnimatePresence>

          {isLoading && <TypingIndicator darkMode={darkMode} />}
          
          <div ref={messagesEndRef} />
        </div>
      </div>

      {/* File Uploader Modal */}
      <AnimatePresence>
        {showFileUploader && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
            onClick={() => setShowFileUploader(false)}
          >
            <motion.div
              initial={{ scale: 0.9, opacity: 0 }}
              animate={{ scale: 1, opacity: 1 }}
              exit={{ scale: 0.9, opacity: 0 }}
              onClick={(e) => e.stopPropagation()}
              className={`max-w-md w-full p-6 rounded-2xl ${
                darkMode ? 'bg-gray-800' : 'bg-white'
              } shadow-2xl`}
            >
              <h3 className="text-lg font-semibold mb-4">Upload Document</h3>
              <FileUploader darkMode={darkMode} onClose={() => setShowFileUploader(false)} />
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>

      {/* Input Area */}
      <div className={`border-t ${
        darkMode ? 'border-gray-700/50 bg-gray-900/95' : 'border-gray-200/50 bg-white/95'
      } backdrop-blur-sm p-6`}>
        <div className="max-w-3xl mx-auto">
          <form onSubmit={handleSubmit} className="relative">
            {/* Enhanced Input Container */}
            <div className={`relative overflow-hidden rounded-2xl border-2 transition-all duration-300 ${
              darkMode 
                ? 'bg-gradient-to-br from-gray-800 to-gray-900 border-gray-600 focus-within:border-primary-500 focus-within:from-gray-700 focus-within:to-gray-800' 
                : 'bg-gradient-to-br from-white to-gray-50 border-gray-300 focus-within:border-primary-500 focus-within:from-blue-50 focus-within:to-white'
            } focus-within:ring-4 focus-within:ring-primary-500/20 shadow-xl hover:shadow-2xl focus-within:shadow-2xl`}>
              
              {/* Subtle Inner Glow */}
              <div className={`absolute inset-0 opacity-0 focus-within:opacity-100 transition-opacity duration-300 ${
                darkMode 
                  ? 'bg-gradient-to-br from-primary-900/20 to-purple-900/20' 
                  : 'bg-gradient-to-br from-primary-50/50 to-purple-50/50'
              }`} />
              
              {/* Input Content */}
              <div className="relative flex items-end space-x-4 p-4">
                {/* File Upload Button */}
                <motion.button
                  type="button"
                  whileHover={{ scale: 1.05 }}
                  whileTap={{ scale: 0.95 }}
                  onClick={() => setShowFileUploader(true)}
                  className={`flex-shrink-0 p-3 rounded-xl transition-all duration-200 ${
                    darkMode
                      ? 'hover:bg-gray-700/70 text-gray-400 hover:text-primary-400 hover:shadow-lg'
                      : 'hover:bg-gray-100/70 text-gray-500 hover:text-primary-600 hover:shadow-md'
                  } relative group backdrop-blur-sm`}
                  title="Upload document"
                >
                  <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 
                      d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
                  </svg>
                  
                  {/* Enhanced Tooltip */}
                  <div className={`absolute -top-14 left-1/2 transform -translate-x-1/2 px-3 py-2 rounded-lg text-xs whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-200 ${
                    darkMode ? 'bg-gray-800 text-white shadow-xl border border-gray-700' : 'bg-gray-900 text-white shadow-xl'
                  }`}>
                    Upload documents
                    <div className={`absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent ${
                      darkMode ? 'border-t-gray-800' : 'border-t-gray-900'
                    }`} />
                  </div>
                </motion.button>

                {/* Enhanced Text Input */}
                <div className="flex-1 relative">
                  <textarea
                    ref={textareaRef}
                    value={message}
                    onChange={(e) => setMessage(e.target.value)}
                    onKeyDown={handleKeyDown}
                    placeholder={messages.length === 0 ? "Hi! Ask me about accounting, finance, taxation, or upload your study materials..." : "Ask a follow-up question..."}
                    className={`w-full resize-none border-none outline-none bg-transparent py-3 px-2 text-base leading-relaxed ${
                      darkMode ? 'text-white placeholder-gray-400' : 'text-gray-900 placeholder-gray-500'
                    } placeholder:text-sm placeholder:leading-relaxed`}
                    rows={1}
                    disabled={isLoading}
                    style={{ 
                      minHeight: '24px',
                      maxHeight: '120px',
                      lineHeight: '1.5'
                    }}
                  />
                  
                  {/* Input Focus Indicator */}
                  <div className={`absolute left-0 bottom-0 h-0.5 w-0 bg-gradient-to-r from-primary-500 to-purple-500 transition-all duration-300 ${
                    message.trim() ? 'w-full' : 'group-focus-within:w-full'
                  }`} />
                </div>

                {/* Enhanced Send Button */}
                <motion.button
                  type="submit"
                  disabled={!message.trim() || isLoading}
                  whileHover={message.trim() && !isLoading ? { scale: 1.05 } : {}}
                  whileTap={message.trim() && !isLoading ? { scale: 0.95 } : {}}
                  className={`flex-shrink-0 p-3 rounded-xl transition-all duration-200 relative group ${
                    message.trim() && !isLoading
                      ? 'bg-gradient-to-r from-primary-600 to-primary-700 hover:from-primary-700 hover:to-primary-800 text-white shadow-lg hover:shadow-xl'
                      : darkMode
                        ? 'bg-gray-600/50 text-gray-400 hover:bg-gray-600/70'
                        : 'bg-gray-300/50 text-gray-500 hover:bg-gray-300/70'
                  } disabled:cursor-not-allowed`}
                  title={isLoading ? "Stop generation" : "Send message"}
                >
                  {isLoading ? (
                    <div className="relative">
                      <StopIcon className="w-5 h-5" />
                      <div className="absolute inset-0 border-2 border-white border-t-transparent rounded-full animate-spin opacity-50"></div>
                    </div>
                  ) : (
                    <PaperAirplaneIcon className="w-5 h-5" />
                  )}
                  
                  {/* Enhanced Send Button Glow Effect */}
                  {message.trim() && !isLoading && (
                    <div className="absolute inset-0 rounded-xl bg-gradient-to-r from-primary-600 to-primary-700 opacity-0 group-hover:opacity-30 transition-opacity duration-200 blur-lg -z-10"></div>
                  )}
                </motion.button>
              </div>
              
              {/* Bottom Border Accent */}
              <div className={`absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-primary-500 to-transparent opacity-0 focus-within:opacity-100 transition-opacity duration-300`} />
            </div>
          </form>

          {/* Footer Text */}
          <motion.p 
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            transition={{ delay: 0.3 }}
            className={`text-xs text-center mt-3 ${
              darkMode ? 'text-gray-500' : 'text-gray-400'
            }`}
          >
            ⚑ Powered by AI β€’ CA Study Assistant can make mistakes. Consider checking important information.
          </motion.p>
        </div>
      </div>
    </div>
  );
};

export default ChatInterface;