From 0b09e72bcf6e1777dfd26f934645e57a2eaa9b27 Mon Sep 17 00:00:00 2001 From: rathi Date: Sun, 8 Dec 2024 12:52:25 -0500 Subject: [PATCH] chat page ui updates 2 --- package-lock.json | 11 ++ package.json | 1 + src/components/chat/Message.tsx | 153 ++++++++++------------- src/pages/dashboard/ask.tsx | 209 +++++++++++++++++++++----------- 4 files changed, 210 insertions(+), 164 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23f04aa..cf4ed8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "@eslint/js": "^9.9.1", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", + "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.18", "eslint": "^9.9.1", @@ -2286,6 +2287,16 @@ "@types/react": "*" } }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", diff --git a/package.json b/package.json index 51c7843..f3e7d45 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@eslint/js": "^9.9.1", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", + "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^4.3.1", "autoprefixer": "^10.4.18", "eslint": "^9.9.1", diff --git a/src/components/chat/Message.tsx b/src/components/chat/Message.tsx index 81e42cb..5fa26f7 100644 --- a/src/components/chat/Message.tsx +++ b/src/components/chat/Message.tsx @@ -2,23 +2,27 @@ import React from 'react'; import { formatDistanceToNow } from 'date-fns'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; -import { Copy, Check, ThumbsUp, ThumbsDown } from 'lucide-react'; +import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { vs } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { Copy, Check } from 'lucide-react'; import { cn } from '../../lib/utils'; import { Button } from '../ui/Button'; import { Avatar, AvatarFallback } from '../ui/Avatar'; import { Tooltip } from '../ui/Tooltip'; import type { ChatMessage } from '../../types/supabase'; +import type { Components } from 'react-markdown'; interface MessageProps { message: ChatMessage; - onFeedback?: (messageId: string, isPositive: boolean) => void; } -export function Message({ message, onFeedback }: MessageProps) { +interface CodeBlockProps { + language: string; + value: string; +} + +export function Message({ message }: MessageProps) { const [copied, setCopied] = React.useState(false); - const [feedback, setFeedback] = React.useState<'positive' | 'negative' | null>(null); const handleCopy = async (text: string) => { await navigator.clipboard.writeText(text); @@ -26,10 +30,49 @@ export function Message({ message, onFeedback }: MessageProps) { setTimeout(() => setCopied(false), 2000); }; - const handleFeedback = (isPositive: boolean) => { - if (!onFeedback) return; - setFeedback(isPositive ? 'positive' : 'negative'); - onFeedback(message.id, isPositive); + const CodeBlock = React.memo(({ language, value }: CodeBlockProps) => ( +
+
+ + + +
+ + {value.replace(/\n$/, '')} + +
+ )); + + const code: Components['code'] = ({ className, children, ...props }) => { + const match = /language-(\w+)/.exec(className || ''); + const language = match ? match[1] : ''; + const value = String(children).replace(/\n$/, ''); + + if (!className) { + return ( + + {children} + + ); + } + + return ; }; return ( @@ -40,19 +83,19 @@ export function Message({ message, onFeedback }: MessageProps) { )} > {message.role === 'assistant' && ( - - + + AI )} - +
@@ -63,94 +106,20 @@ export function Message({ message, onFeedback }: MessageProps) { message.role === 'user' ? 'prose-invert' : 'prose-stone dark:prose-invert' )} components={{ - code({ node, inline, className, children, ...props }) { - const match = /language-(\w+)/.exec(className || ''); - const language = match ? match[1] : ''; - - if (inline) { - return ( - - {children} - - ); - } - - return ( -
-
- - - -
- - {String(children).replace(/\n$/, '')} - -
- ); - }, + code }} > {message.content}
- +
{formatDistanceToNow(new Date(message.created_at), { addSuffix: true })} - - {message.role === 'assistant' && onFeedback && ( -
- - - - - - -
- )}
{message.role === 'user' && ( - + You diff --git a/src/pages/dashboard/ask.tsx b/src/pages/dashboard/ask.tsx index 8e95e9f..072d954 100644 --- a/src/pages/dashboard/ask.tsx +++ b/src/pages/dashboard/ask.tsx @@ -1,8 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Send, Loader2, X, History, MessageSquarePlus, AlertCircle } from 'lucide-react'; import { Link, useParams, useNavigate } from 'react-router-dom'; import { Button } from '../../components/ui/Button'; -import { ScrollArea } from '../../components/ui/ScrollArea'; import { Message } from '../../components/chat/Message'; import { Avatar, AvatarFallback } from '../../components/ui/Avatar'; import { cn } from '../../lib/utils'; @@ -22,6 +21,8 @@ export default function AskQuestion() { const [isNewChat, setIsNewChat] = useState(false); const [hasExistingChats, setHasExistingChats] = useState(null); const [isTyping, setIsTyping] = useState(false); + const messagesEndRef = useRef(null); + const scrollAreaRef = useRef(null); // Check for existing chats and redirect to most recent if on base path useEffect(() => { @@ -163,6 +164,14 @@ export default function AskQuestion() { } }; + // Auto-scroll when new messages arrive + useEffect(() => { + if (scrollAreaRef.current) { + const scrollArea = scrollAreaRef.current; + scrollArea.scrollTop = scrollArea.scrollHeight; + } + }, [messages, isTyping]); + // Show loading state while checking for existing chats if (hasExistingChats === null) { return ( @@ -173,8 +182,9 @@ export default function AskQuestion() { } return ( -
-
+
+ {/* Header */} +
@@ -210,122 +220,177 @@ export default function AskQuestion() {
+ {/* Error Message */} {error && ( -
+
{error}
)} -
- + {/* Messages Area */} +
+
{!isNewChat && !chatId ? ( -
-
-
- -
-
-

Welcome to StudyAI Chat

-

- Start a new conversation to get answers to your questions +

+
+
+
+
+
+

Welcome to StudyAI Chat

+

+ Start a new chat to begin asking questions about your study materials

+ +
+

Example questions you can ask:

+
+ + + +
+
-
) : messages.length === 0 ? ( -
-
-
- -
-
-

No messages yet

-

- Start by asking a question about your studies +

+
+
+
+
+
+

Ask Your First Question

+

+ Type your question below or choose from the examples

+
+ + + +
) : ( <> - {messages.map((message, index) => ( + {messages.map((message) => ( { - // Implement feedback handling here - console.log('Feedback:', messageId, isPositive); - }} /> ))} - {/* Typing indicator */} {isTyping && (
- - + + AI -
+
- AI is thinking...
)} +
)}
- +
- {(isNewChat || chatId) && ( -
-
-
-