diff --git a/src/app/eme/chat-bubble.tsx b/src/app/eme/chat-bubble.tsx index 608f1b7..49ada17 100644 --- a/src/app/eme/chat-bubble.tsx +++ b/src/app/eme/chat-bubble.tsx @@ -1,24 +1,23 @@ -import { Message } from "@/constants/eme" -export function ChatBubble({ msg, index }: {msg: Message, index: number}) { - return ( -
-
-
- {msg.text} -
-
-
- ) -} \ No newline at end of file +import { Message } from '@/constants/eme'; + +export function ChatBubble({ msg, index }: { msg: Message; index: number }) { + return ( +
+
+
{msg.text}
+
+
+ ); +} diff --git a/src/app/eme/eme-chat.tsx b/src/app/eme/eme-chat.tsx index aca366a..668406a 100644 --- a/src/app/eme/eme-chat.tsx +++ b/src/app/eme/eme-chat.tsx @@ -1,164 +1,175 @@ -"use client" -import { useState } from "react"; -import { Message, thinkingMessage } from "@/constants/eme"; -import { fetchEmeResponse, fetchEmeHealth } from "./fetch-eme"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { Button } from "@/components/ui/button"; -import { ChatBubble } from "./chat-bubble"; +'use client'; + +import { useState } from 'react'; + +import { Button } from '@/components/ui/button'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { Message, thinkingMessage } from '@/constants/eme'; + +import { ChatBubble } from './chat-bubble'; +import { fetchEmeHealth, fetchEmeResponse } from './fetch-eme'; export default function EmeChat() { - const [messages, setMessages] = useState([]) - const [prompt, setPrompt] = useState("") - const [isLoading, setIsLoading] = useState(false) + const [messages, setMessages] = useState([]); + const [prompt, setPrompt] = useState(''); + const [isLoading, setIsLoading] = useState(false); const isHealthCheck = false; const submitPrompt = async (e: React.FormEvent) => { e.preventDefault(); - if(isHealthCheck) { + if (isHealthCheck) { healthCheck(); return; } - if (isLoading || prompt.trim() === "") return; + if (isLoading || prompt.trim() === '') return; const userMsg: Message = { id: Date.now().toString(), text: prompt, - sender: "user", + sender: 'user', timestamp: new Date(), }; - setMessages(prev => [...prev, userMsg]); + setMessages((prev) => [...prev, userMsg]); setIsLoading(true); - setPrompt(""); + setPrompt(''); try { const stream = await fetchEmeResponse(prompt); const reader = stream.getReader(); const decoder = new TextDecoder(); - + let botResponse = ''; const botMsg: Message = { id: Date.now().toString(), text: '', - sender: "bot", + sender: 'bot', timestamp: new Date(), }; - - setMessages(prev => [...prev, botMsg]); + + setMessages((prev) => [...prev, botMsg]); setIsLoading(false); - + while (true) { - const { done, value} = await reader.read(); + const { done, value } = await reader.read(); if (done) break; - - const chunk = decoder.decode(value, { stream: true}); + + const chunk = decoder.decode(value, { stream: true }); botResponse += chunk; // Update message with accumulated text setMessages((prev) => - prev.map((msg) => (msg.id === botMsg.id ? { ...msg, text: botResponse } : msg)), + prev.map((msg) => + msg.id === botMsg.id ? { ...msg, text: botResponse } : msg + ) ); } } catch (error) { - console.error("Error retrieving eme output:", error); + console.error('Error retrieving eme output:', error); const errorMsg: Message = { id: (Date.now() + 1).toString(), text: 'Sorry, I encountered an error. Please try again.', sender: 'bot', timestamp: new Date(), - } + }; setMessages((prev) => [...prev, errorMsg]); } finally { setIsLoading(false); } - } + }; const healthCheck = async () => { try { const response = await fetchEmeHealth(); - console.log("Health is: ", response); - } catch(error) { - console.error("Error retrieving eme health: ", error); + console.log('Health is: ', response); + } catch (error) { + console.error('Error retrieving eme health: ', error); } - } + }; return ( -
-

- eme +
+

+ eme

- + - -
-

- eme is the EMCO chatbot that answers your questions about navigating CS at Northwestern. We hope it can be another point of reference for underclass students getting started. + +

+

+ eme is the EMCO chatbot that answers your questions about + navigating CS at Northwestern. We hope it can be another point of + reference for underclass students getting started.

-

- Answers are based on historical EMCO GroupMe messages. eme uses {" "} - +

+ Answers are based on historical EMCO GroupMe messages. eme uses{' '} + RAG - - {" "} to pull relevant messages into its context. + {' '} + to pull relevant messages into its context.

-

- NOTE: eme can make mistakes. Emerging Coders, its affiliated members and Executive Board do not endorse its messages. +

+ NOTE: eme can make mistakes. Emerging Coders, its affiliated + members and Executive Board do not endorse its messages.

-
-
-
- {messages.length === 0 && -

- Ask me something like "should I take CS214 and CS211 at the same time?" +

+
+
+ {messages.length === 0 && ( +

+ Ask me something like "should I take CS214 and CS211 at the + same time?"

- } + )}
{messages.map((msg, i) => ( ))} - {isLoading && - - } + {isLoading && ( + + )}
-
-
- + + setPrompt(e.target.value)} - className="w-full bg-black border-zinc-800 text-white focus-visible:ring-purple-500/30 px-5 py-2 rounded focus:outline-none" + className='w-full bg-black border-zinc-800 text-white focus-visible:ring-purple-500/30 px-5 py-2 rounded focus:outline-none' disabled={isLoading} - /> -
- ) -} \ No newline at end of file + ); +} diff --git a/src/app/eme/fetch-eme.tsx b/src/app/eme/fetch-eme.tsx index c2db9dd..08eac7d 100644 --- a/src/app/eme/fetch-eme.tsx +++ b/src/app/eme/fetch-eme.tsx @@ -1,20 +1,22 @@ -import { EME_API_BASE_URL } from "@/constants/eme"; +import { EME_API_BASE_URL } from '@/constants/eme'; export interface UseEmeResult { - generation: string, - isLoading: boolean, - isError: boolean, + generation: string; + isLoading: boolean; + isError: boolean; } -export async function fetchEmeResponse(message: string): Promise> { - +export async function fetchEmeResponse( + message: string +): Promise> { const response = await fetch(`${EME_API_BASE_URL}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }), }); - if (!response.ok) throw new Error(`eme responded with status ${response.status}`); + if (!response.ok) + throw new Error(`eme responded with status ${response.status}`); if (!response.body) throw new Error(`No response body`); @@ -25,10 +27,10 @@ export async function fetchEmeResponse(message: string): Promise { +export async function fetchEmeHealth(): Promise<{ status: string }> { const response = await fetch(`${EME_API_BASE_URL}/health`); - + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return response.json(); -} \ No newline at end of file +} diff --git a/src/app/eme/page.tsx b/src/app/eme/page.tsx index 7314fc3..1eba0b6 100644 --- a/src/app/eme/page.tsx +++ b/src/app/eme/page.tsx @@ -1,8 +1,7 @@ -"use client" -import EmeChat from "./eme-chat" +'use client'; + +import EmeChat from './eme-chat'; export default function Page() { - return ( - - ) -} \ No newline at end of file + return ; +} diff --git a/src/components/list-serv-signup.tsx b/src/components/list-serv-signup.tsx index 6c7dad9..074f71f 100644 --- a/src/components/list-serv-signup.tsx +++ b/src/components/list-serv-signup.tsx @@ -1,21 +1,22 @@ -"use client"; +'use client'; -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; +import { useState } from 'react'; + +import { Button } from '@/components/ui/button'; import { Dialog, + DialogClose, DialogContent, DialogDescription, + DialogFooter, DialogHeader, DialogTitle, - DialogFooter, - DialogClose, -} from "@/components/ui/dialog"; +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; export default function ListservSignup() { - const [firstName, setFirstName] = useState(""); - const [lastName, setLastName] = useState(""); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); const [showDialog, setShowDialog] = useState(false); const handleSubmit = (e: React.FormEvent) => { @@ -27,29 +28,29 @@ export default function ListservSignup() { const mailtoLink = `mailto:listserv@listserv.it.northwestern.edu?subject=&body=SUBSCRIBE emergingcoders ${firstName} ${lastName}`; window.location.href = mailtoLink; setShowDialog(false); - setFirstName(""); - setLastName(""); + setFirstName(''); + setLastName(''); }; return ( -
-
-
+
+ +
setFirstName(e.target.value)} required - className="col-span-1" + className='col-span-1' /> setLastName(e.target.value)} required - className="col-span-1" + className='col-span-1' />
+ diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index 01e468b..3e79ecf 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -1,48 +1,48 @@ -"use client" +'use client'; -import * as React from "react" -import * as PopoverPrimitive from "@radix-ui/react-popover" +import * as React from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; +import * as PopoverPrimitive from '@radix-ui/react-popover'; function Popover({ ...props }: React.ComponentProps) { - return + return ; } function PopoverTrigger({ ...props }: React.ComponentProps) { - return + return ; } function PopoverContent({ className, - align = "center", + align = 'center', sideOffset = 4, ...props }: React.ComponentProps) { return ( - ) + ); } function PopoverAnchor({ ...props }: React.ComponentProps) { - return + return ; } -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; diff --git a/src/constants/about-us-image-gallery.ts b/src/constants/about-us-image-gallery.ts index 69bd537..2bb69f6 100644 --- a/src/constants/about-us-image-gallery.ts +++ b/src/constants/about-us-image-gallery.ts @@ -50,8 +50,8 @@ export const images: ImageProps[] = [ }, { id: 7, - src: "/about-us-photos/photo-8.JPEG", - alt: "Emerging Coders x Duolingo", + src: '/about-us-photos/photo-8.JPEG', + alt: 'Emerging Coders x Duolingo', order: 7, aspectRatio: 'portrait', description: 'Winter Quarter 2025', diff --git a/src/constants/eme.ts b/src/constants/eme.ts index 0c4117f..6c0bc46 100644 --- a/src/constants/eme.ts +++ b/src/constants/eme.ts @@ -1,4 +1,4 @@ -export const EME_API_BASE_URL = 'https://eme-yr7i.onrender.com' +export const EME_API_BASE_URL = 'https://eme-yr7i.onrender.com'; export interface Message { id: string; @@ -8,22 +8,22 @@ export interface Message { } export const botDummyMessage: Message = { - id: Date.now().toString(), - text: "CS336 is generally considered to be a challenging course. Students have mentioned that it involves a mix of coding and proofs, which can make it harder compared to other classes. However, many also note that if you engage with the material and focus on understanding concepts like greedy algorithms and dynamic programming, you can gain a solid grasp of these important topics. It's recommended to take it if you're looking to prepare for technical interviews, but be prepared for the workload and complexity. If you're unsure, you might want to ask others about their specific experiences or tips for succeeding in the course.", - sender: 'bot', - timestamp: new Date() + id: Date.now().toString(), + text: "CS336 is generally considered to be a challenging course. Students have mentioned that it involves a mix of coding and proofs, which can make it harder compared to other classes. However, many also note that if you engage with the material and focus on understanding concepts like greedy algorithms and dynamic programming, you can gain a solid grasp of these important topics. It's recommended to take it if you're looking to prepare for technical interviews, but be prepared for the workload and complexity. If you're unsure, you might want to ask others about their specific experiences or tips for succeeding in the course.", + sender: 'bot', + timestamp: new Date(), }; export const userDummyMessage: Message = { - id: Date.now().toString(), - text: "Is CS336 a difficult course?", - sender: 'user', - timestamp: new Date() + id: Date.now().toString(), + text: 'Is CS336 a difficult course?', + sender: 'user', + timestamp: new Date(), }; export const thinkingMessage: Message = { - id: Date.now().toString(), - text: "eme is thinking...", - sender: 'bot', - timestamp: new Date() -}; \ No newline at end of file + id: Date.now().toString(), + text: 'eme is thinking...', + sender: 'bot', + timestamp: new Date(), +}; diff --git a/src/constants/executive-boards-list.ts b/src/constants/executive-boards-list.ts index ea5f6db..89de95e 100644 --- a/src/constants/executive-boards-list.ts +++ b/src/constants/executive-boards-list.ts @@ -2,97 +2,97 @@ import type { ExecMember } from '@/types/exec-board-type'; export const executiveMembers2526: ExecMember[] = [ { - role: "President", - name: "Sydney Hoppenworth", - pronouns: "She/They", - major: "Computer Science", - year: "Senior", - linkedin: "https://www.linkedin.com/in/sydney-hoppenworth", - image: "/exec-pictures/sydney.jpeg", - email: "SydneyHoppenworth2026@u.northwestern.edu", - }, - { - role: "Marketing Chair", - name: "Marissa Rocha Rangel", - major: "Computer Science", - year: "Sophomore", - pronouns: "She/Her", - linkedin: "https://www.linkedin.com/in/marissa-rocha-764a19281/", - image: "/exec-pictures/marissa.jpeg", - email: "MarissaRochaRangel2028@u.northwestern.edu", - }, - { - role: "Secretary", - name: "Michelle Ordonez Campos", - major: "Computer Science", - year: "Junior", - pronouns: "She/Her", - linkedin: "https://www.linkedin.com/in/mordonez719/", - image: "/exec-pictures/michelle.jpeg", - email: "MichelleOrdonezCampos2027@u.northwestern.edu", - }, - { - role: "External Coordinator", - name: "Joanna Echeverri Porras", - pronouns: "She/Her", - major: "Computer Science", - year: "Junior", + role: 'President', + name: 'Sydney Hoppenworth', + pronouns: 'She/They', + major: 'Computer Science', + year: 'Senior', + linkedin: 'https://www.linkedin.com/in/sydney-hoppenworth', + image: '/exec-pictures/sydney.jpeg', + email: 'SydneyHoppenworth2026@u.northwestern.edu', + }, + { + role: 'Marketing Chair', + name: 'Marissa Rocha Rangel', + major: 'Computer Science', + year: 'Sophomore', + pronouns: 'She/Her', + linkedin: 'https://www.linkedin.com/in/marissa-rocha-764a19281/', + image: '/exec-pictures/marissa.jpeg', + email: 'MarissaRochaRangel2028@u.northwestern.edu', + }, + { + role: 'Secretary', + name: 'Michelle Ordonez Campos', + major: 'Computer Science', + year: 'Junior', + pronouns: 'She/Her', + linkedin: 'https://www.linkedin.com/in/mordonez719/', + image: '/exec-pictures/michelle.jpeg', + email: 'MichelleOrdonezCampos2027@u.northwestern.edu', + }, + { + role: 'External Coordinator', + name: 'Joanna Echeverri Porras', + pronouns: 'She/Her', + major: 'Computer Science', + year: 'Junior', linkedin: 'https://www.linkedin.com/in/joanna-ep?miniProfileUrn=urn%3Ali%3Afs_miniProfile%3AACoAADuwj-wBV6vE5evg2lfQdfvHXy2WPyP0jMM&lipi=urn%3Ali%3Apage%3Ad_flagship3_search_srp_all%3BjUOheGLeT%2F%2B0K4QbZyteVw%3D%3D', image: '/exec-pictures/joanna.jpg', email: 'JoannaEcheverriPorras2027@u.northwestern.edu', }, { - role: "Technical Development Chair", - name: "Keigo Healy", - major: "Computer Science", - year: "Sophomore", - pronouns: "He/Him", - linkedin: "https://www.linkedin.com/in/keigo-healy-3a456a278/", - image: "/exec-pictures/keigo.jpg", - email: "KeigoHealy2028@u.northwestern.edu", + role: 'Technical Development Chair', + name: 'Keigo Healy', + major: 'Computer Science', + year: 'Sophomore', + pronouns: 'He/Him', + linkedin: 'https://www.linkedin.com/in/keigo-healy-3a456a278/', + image: '/exec-pictures/keigo.jpg', + email: 'KeigoHealy2028@u.northwestern.edu', }, { - role: "Professional Liaison", - name: "Lana Alnajm", - major: "Data Science, Social Policy", - year: "Sophomore", - pronouns: "She/Her", + role: 'Professional Liaison', + name: 'Lana Alnajm', + major: 'Data Science, Social Policy', + year: 'Sophomore', + pronouns: 'She/Her', linkedin: 'https://www.linkedin.com/in/lana-alnajm-451b532a6/?trk=organization_guest_main-feed-card-text', image: '/exec-pictures/lana.jpeg', email: 'LanaAlnajm2028@u.northwestern.edu', }, { - role: "Internal Coordinator", - name: "Areg Aslanyan", - pronouns: "He/Him", - major: "Computer Science, Psychology", - year: "Junior", + role: 'Internal Coordinator', + name: 'Areg Aslanyan', + pronouns: 'He/Him', + major: 'Computer Science, Psychology', + year: 'Junior', linkedin: 'https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://www.linkedin.com/in/areg-aslanyan&ved=2ahUKEwiA5rv5k4qMAxVJF1kFHReAMcAQFnoECCUQAQ&usg=AOvVaw0GT477FJbABMalZ4kesjOx', image: '/exec-pictures/areg.jpg', email: 'AregAslanyan2027@u.northwestern.edu', }, { - role: "Internal Coordinator", - name: "Melanie Cuenca", - pronouns: "She/Her", - major: "Computer Science, German", - year: "Junior", - linkedin: "https://www.linkedin.com/in/melanie-cuenca-9272112b6/", - image: "/exec-pictures/melanie.jpeg", - email: "MelanieCuenca2027@u.northwestern.edu", - }, - { - role: "Treasurer", - name: "Faris Eltayib", - pronouns: "He/Him", - major: "Computer Engineering", - year: "Junior", - linkedin: "https://www.linkedin.com/in/faris-eltayib-a89709297/", - image: "/exec-pictures/faris.jpg", - email: "FarisEltayib2027@u.northwestern.edu", + role: 'Internal Coordinator', + name: 'Melanie Cuenca', + pronouns: 'She/Her', + major: 'Computer Science, German', + year: 'Junior', + linkedin: 'https://www.linkedin.com/in/melanie-cuenca-9272112b6/', + image: '/exec-pictures/melanie.jpeg', + email: 'MelanieCuenca2027@u.northwestern.edu', + }, + { + role: 'Treasurer', + name: 'Faris Eltayib', + pronouns: 'He/Him', + major: 'Computer Engineering', + year: 'Junior', + linkedin: 'https://www.linkedin.com/in/faris-eltayib-a89709297/', + image: '/exec-pictures/faris.jpg', + email: 'FarisEltayib2027@u.northwestern.edu', }, ]; diff --git a/src/hooks/use-fetch-github.ts b/src/hooks/use-fetch-github.ts index 3a5fb45..d0d8c2a 100644 --- a/src/hooks/use-fetch-github.ts +++ b/src/hooks/use-fetch-github.ts @@ -38,11 +38,12 @@ export function useFetchGithub(repoPath: string): UseFetchGithubResult { const readme = await response.json(); const { content, encoding } = readme; // encoding should be "base64" - if (encoding !== "base64") throw new Error("Unexpected README encoding"); + if (encoding !== 'base64') + throw new Error('Unexpected README encoding'); + + const bytes = Uint8Array.from(atob(content), (c) => c.charCodeAt(0)); + const decodedContent = new TextDecoder('utf-8').decode(bytes); - const bytes = Uint8Array.from(atob(content), c => c.charCodeAt(0)); - const decodedContent = new TextDecoder("utf-8").decode(bytes); - const parsedJobs = parseInternshipData(decodedContent); setData(parsedJobs); } catch (error) { diff --git a/src/utils/parse-internships.ts b/src/utils/parse-internships.ts index 343c776..9c2dcc7 100644 --- a/src/utils/parse-internships.ts +++ b/src/utils/parse-internships.ts @@ -1,42 +1,54 @@ -import { Job } from "@/types/internship"; +import { Job } from '@/types/internship'; -const norm = (s?: string | null) => - (s ?? "").replace(/\s+/g, " ").trim(); +const norm = (s?: string | null) => (s ?? '').replace(/\s+/g, ' ').trim(); export function parseInternshipData(content: string): Job[] { // Parse HTML inside the Markdown string - const container = document.createElement("div"); + const container = document.createElement('div'); container.innerHTML = content; - const tables = Array.from(container.querySelectorAll("table")); - const target = tables.find(t => { - const ths = Array.from(t.querySelectorAll("thead th")).map(th => norm(th.textContent).toLowerCase()); - return ["company", "role", "location", "application", "age"].every(h => ths.includes(h)); + const tables = Array.from(container.querySelectorAll('table')); + const target = tables.find((t) => { + const ths = Array.from(t.querySelectorAll('thead th')).map((th) => + norm(th.textContent).toLowerCase() + ); + return ['company', 'role', 'location', 'application', 'age'].every((h) => + ths.includes(h) + ); }); if (!target) return []; - const rows = Array.from(target.querySelectorAll("tbody tr")); + const rows = Array.from(target.querySelectorAll('tbody tr')); const jobs: Job[] = []; - let lastCompany = ""; + let lastCompany = ''; for (const row of rows) { - const tds = row.querySelectorAll("td"); + const tds = row.querySelectorAll('td'); if (tds.length < 5) continue; - let company = norm(tds[0].querySelector("a")?.textContent || tds[0].textContent); - if (!company) company = lastCompany; else lastCompany = company; + let company = norm( + tds[0].querySelector('a')?.textContent || tds[0].textContent + ); + if (!company) company = lastCompany; + else lastCompany = company; const title = norm(tds[1].textContent); const location = norm(tds[2].textContent); // Application link: prefer external "apply" link, else Simplify, else first href - const anchors = Array.from(tds[3].querySelectorAll("a[href]")); - const apply = anchors.find(a => - /apply|greenhouse|lever|workday|myworkdayjobs|careers|jobs|boards\.greenhouse/i.test(a.href) + const anchors = Array.from( + tds[3].querySelectorAll('a[href]') + ); + const apply = anchors.find((a) => + /apply|greenhouse|lever|workday|myworkdayjobs|careers|jobs|boards\.greenhouse/i.test( + a.href + ) + ); + const simplify = anchors.find((a) => /simplify\.jobs/i.test(a.href)); + const link = norm( + (apply?.href || simplify?.href || anchors[0]?.href) ?? '' ); - const simplify = anchors.find(a => /simplify\.jobs/i.test(a.href)); - const link = norm((apply?.href || simplify?.href || anchors[0]?.href) ?? ""); const addedOn = norm(tds[4].textContent);