File size: 6,318 Bytes
6a50e97
 
 
 
 
8eb16a3
6a50e97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8eb16a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6a50e97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import * as React from 'react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'
import { Send, Square, User, Bot } from 'lucide-react'
import ReactMarkdown from 'react-markdown'

export interface ChatProps extends React.HTMLAttributes<HTMLDivElement> {
  messages: Array<{
    id: string
    role: 'user' | 'assistant' | 'system'
    content: string
    createdAt?: Date
  }>
  input: string
  handleInputChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
  handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void
  isGenerating?: boolean
  stop?: () => void
}

const Chat = React.forwardRef<HTMLDivElement, ChatProps>(
  ({ className, messages, input, handleInputChange, handleSubmit, isGenerating, stop, ...props }, ref) => {
    const messagesEndRef = React.useRef<HTMLDivElement>(null)

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

    React.useEffect(() => {
      console.log('Chat component - messages updated:', messages.length, messages.map(m => ({ id: m.id, role: m.role, content: m.content.slice(0, 50) + '...' })))
      scrollToBottom()
    }, [messages])

    return (
      <div
        className={cn('flex h-full flex-col', className)}
        ref={ref}
        {...props}
      >
        {/* Messages */}
        <div className="flex-1 overflow-y-auto p-4 space-y-4">
          {messages.length === 0 ? (
            <div className="flex items-center justify-center h-full text-muted-foreground">
              <p>No messages yet. Start a conversation!</p>
            </div>
          ) : (
            messages.map((message, index) => (
              <div
                key={`${message.id}-${index}`}
                className={cn(
                  'flex gap-3 w-full',
                  message.role === 'user' ? 'justify-end' : 'justify-start'
                )}
              >
                {/* Avatar for assistant */}
                {message.role !== 'user' && (
                  <div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
                    <Bot className="h-4 w-4 text-primary-foreground" />
                  </div>
                )}
                
                {/* Message content */}
                <div
                  className={cn(
                    'max-w-[75%] flex flex-col gap-2 rounded-lg px-3 py-2 text-sm',
                    message.role === 'user'
                      ? 'bg-primary text-primary-foreground'
                      : 'bg-muted'
                  )}
                >
                  <div className="text-xs opacity-70">
                    {message.role === 'user' ? 'You' : 'Assistant'} • #{index + 1}
                  </div>
                  <div className="leading-relaxed prose prose-sm dark:prose-invert max-w-none">
                    <ReactMarkdown
                      components={{
                        // Customize components for better styling
                        p: ({ children }) => <p className="mb-2 last:mb-0">{children}</p>,
                        ul: ({ children }) => <ul className="mb-2 last:mb-0 list-disc pl-4">{children}</ul>,
                        ol: ({ children }) => <ol className="mb-2 last:mb-0 list-decimal pl-4">{children}</ol>,
                        li: ({ children }) => <li className="mb-1">{children}</li>,
                        code: ({ children, className }) => {
                          const isInline = !className;
                          return isInline ? (
                            <code className="bg-muted px-1 py-0.5 rounded text-xs font-mono">{children}</code>
                          ) : (
                            <code className="block bg-muted p-2 rounded text-xs font-mono overflow-x-auto whitespace-pre">{children}</code>
                          )
                        },
                        pre: ({ children }) => <div className="mb-2 last:mb-0">{children}</div>,
                        strong: ({ children }) => <strong className="font-semibold">{children}</strong>,
                        em: ({ children }) => <em className="italic">{children}</em>,
                        blockquote: ({ children }) => <blockquote className="border-l-4 border-muted pl-4 italic mb-2 last:mb-0">{children}</blockquote>,
                        h1: ({ children }) => <h1 className="text-lg font-bold mb-2 last:mb-0">{children}</h1>,
                        h2: ({ children }) => <h2 className="text-base font-bold mb-2 last:mb-0">{children}</h2>,
                        h3: ({ children }) => <h3 className="text-sm font-bold mb-2 last:mb-0">{children}</h3>,
                      }}
                    >
                      {message.content}
                    </ReactMarkdown>
                  </div>
                </div>
                
                {/* Avatar for user */}
                {message.role === 'user' && (
                  <div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center flex-shrink-0">
                    <User className="h-4 w-4" />
                  </div>
                )}
              </div>
            ))
          )}
          <div ref={messagesEndRef} />
        </div>

        {/* Input */}
        <div className="border-t p-4">
          <form onSubmit={handleSubmit} className="flex gap-2">
            <Textarea
              value={input}
              onChange={handleInputChange}
              placeholder="Type your message..."
              className="min-h-[60px] resize-none"
              onKeyDown={(e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                  e.preventDefault()
                  handleSubmit(e as any)
                }
              }}
            />
            {isGenerating ? (
              <Button type="button" onClick={stop} variant="outline" size="icon">
                <Square className="h-4 w-4" />
              </Button>
            ) : (
              <Button type="submit" disabled={!input.trim()} size="icon">
                <Send className="h-4 w-4" />
              </Button>
            )}
          </form>
        </div>
      </div>
    )
  }
)
Chat.displayName = 'Chat'

export { Chat }