1
mirror of https://github.com/xitanggg/open-resume synced 2025-02-05 20:12:45 +01:00

added new fonts new color updated the ui of dark theme and chatbot

This commit is contained in:
sahuf2003 2024-10-12 16:58:58 +05:30
parent e3e14c0381
commit 7f574c3a53
26 changed files with 352 additions and 159 deletions

22
package-lock.json generated
View File

@ -21,6 +21,7 @@
"docx": "^8.5.0",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"lucide-react": "^0.451.0",
"next": "13.4.4",
"open-resume": "file:",
"pdfjs": "^2.5.0",
@ -8325,6 +8326,14 @@
"node": ">=10"
}
},
"node_modules/lucide-react": {
"version": "0.451.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.451.0.tgz",
"integrity": "sha512-OwQ3uljZLp2cerj8sboy5rnhtGTCl9UCJIhT1J85/yOuGVlEH+xaUPR7tvNdddPvmV5M5VLdr7cQuWE3hzA4jw==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@ -17506,6 +17515,12 @@
"yallist": "^4.0.0"
}
},
"lucide-react": {
"version": "0.451.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.451.0.tgz",
"integrity": "sha512-OwQ3uljZLp2cerj8sboy5rnhtGTCl9UCJIhT1J85/yOuGVlEH+xaUPR7tvNdddPvmV5M5VLdr7cQuWE3hzA4jw==",
"requires": {}
},
"lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@ -17915,6 +17930,7 @@
"eslint-config-next": "13.4.4",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"lucide-react": "^0.451.0",
"next": "13.4.4",
"open-resume": "file:",
"pdfjs": "^2.5.0",
@ -24229,6 +24245,12 @@
"yallist": "^4.0.0"
}
},
"lucide-react": {
"version": "0.451.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.451.0.tgz",
"integrity": "sha512-OwQ3uljZLp2cerj8sboy5rnhtGTCl9UCJIhT1J85/yOuGVlEH+xaUPR7tvNdddPvmV5M5VLdr7cQuWE3hzA4jw==",
"requires": {}
},
"lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",

View File

@ -24,6 +24,7 @@
"docx": "^8.5.0",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"lucide-react": "^0.451.0",
"next": "13.4.4",
"open-resume": "file:",
"pdfjs": "^2.5.0",

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/fonts/Helvetica.ttf Normal file

Binary file not shown.

View File

@ -10,7 +10,8 @@
@font-face {font-family: "OpenSans"; src: url("/fonts/OpenSans-Bold.ttf"); font-weight: bold;}
@font-face {font-family: "Raleway"; src: url("/fonts/Raleway-Regular.ttf");}
@font-face {font-family: "Raleway"; src: url("/fonts/Raleway-Bold.ttf"); font-weight: bold;}
@font-face {font-family: "Helevetica"; src: url("/fonts/Helevetica.ttf");}
@font-face {font-family: "Helevetica"; src: url("/fonts/Helevetica-Bold.ttf"); font-weight: bold;}
/* Serif Fonts */
@font-face {font-family: "Caladea"; src: url("/fonts/Caladea-Regular.ttf");}
@font-face {font-family: "Caladea"; src: url("/fonts/Caladea-Bold.ttf"); font-weight: bold;}
@ -22,3 +23,6 @@
@font-face {font-family: "PlayfairDisplay"; src: url("/fonts/PlayfairDisplay-Bold.ttf"); font-weight: bold;}
@font-face {font-family: "Merriweather"; src: url("/fonts/Merriweather-Regular.ttf");}
@font-face {font-family: "Merriweather"; src: url("/fonts/Merriweather-Bold.ttf"); font-weight: bold;}
@font-face {font-family: "Garamond"; src: url("/fonts/Garamond-Regular.ttf");}
@font-face {font-family: "Garamond"; src: url("/fonts/Garamond-Bold.ttf"); font-weight: bold;}

View File

@ -6,7 +6,7 @@ export async function POST(request: NextRequest) {
const { prompt }: { prompt: string } = await request.json();
const cohere = new CohereClientV2({
token: 'exWH6pp1G5wT7tpZ6ryCoVgE0n9w7e6l2Wr4CMx0',
token: '',
});
try {

View File

@ -1,32 +1,8 @@
"use client";
import { useEffect, useState } from "react";
import { useTheme } from "../contexts/ThemeContext"; // Import the useTheme hook
const DarkModeToggle = () => {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
// On initial load, set the theme based on user's preference
const storedTheme = localStorage.getItem("theme");
if (storedTheme === "dark" || (!storedTheme && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
setIsDarkMode(true);
document.documentElement.classList.add("dark");
} else {
setIsDarkMode(false);
document.documentElement.classList.remove("dark");
}
}, []);
const toggleTheme = () => {
if (isDarkMode) {
document.documentElement.classList.remove("dark");
localStorage.setItem("theme", "light");
} else {
document.documentElement.classList.add("dark");
localStorage.setItem("theme", "dark");
}
setIsDarkMode(!isDarkMode);
};
const { isDarkMode, toggleTheme } = useTheme(); // Get dark mode state and toggle function from context
return (
<button
@ -35,11 +11,11 @@ const DarkModeToggle = () => {
>
{isDarkMode ? (
<>
<span className="mr-2">🌞</span>
<span className="mr-2">🌞</span>
</>
) : (
<>
<span className="mr-2">🌙</span>
<span className="mr-2">🌙</span>
</>
)}
</button>

View File

@ -28,7 +28,7 @@ export const InputGroupWrapper = ({
children?: React.ReactNode;
wordCount?: number;
}) => (
<label className={`text-base font-medium text-gray-700 ${className}`}>
<label className={`text-base font-medium text-gray-700 ${className} dark:bg-white`}>
{label}
{wordCount !== undefined && (
<span className="ml-2 text-sm text-gray-500">{wordCount} words</span>

View File

@ -11,4 +11,12 @@ export const THEME_COLORS = [
"#0ea5e9", // Sky-500
"#818cf8", // Indigo-400
"#6366f1", // Indigo-500
"#964B00", //brwon
"#D3D3D3", //gray
"#F3CFC6", //pink
"#9370DB", //medium purple
"#6A5ACD", //stale blue
"#008080", //teal
"#001F3F", //navy blue
"#228B22", //forest green
];

View File

@ -1,15 +1,8 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useTheme } from "../contexts/ThemeContext"; // Import useTheme
/**
* A simple Tooltip component that shows tooltip text center below children on hover and on focus
*
* @example
* <Tooltip text="Tooltip Text">
* <div>Hello</div>
* </Tooltip>
*/
export const Tooltip = ({
text,
children,
@ -19,14 +12,14 @@ export const Tooltip = ({
}) => {
const spanRef = useRef<HTMLSpanElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const { isDarkMode } = useTheme(); // Access the dark mode state
const [tooltipPos, setTooltipPos] = useState({ top: 0, left: 0 });
const [show, setShow] = useState(false);
const showTooltip = () => setShow(true);
const hideTooltip = () => setShow(false);
// Hook to set tooltip position to be right below children and centered
useEffect(() => {
const span = spanRef.current;
const tooltip = tooltipRef.current;
@ -57,7 +50,9 @@ export const Tooltip = ({
<div
ref={tooltipRef}
role="tooltip"
className="absolute left-0 top-0 z-10 w-max rounded-md bg-gray-200 dark:bg-gray-600 px-2 py-0.5 text-sm text-black dark:text-white" // Added light and dark mode styles
className={`absolute left-0 top-0 z-10 w-max rounded-md px-2 py-0.5 text-sm ${
isDarkMode ? "bg-gray-600 text-white" : "bg-gray-200 text-black"
}`} // Toggle dark mode classes based on state
style={{
left: `${tooltipPos.left}px`,
top: `${tooltipPos.top}px`,

View File

@ -2,8 +2,9 @@
import { usePathname } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import logoSrc from "public/logo.svg";
import logoSrc from "public/logo.svg"; // Ensure your logo is an SVG
import { cx } from "lib/cx";
import DarkModeToggle from "./DarkModeToggle";
export const TopNavBar = () => {
const pathName = usePathname();
@ -18,12 +19,15 @@ export const TopNavBar = () => {
)}
>
<div className="flex h-10 w-full items-center justify-between">
<Link href="/">
<span className="sr-only">OpenResume</span>
<Link href="/" className="flex items-center">
{/* Ensure consistent styling */}
<span className="text-black dark:text-white text-lg font-bold mr-2">
OpenResume
</span>
<Image
src={logoSrc}
alt="OpenResume Logo"
className="h-8 w-full"
className="h-8"
priority
/>
</Link>
@ -37,12 +41,13 @@ export const TopNavBar = () => {
].map(([href, text]) => (
<Link
key={text}
className="rounded-md px-1.5 py-2 text-gray-500 hover:bg-gray-100 focus-visible:bg-gray-100 lg:px-4"
className="rounded-md px-1.5 py-2 text-gray-500 hover:bg-gray-100 focus-visible:bg-gray-100 dark:text-gray-300 lg:px-4"
href={href}
>
{text}
</Link>
))}
<div className="ml-1 mt-1">
<iframe
src="https://ghbtns.com/github-btn.html?user=xitanggg&repo=open-resume&type=star&count=true"
@ -52,6 +57,7 @@ export const TopNavBar = () => {
title="GitHub"
/>
</div>
<DarkModeToggle />
</nav>
</div>
</header>

View File

@ -17,13 +17,13 @@ export const Table = ({
const tableBody = table.slice(1);
return (
<table
className={cx("w-full divide-y border text-sm text-gray-900", className)}
className={cx("w-full divide-y border text-sm text-gray-900 dark:text-gray-300", className)}
>
<thead className="divide-y bg-gray-50 text-left align-top">
<thead className="divide-y bg-gray-50 text-left align-top ">
{title && (
<tr className="divide-x bg-gray-50">
<tr className="divide-x bg-gray-50 ">
<th
className="px-2 py-1.5 font-bold"
className="px-2 py-1.5 font-bold "
scope="colSpan"
colSpan={tableHeader.length}
>
@ -31,7 +31,7 @@ export const Table = ({
</th>
</tr>
)}
<tr className="divide-x bg-gray-50">
<tr className="divide-x bg-primary">
{tableHeader.map((item, idx) => (
<th className="px-2 py-1.5 font-semibold" scope="col" key={idx}>
{item}

View File

@ -33,6 +33,7 @@ const SANS_SERIF_ENGLISH_FONT_FAMILIES = [
"Montserrat",
"OpenSans",
"Raleway",
"Helevetica"
] as const;
const SERIF_ENGLISH_FONT_FAMILIES = [
@ -41,6 +42,7 @@ const SERIF_ENGLISH_FONT_FAMILIES = [
"RobotoSlab",
"PlayfairDisplay",
"Merriweather",
"Garamond"
] as const;
export const ENGLISH_FONT_FAMILIES = [
@ -67,12 +69,14 @@ export const FONT_FAMILY_TO_STANDARD_SIZE_IN_PT: Record<FontFamily, number> = {
Montserrat: 10,
OpenSans: 10,
Raleway: 10,
Helevetica:12,
// Serif Fonts
Caladea: 11,
Lora: 11,
RobotoSlab: 10,
PlayfairDisplay: 10,
Merriweather: 10,
Garamond:12,
// Non-English Fonts
NotoSansSC: 11,
};
@ -84,12 +88,14 @@ export const FONT_FAMILY_TO_DISPLAY_NAME: Record<FontFamily, string> = {
Montserrat: "Montserrat",
OpenSans: "Open Sans",
Raleway: "Raleway",
Helevetica:"Helevetica",
// Serif Fonts
Caladea: "Caladea",
Lora: "Lora",
RobotoSlab: "Roboto Slab",
PlayfairDisplay: "Playfair Display",
Merriweather: "Merriweather",
Garamond:"Garamond",
// Non-English Fonts
NotoSansSC: "思源黑体(简体)",
};

View File

@ -0,0 +1,56 @@
"use client"
import { createContext, useState, useEffect, ReactNode, useContext } from "react";
// Define the shape of the context
interface ThemeContextType {
isDarkMode: boolean;
toggleTheme: () => void;
}
// Create the context with a default value
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Create a provider component
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
const storedTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (storedTheme === "dark" || (!storedTheme && prefersDark)) {
setIsDarkMode(true);
document.documentElement.classList.add("dark");
} else {
setIsDarkMode(false);
document.documentElement.classList.remove("dark");
}
}, []);
const toggleTheme = () => {
if (isDarkMode) {
document.documentElement.classList.remove("dark");
localStorage.setItem("theme", "light");
setIsDarkMode(false);
} else {
document.documentElement.classList.add("dark");
localStorage.setItem("theme", "dark");
setIsDarkMode(true);
}
};
return (
<ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// Custom hook to use the ThemeContext in components
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
};

View File

@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from 'react';
"use client"
import { useState, useEffect, useRef } from 'react';
import { Send } from 'lucide-react';
interface Message {
type: 'user' | 'cohere';
@ -10,11 +10,27 @@ interface Message {
export default function ChatPage() {
const [userMessage, setUserMessage] = useState<string>('');
const [messages, setMessages] = useState<Message[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(scrollToBottom, [messages]);
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
}
}, [userMessage]);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
// Add user message to chat
if (!userMessage.trim()) return;
const newMessages = [...messages, { type: 'user', text: userMessage }];
setMessages(newMessages);
setUserMessage('');
@ -31,45 +47,64 @@ export default function ChatPage() {
const data = await response.json();
const cohereMessage = data.message;
// Add cohere response to chat
setMessages([...newMessages, { type: 'cohere', text: cohereMessage }]);
} catch (error) {
console.error('Error sending request:', error);
}
};
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleSubmit(event);
}
};
return (
<div className="flex flex-col h-full">
<div className="flex flex-col h-screen bg-gray-50">
{/* Chat display area */}
<div className="flex-grow p-4 overflow-y-auto space-y-4">
{messages.map((message, index) => (
<div
key={index}
className={`p-2 max-w-xs rounded-lg ${
message.type === 'user' ? 'bg-blue-500 text-white self-end' : 'bg-gray-200 text-black self-start'
}`}
>
{message.text}
</div>
))}
<div className="flex-grow p-4 overflow-y-auto">
<div className="max-w-3xl mx-auto space-y-4">
{messages.map((message, index) => (
<div
key={index}
className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`p-3 rounded-lg max-w-md ${
message.type === 'user'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-800 border border-gray-300'
}`}
>
{message.text}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
</div>
{/* Input section */}
<form onSubmit={handleSubmit} className="p-4 border-t flex items-center">
<input
type="text"
value={userMessage}
onChange={(e) => setUserMessage(e.target.value)}
placeholder="Enter your message..."
className="border p-2 flex-grow rounded-l-md"
/>
<button
type="submit"
className="bg-blue-500 text-white p-2 rounded-r-md hover:bg-blue-600"
>
Send
</button>
</form>
<div className="border-t bg-white p-4">
<form onSubmit={handleSubmit} className="max-w-3xl mx-auto flex items-end">
<textarea
ref={textareaRef}
value={userMessage}
onChange={(e) => setUserMessage(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Type your message here..."
className="flex-grow p-3 border rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none overflow-hidden"
rows={1}
/>
<button
type="submit"
className="bg-blue-500 text-white p-3 rounded-r-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 h-[42px]"
>
<Send size={20} />
</button>
</form>
</div>
</div>
);
}
}

View File

@ -44,7 +44,7 @@ export const Features = () => {
{FEATURES.map(({ src, title, text }) => (
<div className="px-2" key={title}>
<div className="relative w-96 self-center pl-16">
<dt className="text-2xl font-bold">
<dt className="text-2xl font-bold dark:text-gray-300">
<Image
src={src}
className="absolute left-0 top-1 h-12 w-12"
@ -52,7 +52,7 @@ export const Features = () => {
/>
{title}
</dt>
<dd className="mt-2">{text}</dd>
<dd className="mt-2 dark:text-gray-300">{text}</dd>
</div>
</div>
))}

View File

@ -12,8 +12,8 @@ export const Hero = () => {
<br />
resume easily
</h1>
<p className="mt-3 text-lg lg:mt-5 lg:text-xl">
With this free, open-source, and powerful resume builder
<p className="mt-3 text-lg lg:mt-5 lg:text-xl dark:text-gray-300">
With this free, open-source, and powerful resume
</p>
<Link href="/resume-import" className="btn-primary mt-6 lg:mt-14">
Create Resume <span aria-hidden="true"></span>

View File

@ -125,8 +125,8 @@ const QAS = [
export const QuestionsAndAnswers = () => {
return (
<section className="mx-auto max-w-3xl divide-y divide-gray-300 lg:mt-4 lg:px-2">
<h2 className="text-center text-3xl font-bold">Questions & Answers</h2>
<div className="mt-6 divide-y divide-gray-300">
<h2 className="text-center text-3xl font-bold dark:text-gray-300">Questions & Answers</h2>
<div className="mt-6 divide-y divide-gray-300 dark:text-gray-300">
{QAS.map(({ question, answer }) => (
<div key={question} className="py-6">
<h3 className="font-semibold leading-7">{question}</h3>

View File

@ -64,7 +64,7 @@ export const Testimonials = ({ children }: { children?: React.ReactNode }) => {
return (
<section className="mx-auto -mt-2 px-8 pb-24">
<h2 className="mb-8 text-center text-3xl font-bold">
<h2 className="mb-8 text-center text-3xl font-bold dark:text-gray-300">
People{" "}
<Image src={heartSrc} alt="love" className="-mt-1 inline-block w-7" />{" "}
OpenResume

View File

@ -1,7 +1,7 @@
import "globals.css";
import { TopNavBar } from "components/TopNavBar";
import { Analytics } from "@vercel/analytics/react";
import DarkModeToggle from "components/DarkModeToggle";
import { ThemeProvider } from "contexts/ThemeContext"; // Import ThemeProvider
export default function RootLayout({
children,
@ -11,12 +11,11 @@ export default function RootLayout({
return (
<html lang="en">
<body className="bg-white dark:bg-gray-900 text-black dark:text-white">
<TopNavBar />
<div className="flex top-4 right-4 z-50">
<DarkModeToggle /> {/* Positioning the button at the center */}
</div>
{children}
<Analytics />
<ThemeProvider>
<TopNavBar />
{children}
<Analytics />
</ThemeProvider>
</body>
</html>
);

View File

@ -3,59 +3,138 @@ import { Provider } from "react-redux";
import { store } from "lib/redux/store";
import { ResumeForm } from "components/ResumeForm";
import { Resume } from "components/Resume";
import ChatPage from "home/ChatPage";
import { useState } from "react";
import { useState, useRef, useEffect } from "react";
import { FaWandMagicSparkles } from "react-icons/fa6";
import { Send, X } from 'lucide-react';
// Floating Chat Button Component
interface FloatingChatButtonProps {
toggleChat: () => void;
interface Message {
type: 'user' | 'cohere';
text: string;
}
// Floating Chat Button Component
interface FloatingChatButtonProps {
toggleChat: () => void;
// Chat Sidebar Component
interface ChatSidebarProps {
isOpen: boolean;
toggleSidebar: () => void;
}
const FloatingChatButton: React.FC<FloatingChatButtonProps> = ({ toggleChat }) => (
<button
className="fixed bottom-20 right-5 z-50 flex items-center rounded-full bg-blue-500 p-3 text-white shadow-lg hover:bg-blue-600 focus:outline-none"
onClick={toggleChat}
>
<FaWandMagicSparkles className="mr-2" /> {/* Margin to the right of the icon */}
<span>Chat</span> {/* Use a span for proper text alignment */}
</button>
);
const ChatSidebar: React.FC<ChatSidebarProps> = ({ isOpen, toggleSidebar }) => {
const [userMessage, setUserMessage] = useState<string>('');
const [messages, setMessages] = useState<Message[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Chat Window Component
interface ChatWindowProps {
toggleChat: () => void;
}
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
const ChatWindow: React.FC<ChatWindowProps> = ({ toggleChat }) => (
<div className="fixed bottom-16 right-5 z-50 w-80 h-96 bg-white shadow-lg rounded-lg overflow-hidden">
<div className="flex justify-between items-center p-2 bg-blue-500 text-white">
<h3>Chat</h3>
<button onClick={toggleChat} className="text-white">
</button>
useEffect(scrollToBottom, [messages]);
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
}
}, [userMessage]);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!userMessage.trim()) return;
const newMessages = [...messages, { type: 'user', text: userMessage }];
setMessages(newMessages);
setUserMessage('');
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt: userMessage }),
});
const data = await response.json();
const cohereMessage = data.message;
setMessages([...newMessages, { type: 'cohere', text: cohereMessage }]);
} catch (error) {
console.error('Error sending request:', error);
}
};
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleSubmit(event);
}
};
return (
<div className={`fixed top-0 right-0 h-full w-96 bg-white shadow-lg transform transition-transform duration-300 ease-in-out dark:bg-gray-800 ${isOpen ? 'translate-x-0' : 'translate-x-full'}`}>
<div className="flex flex-col h-full">
<div className="flex justify-between items-center p-4 border-b dark:border-gray-700">
<h2 className="text-xl font-semibold dark:text-white">Chat</h2>
<button onClick={toggleSidebar} className="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-200">
<X size={24} />
</button>
</div>
<div className="flex-grow overflow-y-auto p-4">
<div className="space-y-4">
{messages.map((message, index) => (
<div
key={index}
className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`p-3 rounded-lg max-w-[80%] ${
message.type === 'user'
? 'bg-blue-500 text-white'
: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'
}`}
>
{message.text}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
</div>
<div className="border-t p-4 dark:border-gray-700">
<form onSubmit={handleSubmit} className="flex items-end">
<textarea
ref={textareaRef}
value={userMessage}
onChange={(e) => setUserMessage(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Type your message here..."
className="flex-grow p-2 border rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none overflow-hidden dark:bg-gray-600 dark:text-gray-200"
rows={1}
/>
<button
type="submit"
className="bg-blue-500 text-white p-2 rounded-r-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 h-[38px]"
>
<Send size={20} />
</button>
</form>
</div>
</div>
</div>
<div className="p-8 h-full">
<ChatPage />
</div>
</div>
);
);
};
const Create: React.FC = () => {
const [isChatOpen, setIsChatOpen] = useState(false);
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const toggleChat = () => {
setIsChatOpen(!isChatOpen);
const toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen);
};
return (
<Provider store={store}>
<main className="relative h-full w-full overflow-hidden bg-gray-50">
<main className="relative h-full w-full overflow-hidden bg-gray-50 dark:bg-gray-900">
<div className="grid grid-cols-3 md:grid-cols-6">
<div className="col-span-3">
<ResumeForm />
@ -65,11 +144,17 @@ const Create: React.FC = () => {
</div>
</div>
{/* Floating Chat Button */}
<FloatingChatButton toggleChat={toggleChat} />
{!isSidebarOpen && ( // Hide button when sidebar is open
<button
className="fixed bottom-10 right-5 z-50 flex items-center rounded-full bg-blue-500 p-3 text-white shadow-lg hover:bg-blue-600 focus:outline-none"
onClick={toggleSidebar}
>
<FaWandMagicSparkles className="mr-2" />
<span>Chat</span>
</button>
)}
{/* Conditionally render Chat Window with ChatPage */}
{isChatOpen && <ChatWindow toggleChat={toggleChat} />}
<ChatSidebar isOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
</main>
</Provider>
);

View File

@ -103,7 +103,7 @@ export const ResumeParserAlgorithmArticle = ({
return (
<article className="mt-10">
<Heading className="text-primary !mt-0 border-t-2 pt-8">
<Heading className="text-primary !mt-0 border-t-2 pt-8 ">
Resume Parser Algorithm Deep Dive
</Heading>
<Paragraph smallMarginTop={true}>
@ -113,7 +113,7 @@ export const ResumeParserAlgorithmArticle = ({
language)
</Paragraph>
{/* Step 1. Read the text items from a PDF file */}
<Heading level={2}>Step 1. Read the text items from a PDF file</Heading>
<Heading className="dark:text-gray-300" level={2}>Step 1. Read the text items from a PDF file</Heading>
<Paragraph smallMarginTop={true}>
A PDF file is a standardized file format defined by the{" "}
<Link href="https://www.iso.org/standard/51502.html">
@ -140,7 +140,7 @@ export const ResumeParserAlgorithmArticle = ({
(Note that x,y position is relative to the bottom left corner of the
page, which is the origin 0,0)
</Paragraph>
<div className="mt-4 max-h-72 overflow-y-scroll border scrollbar scrollbar-track-gray-100 scrollbar-thumb-gray-200 scrollbar-w-3">
<div className="mt-4 max-h-72 overflow-y-scroll border scrollbar scrollbar-track-gray-100 scrollbar-thumb-gray-200 scrollbar-w-3 dark:text-gray-300">
<Table
table={step1TextItemsTable}
className="!border-none"
@ -148,7 +148,7 @@ export const ResumeParserAlgorithmArticle = ({
/>
</div>
{/* Step 2. Group text items into lines */}
<Heading level={2}>Step 2. Group text items into lines</Heading>
<Heading className="dark:text-gray-300" level={2}>Step 2. Group text items into lines</Heading>
<Paragraph smallMarginTop={true}>
The extracted text items aren't ready to use yet and have 2 main issues:
</Paragraph>
@ -215,7 +215,7 @@ export const ResumeParserAlgorithmArticle = ({
<Table table={step2LinesTable} className="!border-none" />
</div>
{/* Step 3. Group lines into sections */}
<Heading level={2}>Step 3. Group lines into sections</Heading>
<Heading className="dark:text-gray-300" level={2}>Step 3. Group lines into sections</Heading>
<Paragraph smallMarginTop={true}>
At step 2, the resume parser starts building contexts and associations
to text items by first grouping them into lines. Step 3 continues the
@ -262,13 +262,13 @@ export const ResumeParserAlgorithmArticle = ({
</Paragraph>
<Step3SectionsTable sections={sections} />
{/* Step 4. Extract resume from sections */}
<Heading level={2}>Step 4. Extract resume from sections</Heading>
<Heading className="dark:text-gray-300" level={2}>Step 4. Extract resume from sections</Heading>
<Paragraph smallMarginTop={true}>
Step 4 is the last step of the resume parsing process and is also the
core of the resume parser, where it extracts resume information from the
sections.
</Paragraph>
<Heading level={3}>Feature Scoring System</Heading>
<Heading className="dark:text-gray-300" level={3}>Feature Scoring System</Heading>
<Paragraph smallMarginTop={true}>
The gist of the extraction engine is a feature scoring system. Each
resume attribute to be extracted has a custom feature sets, where each
@ -297,7 +297,7 @@ export const ResumeParserAlgorithmArticle = ({
indicating they are very unlikely to be the targeted attribute)
</Paragraph>
)}
<Heading level={3}>Feature Sets</Heading>
<Heading className="dark:text-gray-300" level={3}>Feature Sets</Heading>
<Paragraph smallMarginTop={true}>
Having explained the feature scoring system, we can dive more into how
feature sets are constructed for a resume attribute. It follows 2
@ -318,7 +318,7 @@ export const ResumeParserAlgorithmArticle = ({
title="Name Feature Sets"
className="mt-4"
/>
<Heading level={3}>Core Feature Function</Heading>
<Heading className="dark:text-gray-300" level={3}>Core Feature Function</Heading>
<Paragraph smallMarginTop={true}>
Each resume attribute has multiple feature sets. They can be found in
the source code under the extract-resume-from-sections folder and we
@ -327,7 +327,7 @@ export const ResumeParserAlgorithmArticle = ({
core feature function below.
</Paragraph>
<Table table={step4CoreFeatureFunctionTable} className="mt-4" />
<Heading level={3}>Special Case: Subsections</Heading>
<Heading className="dark:text-gray-300" level={3}>Special Case: Subsections</Heading>
<Paragraph smallMarginTop={true}>
The last thing that is worth mentioning is subsections. For profile
section, we can directly pass all the text items to the feature scoring
@ -461,7 +461,7 @@ const Step3SectionsTable = ({
}
return (
<div className="mt-4 max-h-96 overflow-y-scroll border scrollbar scrollbar-track-gray-100 scrollbar-thumb-gray-200 scrollbar-w-3">
<div className="mt-4 max-h-96 overflow-y-scroll border scrollbar scrollbar-track-gray-100 scrollbar-thumb-gray-200 scrollbar-w-3 dark:text-gray-300">
<Table
table={table}
className="!border-none"

View File

@ -5,7 +5,7 @@ import { deepClone } from "lib/deep-clone";
import { cx } from "lib/cx";
const TableRowHeader = ({ children }: { children: React.ReactNode }) => (
<tr className="divide-x bg-gray-50">
<tr className="divide-x bg-primary"> {/* Updated to bg-primary */}
<th className="px-3 py-2 font-semibold" scope="colgroup" colSpan={2}>
{children}
</th>
@ -57,8 +57,8 @@ export const ResumeTable = ({ resume }: { resume: Resume }) => {
skills.unshift(featuredSkills);
}
return (
<table className="mt-2 w-full border text-sm text-gray-900">
<tbody className="divide-y text-left align-top">
<table className="mt-2 w-full border text-sm text-gray-900 dark:text-gray-300 ">
<tbody className="divide-y text-left align-top ">
<TableRowHeader>Profile</TableRowHeader>
<TableRow label="Name" value={resume.profile.name} />
<TableRow label="Email" value={resume.profile.email} />
@ -119,12 +119,12 @@ export const ResumeTable = ({ resume }: { resume: Resume }) => {
/>
</Fragment>
))}
{resume.awards.length > 0 && (
{resume.awards.length > 0 && (
<TableRowHeader>Awards</TableRowHeader>
)}
{resume.awards.map((award, idx) => (
<Fragment key={idx}>
<TableRow label="award" value={award.award} />
<TableRow label="Award" value={award.award} />
<TableRow label="Date" value={award.date} />
<TableRow
label="Descriptions"

View File

@ -62,13 +62,13 @@ export default function ResumeParser() {
</section>
<FlexboxSpacer maxWidth={45} className="hidden md:block" />
</div>
<div className="flex px-6 text-gray-900 md:col-span-3 md:h-[calc(100vh-var(--top-nav-bar-height))] md:overflow-y-scroll">
<div className="flex px-6 text-gray-900 md:col-span-3 md:h-[calc(100vh-var(--top-nav-bar-height))] md:overflow-y-scroll dark:text-gray-300 ">
<FlexboxSpacer maxWidth={45} className="hidden md:block" />
<section className="max-w-[600px] grow">
<Heading className="text-primary !mt-4">
Resume Parser Playground
</Heading>
<Paragraph smallMarginTop={true}>
<Paragraph smallMarginTop={true} className="dark:text-gray-300">
This playground showcases the OpenResume resume parser and its
ability to parse information from a resume PDF. Click around the
PDF examples below to observe different parsing results.
@ -118,7 +118,7 @@ export default function ResumeParser() {
Resume Parsing Results
</Heading>
<ResumeTable resume={resume} />
<ResumeParserAlgorithmArticle
<ResumeParserAlgorithmArticle
textItems={textItems}
lines={lines}
sections={sections}