diff --git a/web/README.md b/web/README.md new file mode 100644 index 0000000000000000000000000000000000000000..01b1cd2d6adbf7aea13c1c69e7ab1208fc2b15ad --- /dev/null +++ b/web/README.md @@ -0,0 +1,11 @@ + + # Clare AI Tutor UI Redesign (Copy) + + This is a code bundle for Clare AI Tutor UI Redesign (Copy). The original project is available at https://www.figma.com/design/yC1iBsNtBGpBH8sXO43uah/Clare-AI-Tutor-UI-Redesign--Copy-. + + ## Running the code + + Run `npm i` to install the dependencies. + + Run `npm run dev` to start the development server. + \ No newline at end of file diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000000000000000000000000000000000000..603ccaf3717df1d5a4b76ec20d1d9709c23124cf --- /dev/null +++ b/web/index.html @@ -0,0 +1,15 @@ + + + + + + + Clare AI Tutor UI Redesign (Copy) + + + +
+ + + + \ No newline at end of file diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000000000000000000000000000000000000..a759f777c5b701895d46305523224b0b3e3110cf --- /dev/null +++ b/web/package.json @@ -0,0 +1,59 @@ + + { + "name": "Clare AI Tutor UI Redesign (Copy)", + "version": "0.1.0", + "private": true, + "dependencies": { + "@radix-ui/react-accordion": "^1.2.3", + "@radix-ui/react-alert-dialog": "^1.1.6", + "@radix-ui/react-aspect-ratio": "^1.1.2", + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-context-menu": "^2.2.6", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-hover-card": "^1.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-menubar": "^1.1.6", + "@radix-ui/react-navigation-menu": "^1.2.5", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-progress": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.3", + "@radix-ui/react-scroll-area": "^1.2.3", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slider": "^1.2.3", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-toggle": "^1.1.2", + "@radix-ui/react-toggle-group": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", + "class-variance-authority": "^0.7.1", + "clsx": "*", + "cmdk": "^1.1.1", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.487.0", + "next-themes": "^0.4.6", + "react": "^18.3.1", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.55.0", + "react-resizable-panels": "^2.1.7", + "recharts": "^2.15.2", + "sonner": "^2.0.3", + "tailwind-merge": "*", + "vaul": "^1.1.2" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@vitejs/plugin-react-swc": "^3.10.2", + "vite": "6.3.5" + }, + "scripts": { + "dev": "vite", + "build": "vite build" + } + } \ No newline at end of file diff --git a/web/src/.DS_Store b/web/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..70cfb8d2d8d3d43ba7286d6a132d6d051f1a2214 Binary files /dev/null and b/web/src/.DS_Store differ diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d961f4ee3ca367995065a4e96f424af2fa95f71c --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,377 @@ +import React, { useState, useEffect } from 'react'; +import { Header } from './components/Header'; +import { LeftSidebar } from './components/LeftSidebar'; +import { ChatArea } from './components/ChatArea'; +import { RightPanel } from './components/RightPanel'; +import { FloatingActionButtons } from './components/FloatingActionButtons'; +import { Menu, X, User } from 'lucide-react'; +import { Button } from './components/ui/button'; +import { Toaster } from './components/ui/sonner'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { toast } from 'sonner'; + +export interface Message { + id: string; + role: 'user' | 'assistant'; + content: string; + timestamp: Date; + references?: string[]; + sender?: GroupMember; // For group chat +} + +export interface User { + name: string; + email: string; +} + +export interface GroupMember { + id: string; + name: string; + email: string; + avatar?: string; + isAI?: boolean; +} + +export type SpaceType = 'individual' | 'group'; + +export type FileType = 'syllabus' | 'lecture-slides' | 'literature-review' | 'other'; + +export interface UploadedFile { + file: File; + type: FileType; +} + +export type LearningMode = 'concept' | 'socratic' | 'exam' | 'assignment' | 'summary'; +export type Language = 'auto' | 'en' | 'zh'; + +function App() { + const [isDarkMode, setIsDarkMode] = useState(() => { + const saved = localStorage.getItem('theme'); + return saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches); + }); + const [user, setUser] = useState(null); + const [messages, setMessages] = useState([ + { + id: '1', + role: 'assistant', + content: "👋 Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. I'm here to help you learn through personalized tutoring. Feel free to ask me anything about the course materials, or upload your documents to get started!", + timestamp: new Date(), + } + ]); + const [learningMode, setLearningMode] = useState('concept'); + const [language, setLanguage] = useState('auto'); + const [uploadedFiles, setUploadedFiles] = useState([]); + const [memoryProgress, setMemoryProgress] = useState(36); + const [leftSidebarOpen, setLeftSidebarOpen] = useState(false); + const [rightPanelOpen, setRightPanelOpen] = useState(false); + const [rightPanelVisible, setRightPanelVisible] = useState(true); + const [spaceType, setSpaceType] = useState('individual'); + const [exportResult, setExportResult] = useState(''); + const [resultType, setResultType] = useState<'export' | 'quiz' | 'summary' | null>(null); + + // Mock group members + const [groupMembers] = useState([ + { id: 'clare', name: 'Clare AI', email: 'clare@ai.assistant', isAI: true }, + { id: '1', name: 'Sarah Johnson', email: 'sarah.j@university.edu' }, + { id: '2', name: 'Michael Chen', email: 'michael.c@university.edu' }, + { id: '3', name: 'Emma Williams', email: 'emma.w@university.edu' }, + ]); + + useEffect(() => { + document.documentElement.classList.toggle('dark', isDarkMode); + localStorage.setItem('theme', isDarkMode ? 'dark' : 'light'); + }, [isDarkMode]); + + const handleSendMessage = (content: string) => { + if (!content.trim() || !user) return; + + // In group mode, add sender info + const sender: GroupMember | undefined = spaceType === 'group' + ? { id: user.email, name: user.name, email: user.email } + : undefined; + + const userMessage: Message = { + id: Date.now().toString(), + role: 'user', + content, + timestamp: new Date(), + sender, + }; + + setMessages(prev => [...prev, userMessage]); + + // In group mode, only respond if @Clare or @clare is mentioned + const shouldAIRespond = spaceType === 'individual' || + content.toLowerCase().includes('@clare'); + + if (shouldAIRespond) { + // Simulate AI response + setTimeout(() => { + const responses: Record = { + concept: "Great question! Let me break this concept down for you. In Responsible AI, this relates to ensuring our AI systems are fair, transparent, and accountable. Would you like me to explain any specific aspect in more detail?", + socratic: "That's an interesting point! Let me ask you this: What do you think are the key ethical considerations when deploying AI systems? Take a moment to think about it.", + exam: "Let me test your understanding with a quick question: Which of the following is NOT a principle of Responsible AI? A) Fairness B) Transparency C) Profit Maximization D) Accountability", + assignment: "I can help you with that assignment! Let's break it down into manageable steps. First, what specific aspect are you working on?", + summary: "Here's a quick summary: Responsible AI focuses on developing and deploying AI systems that are ethical, fair, transparent, and accountable to society.", + }; + + const assistantMessage: Message = { + id: (Date.now() + 1).toString(), + role: 'assistant', + content: responses[learningMode], + timestamp: new Date(), + references: ['Module 10, Section 2.3', 'Lecture Notes - Week 5'], + sender: spaceType === 'group' ? groupMembers.find(m => m.isAI) : undefined, + }; + + setMessages(prev => [...prev, assistantMessage]); + }, 1000); + } + }; + + const handleFileUpload = (files: File[]) => { + const newFiles: UploadedFile[] = files.map(file => ({ + file, + type: 'other' as FileType, // Default type + })); + setUploadedFiles(prev => [...prev, ...newFiles]); + }; + + const handleRemoveFile = (index: number) => { + setUploadedFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const handleFileTypeChange = (index: number, type: FileType) => { + setUploadedFiles(prev => prev.map((file, i) => + i === index ? { ...file, type } : file + )); + }; + + const handleClearConversation = () => { + setMessages([{ + id: '1', + role: 'assistant', + content: "👋 Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. I'm here to help you learn through personalized tutoring. Feel free to ask me anything about the course materials, or upload your documents to get started!", + timestamp: new Date(), + }]); + }; + + const handleExport = () => { + const result = `# Conversation Export +Date: ${new Date().toLocaleDateString()} +Student: ${user?.name} + +## Summary +This conversation covered key concepts in Module 10 – Responsible AI, including ethical considerations, fairness, transparency, and accountability in AI systems. + +## Key Takeaways +1. Understanding the principles of Responsible AI +2. Real-world applications and implications +3. Best practices for ethical AI development + +Exported successfully! ✓`; + + setExportResult(result); + setResultType('export'); + toast.success('Conversation exported!'); + }; + + const handleQuiz = () => { + const quiz = `# Micro-Quiz: Responsible AI + +**Question 1:** Which of the following is a key principle of Responsible AI? +a) Profit maximization +b) Transparency +c) Rapid deployment +d) Cost reduction + +**Question 2:** What is algorithmic fairness? +(Short answer expected) + +**Question 3:** True or False: AI systems should always prioritize accuracy over fairness. + +Generate quiz based on your conversation!`; + + setExportResult(quiz); + setResultType('quiz'); + toast.success('Quiz generated!'); + }; + + const handleSummary = () => { + const summary = `# Learning Summary + +## Today's Session +**Duration:** 25 minutes +**Topics Covered:** 3 +**Messages Exchanged:** 12 + +## Key Concepts Discussed +â€ĸ Principles of Responsible AI +â€ĸ Ethical considerations in AI development +â€ĸ Fairness and transparency in algorithms + +## Recommended Next Steps +1. Review Module 10, Section 2.3 +2. Complete practice quiz on algorithmic fairness +3. Read additional resources on AI ethics + +## Progress Update +You've covered 65% of Module 10 content. Keep up the great work! 🎉`; + + setExportResult(summary); + setResultType('summary'); + toast.success('Summary generated!'); + }; + + return ( +
+ +
setLeftSidebarOpen(!leftSidebarOpen)} + onUserClick={() => setRightPanelOpen(!rightPanelOpen)} + isDarkMode={isDarkMode} + onToggleDarkMode={() => setIsDarkMode(!isDarkMode)} + /> + +
+ {/* Mobile Sidebar Toggle - Left */} + {leftSidebarOpen && ( +
setLeftSidebarOpen(false)} + /> + )} + + {/* Left Sidebar */} + + + {/* Main Chat Area */} +
+ +
+ + {/* Mobile Sidebar Toggle - Right */} + {rightPanelOpen && ( +
setRightPanelOpen(false)} + /> + )} + + {/* Right Panel */} + {rightPanelVisible && ( + + )} + + {/* Toggle Right Panel Button - Desktop only */} + + + {/* Floating Action Buttons - Desktop only, when panel is closed */} + {!rightPanelVisible && ( + setRightPanelVisible(true)} + onExport={handleExport} + onQuiz={handleQuiz} + onSummary={handleSummary} + /> + )} +
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/web/src/Attributions.md b/web/src/Attributions.md new file mode 100644 index 0000000000000000000000000000000000000000..9b7cd4e13487db2e20f4f3844255f0b967db8448 --- /dev/null +++ b/web/src/Attributions.md @@ -0,0 +1,3 @@ +This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md). + +This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license). \ No newline at end of file diff --git a/web/src/components/.DS_Store b/web/src/components/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ead76fb4506471282295b2a388d5e2dd48b760a1 Binary files /dev/null and b/web/src/components/.DS_Store differ diff --git a/web/src/components/ChatArea.tsx b/web/src/components/ChatArea.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b086d73c4d2e56dbc603f1c1f8b1b35442be0613 --- /dev/null +++ b/web/src/components/ChatArea.tsx @@ -0,0 +1,343 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Button } from './ui/button'; +import { Textarea } from './ui/textarea'; +import { Send, ArrowDown, AlertCircle, Trash2, Share2 } from 'lucide-react'; +import { Message } from './Message'; +import { FileUploadArea } from './FileUploadArea'; +import { MemoryLine } from './MemoryLine'; +import { Alert, AlertDescription } from './ui/alert'; +import { Badge } from './ui/badge'; +import type { Message as MessageType, LearningMode, UploadedFile, FileType, SpaceType } from '../App'; +import { toast } from 'sonner'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from './ui/dropdown-menu'; + +interface ChatAreaProps { + messages: MessageType[]; + onSendMessage: (content: string) => void; + uploadedFiles: UploadedFile[]; + onFileUpload: (files: File[]) => void; + onRemoveFile: (index: number) => void; + onFileTypeChange: (index: number, type: FileType) => void; + memoryProgress: number; + isLoggedIn: boolean; + learningMode: LearningMode; + onClearConversation: () => void; + onLearningModeChange: (mode: LearningMode) => void; + spaceType: SpaceType; +} + +export function ChatArea({ + messages, + onSendMessage, + uploadedFiles, + onFileUpload, + onRemoveFile, + onFileTypeChange, + memoryProgress, + isLoggedIn, + learningMode, + onClearConversation, + onLearningModeChange, + spaceType, +}: ChatAreaProps) { + const [input, setInput] = useState(''); + const [isTyping, setIsTyping] = useState(false); + const [showScrollButton, setShowScrollButton] = useState(false); + const messagesEndRef = useRef(null); + const scrollContainerRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + useEffect(() => { + const handleScroll = () => { + if (scrollContainerRef.current) { + const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current; + setShowScrollButton(scrollHeight - scrollTop - clientHeight > 100); + } + }; + + const container = scrollContainerRef.current; + container?.addEventListener('scroll', handleScroll); + return () => container?.removeEventListener('scroll', handleScroll); + }, []); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim() || !isLoggedIn) return; + + onSendMessage(input); + setInput(''); + setIsTyping(true); + setTimeout(() => setIsTyping(false), 1500); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSubmit(e); + } + }; + + const modeLabels: Record = { + concept: 'Concept Explainer', + socratic: 'Socratic Tutor', + exam: 'Exam Prep', + assignment: 'Assignment Helper', + summary: 'Quick Summary', + }; + + const handleClearClick = () => { + if (messages.length <= 1) { + toast.info('No conversation to clear'); + return; + } + + if (window.confirm('Are you sure you want to clear the conversation? This cannot be undone.')) { + onClearConversation(); + toast.success('Conversation cleared'); + } + }; + + const handleShareClick = () => { + if (messages.length <= 1) { + toast.info('No conversation to share'); + return; + } + + // Create a shareable text version of the conversation + const conversationText = messages + .map(msg => `${msg.sender === 'user' ? 'You' : 'Clare'}: ${msg.content}`) + .join('\n\n'); + + // Copy to clipboard + navigator.clipboard.writeText(conversationText).then(() => { + toast.success('Conversation copied to clipboard!'); + }).catch(() => { + toast.error('Failed to copy conversation'); + }); + }; + + return ( +
+ {/* Chat Area with Floating Input */} +
+ {/* Action Buttons - Fixed at top right */} + {messages.length > 1 && ( +
+ + +
+ )} + + {/* Messages Area */} +
+
+ {messages.map((message) => ( + + ))} + + {isTyping && ( +
+
+ C +
+
+
+
+
+
+
+
+
+ )} + +
+
+
+ + {/* Scroll to Bottom Button - Floating above input */} + {showScrollButton && ( +
+ +
+ )} + + {/* Floating Input Area */} +
+
+
+
+ {/* Mode Selector - ChatGPT style at bottom left */} + + + + + + onLearningModeChange('concept')} + className={learningMode === 'concept' ? 'bg-accent' : ''} + > +
+ Concept Explainer + + Get detailed explanations of concepts + +
+
+ onLearningModeChange('socratic')} + className={learningMode === 'socratic' ? 'bg-accent' : ''} + > +
+ Socratic Tutor + + Learn through guided questions + +
+
+ onLearningModeChange('exam')} + className={learningMode === 'exam' ? 'bg-accent' : ''} + > +
+ Exam Prep + + Practice with quiz questions + +
+
+ onLearningModeChange('assignment')} + className={learningMode === 'assignment' ? 'bg-accent' : ''} + > +
+ Assignment Helper + + Get help with assignments + +
+
+ onLearningModeChange('summary')} + className={learningMode === 'summary' ? 'bg-accent' : ''} + > +
+ Quick Summary + + Get concise summaries + +
+
+
+
+ +