1 line
6.6 KiB
JSON
1 line
6.6 KiB
JSON
{"repo": "briannegarilao/esgenius", "files": {"src/components/AIChatbot.tsx": {"3c9c8cdba4ba911ff2311fd3e697928d4952b78f": {"t": 1785747172.0744653, "n": 6286, "h": "ba5c1cc85eb4c618", "c": "import { useState, useEffect } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport { MessageCircle, X, Send, Loader } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\n\ninterface AIChatbotProps {\n pdfContext: string | null;\n}\n\ninterface Message {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport const AIChatbot = ({ pdfContext }: AIChatbotProps) => {\n const [isOpen, setIsOpen] = useState(false);\n const [messages, setMessages] = useState<Message[]>([]);\n const [inputMessage, setInputMessage] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n\n useEffect(() => {\n if (pdfContext) {\n const systemMessage: Message = {\n role: 'system',\n content: `You are an expert ESG analyst. You are helping a user analyze this ESG report:\\n\\n${pdfContext.slice(0, 12000)}`\n };\n setMessages([systemMessage]);\n } else {\n const defaultSystemMessage: Message = {\n role: 'system',\n content: `You are an expert ESG analyst. Please wait for the document to be loaded.`\n };\n setMessages([defaultSystemMessage]);\n }\n }, [pdfContext]);\n\n const sendMessage = async () => {\n if (!inputMessage.trim() || isLoading) return;\n\n const userMessageContent = inputMessage.trim();\n const currentMessages: Message[] = [...messages, { role: 'user', content: userMessageContent }];\n \n setMessages(currentMessages);\n setInputMessage(\"\");\n setIsLoading(true);\n\n try {\n const deepSeekApiKey = \"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\";\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Authorization\": `Bearer ${deepSeekApiKey}`,\n \"Content-Type\": \"application/json\"\n },\n // We send the relevant messages, not the whole history if it gets too long\n body: JSON.stringify({\n \"model\": \"deepseek/deepseek-r1-0528:free\",\n \"messages\": currentMessages \n })\n });\n\n if (!response.ok) {\n throw new Error(\"Failed to get a response from the AI.\");\n }\n\n const data = await response.json();\n const assistantMessage = data.choices?.[0]?.message?.content || \"Sorry, I couldn't process your request.\";\n \n setMessages(prev => [...prev, { role: 'assistant', content: assistantMessage }]);\n } catch (error) {\n console.error('Error sending message:', error);\n const errorMessage = error instanceof Error ? error.message : \"An unknown error occurred.\";\n setMessages(prev => [...prev, { role: 'assistant', content: `Sorry, there was an error: ${errorMessage}` }]);\n } finally {\n setIsLoading(false);\n }\n };\n\n const handleKeyPress = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n sendMessage();\n }\n };\n\n return (\n <>\n {/* Floating Chat Button */}\n <button\n onClick={() => setIsOpen(!isOpen)}\n className=\"fixed bottom-6 right-6 w-14 h-14 bg-blue-600 hover:bg-blue-700 rounded-full flex items-center justify-center shadow-lg transition-all duration-300 z-50\"\n >\n {isOpen ? <X size={24} className=\"text-white\" /> : <MessageCircle size={24} className=\"text-white\" />}\n </button>\n\n {/* Chat Card */}\n {isOpen && (\n <div className=\"fixed bottom-24 right-6 w-80 h-96 bg-gray-900 border border-gray-700 rounded-lg shadow-2xl z-40 flex flex-col animate-scale-in\">\n {/* Header */}\n <div className=\"border-b border-gray-700 p-4\">\n <h3 className=\"text-white font-semibold\">AI Assistant</h3>\n <p className=\"text-gray-400 text-sm\">Ask questions about your ESG report</p>\n </div>\n\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-3\">\n {!pdfContext && (\n <div className=\"flex items-center justify-center h-full text-gray-400 text-sm\">\n <Loader size={16} className=\"animate-spin mr-2\" />\n Waiting for document...\n </div>\n )}\n {pdfContext && messages.filter(msg => msg.role !== 'system').length === 0 && (\n <div className=\"text-gray-400 text-sm text-center py-8\">\n Document loaded. Start a conversation by asking a question!\n </div>\n )}\n {messages.filter(msg => msg.role !== 'system').map((message, index) => (\n <div\n key={index}\n className={`prose prose-sm prose-invert max-w-none p-3 rounded-lg text-sm ${\n message.role === 'user'\n ? 'bg-blue-600 text-white ml-8'\n : 'bg-gray-800 text-gray-200 mr-8'\n }`}\n >\n <ReactMarkdown>{message.content}</ReactMarkdown>\n </div>\n ))}\n {isLoading && (\n <div className=\"bg-gray-800 text-gray-200 mr-8 p-3 rounded-lg text-sm flex items-center gap-2\">\n <Loader size={16} className=\"animate-spin\" />\n Thinking...\n </div>\n )}\n </div>\n\n {/* Input */}\n <div className=\"border-t border-gray-700 p-4\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={inputMessage}\n onChange={(e) => setInputMessage(e.target.value)}\n onKeyPress={handleKeyPress}\n placeholder=\"Ask about your ESG report...\"\n disabled={isLoading || !pdfContext}\n className=\"flex-1 bg-gray-800 border border-gray-600 rounded-lg px-3 py-2 text-white text-sm placeholder-gray-400 focus:outline-none focus:border-blue-500\"\n />\n <Button\n onClick={sendMessage}\n disabled={!inputMessage.trim() || isLoading || !pdfContext}\n size=\"sm\"\n className=\"bg-blue-600 hover:bg-blue-700 disabled:opacity-50\"\n >\n <Send size={16} />\n </Button>\n </div>\n </div>\n </div>\n )}\n </>\n );\n};\n"}}}} |