SarahXia0405's picture
Upload 72 files
760b33c verified
raw
history blame
13.2 kB
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<User | null>(null);
const [messages, setMessages] = useState<Message[]>([
{
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<LearningMode>('concept');
const [language, setLanguage] = useState<Language>('auto');
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
const [memoryProgress, setMemoryProgress] = useState(36);
const [leftSidebarOpen, setLeftSidebarOpen] = useState(false);
const [rightPanelOpen, setRightPanelOpen] = useState(false);
const [rightPanelVisible, setRightPanelVisible] = useState(true);
const [spaceType, setSpaceType] = useState<SpaceType>('individual');
const [exportResult, setExportResult] = useState('');
const [resultType, setResultType] = useState<'export' | 'quiz' | 'summary' | null>(null);
// Mock group members
const [groupMembers] = useState<GroupMember[]>([
{ 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<LearningMode, string> = {
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 (
<div className="min-h-screen bg-background flex flex-col">
<Toaster />
<Header
user={user}
onMenuClick={() => setLeftSidebarOpen(!leftSidebarOpen)}
onUserClick={() => setRightPanelOpen(!rightPanelOpen)}
isDarkMode={isDarkMode}
onToggleDarkMode={() => setIsDarkMode(!isDarkMode)}
/>
<div className="flex-1 flex overflow-hidden">
{/* Mobile Sidebar Toggle - Left */}
{leftSidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
onClick={() => setLeftSidebarOpen(false)}
/>
)}
{/* Left Sidebar */}
<aside
className={`
fixed lg:static inset-y-0 left-0 z-50
w-80 bg-card border-r border-border
transform transition-transform duration-300 ease-in-out
${leftSidebarOpen ? 'translate-x-0' : '-translate-x-full'}
lg:translate-x-0
flex flex-col
mt-16 lg:mt-0
`}
>
<div className="lg:hidden p-4 border-b border-border flex justify-between items-center">
<h3>Settings & Guide</h3>
<Button
variant="ghost"
size="icon"
onClick={() => setLeftSidebarOpen(false)}
>
<X className="h-5 w-5" />
</Button>
</div>
<LeftSidebar
learningMode={learningMode}
language={language}
onLearningModeChange={setLearningMode}
onLanguageChange={setLanguage}
spaceType={spaceType}
onSpaceTypeChange={setSpaceType}
groupMembers={groupMembers}
/>
</aside>
{/* Main Chat Area */}
<main className="flex-1 flex flex-col min-w-0">
<ChatArea
messages={messages}
onSendMessage={handleSendMessage}
uploadedFiles={uploadedFiles}
onFileUpload={handleFileUpload}
onRemoveFile={handleRemoveFile}
onFileTypeChange={handleFileTypeChange}
memoryProgress={memoryProgress}
isLoggedIn={!!user}
learningMode={learningMode}
onClearConversation={handleClearConversation}
onLearningModeChange={setLearningMode}
spaceType={spaceType}
/>
</main>
{/* Mobile Sidebar Toggle - Right */}
{rightPanelOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
onClick={() => setRightPanelOpen(false)}
/>
)}
{/* Right Panel */}
{rightPanelVisible && (
<aside
className={`
fixed lg:static inset-y-0 right-0 z-50
w-80 bg-card border-l border-border
transform transition-transform duration-300 ease-in-out
${rightPanelOpen ? 'translate-x-0' : 'translate-x-full'}
lg:translate-x-0
flex flex-col
mt-16 lg:mt-0
`}
>
<div className="lg:hidden p-4 border-b border-border flex justify-between items-center">
<h3>Account & Actions</h3>
<Button
variant="ghost"
size="icon"
onClick={() => setRightPanelOpen(false)}
>
<X className="h-5 w-5" />
</Button>
</div>
<RightPanel
user={user}
onLogin={setUser}
onLogout={() => setUser(null)}
isLoggedIn={!!user}
onClose={() => setRightPanelVisible(false)}
exportResult={exportResult}
setExportResult={setExportResult}
resultType={resultType}
setResultType={setResultType}
/>
</aside>
)}
{/* Toggle Right Panel Button - Desktop only */}
<Button
variant="outline"
size="icon"
onClick={() => setRightPanelVisible(!rightPanelVisible)}
className={`hidden lg:flex fixed top-20 z-[70] h-8 w-5 shadow-lg transition-all rounded-l-full rounded-r-none border-r-0 ${
rightPanelVisible
? 'right-[320px]'
: 'right-0'
}`}
title={rightPanelVisible ? 'Close panel' : 'Open panel'}
>
{rightPanelVisible ? (
<ChevronRight className="h-3 w-3" />
) : (
<ChevronLeft className="h-3 w-3" />
)}
</Button>
{/* Floating Action Buttons - Desktop only, when panel is closed */}
{!rightPanelVisible && (
<FloatingActionButtons
user={user}
isLoggedIn={!!user}
onOpenPanel={() => setRightPanelVisible(true)}
onExport={handleExport}
onQuiz={handleQuiz}
onSummary={handleSummary}
/>
)}
</div>
</div>
);
}
export default App;