1
mirror of https://github.com/xitanggg/open-resume synced 2024-11-03 09:19:21 +01:00

Add a returnConcatenatedStringForTextsWithSameHighestScore arg in getTextWithHighestFeatureScore

This commit is contained in:
Xitang 2023-07-10 03:23:08 -07:00 committed by Xitang Zhao
parent 2906db90b1
commit f566681358
12 changed files with 59 additions and 59 deletions

18
package-lock.json generated
View File

@ -11,7 +11,6 @@
"@heroicons/react": "^2.0.18", "@heroicons/react": "^2.0.18",
"@react-pdf/renderer": "^3.1.10", "@react-pdf/renderer": "^3.1.10",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@types/lodash": "^4.14.195",
"@types/node": "20.2.5", "@types/node": "20.2.5",
"@types/react": "18.2.7", "@types/react": "18.2.7",
"@types/react-dom": "18.2.4", "@types/react-dom": "18.2.4",
@ -19,7 +18,6 @@
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"eslint": "8.41.0", "eslint": "8.41.0",
"eslint-config-next": "13.4.4", "eslint-config-next": "13.4.4",
"lodash": "^4.17.21",
"next": "13.4.4", "next": "13.4.4",
"pdfjs": "^2.5.0", "pdfjs": "^2.5.0",
"pdfjs-dist": "^3.7.107", "pdfjs-dist": "^3.7.107",
@ -1956,11 +1954,6 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
}, },
"node_modules/@types/lodash": {
"version": "4.14.195",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg=="
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.2.5", "version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
@ -6491,7 +6484,8 @@
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
}, },
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
@ -10784,11 +10778,6 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
}, },
"@types/lodash": {
"version": "4.14.195",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg=="
},
"@types/node": { "@types/node": {
"version": "20.2.5", "version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
@ -14112,7 +14101,8 @@
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
}, },
"lodash.merge": { "lodash.merge": {
"version": "4.6.2", "version": "4.6.2",

View File

@ -14,7 +14,6 @@
"@heroicons/react": "^2.0.18", "@heroicons/react": "^2.0.18",
"@react-pdf/renderer": "^3.1.10", "@react-pdf/renderer": "^3.1.10",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@types/lodash": "^4.14.195",
"@types/node": "20.2.5", "@types/node": "20.2.5",
"@types/react": "18.2.7", "@types/react": "18.2.7",
"@types/react-dom": "18.2.4", "@types/react-dom": "18.2.4",
@ -22,7 +21,6 @@
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"eslint": "8.41.0", "eslint": "8.41.0",
"eslint-config-next": "13.4.4", "eslint-config-next": "13.4.4",
"lodash": "^4.17.21",
"next": "13.4.4", "next": "13.4.4",
"pdfjs": "^2.5.0", "pdfjs": "^2.5.0",
"pdfjs-dist": "^3.7.107", "pdfjs-dist": "^3.7.107",

View File

@ -3,16 +3,15 @@ import { LockClosedIcon } from "@heroicons/react/24/solid";
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from "@heroicons/react/24/outline";
import { parseResumeFromPdf } from "lib/parse-resume-from-pdf"; import { parseResumeFromPdf } from "lib/parse-resume-from-pdf";
import { import {
isLocalStorageEmpty, getHasUsedAppBefore,
saveStateToLocalStorage, saveStateToLocalStorage,
} from "lib/redux/local-storage"; } from "lib/redux/local-storage";
import { initialSettings } from "lib/redux/settingsSlice"; import { type ShowForm, initialSettings } from "lib/redux/settingsSlice";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import addPdfSrc from "public/assets/add-pdf.svg"; import addPdfSrc from "public/assets/add-pdf.svg";
import Image from "next/image"; import Image from "next/image";
import { cx } from "lib/cx"; import { cx } from "lib/cx";
import isEmpty from "lodash/isEmpty"; import { deepClone } from "lib/deep-clone";
import cloneDeep from "lodash/cloneDeep";
const defaultFileState = { const defaultFileState = {
name: "", name: "",
@ -74,15 +73,20 @@ export const ResumeDropzone = ({
const onImportClick = async () => { const onImportClick = async () => {
const resume = await parseResumeFromPdf(file.fileUrl); const resume = await parseResumeFromPdf(file.fileUrl);
let settings = cloneDeep(initialSettings); const settings = deepClone(initialSettings);
const sections = Object.keys(
settings.formToShow // Set formToShow settings based on uploaded resume if users have used the app before
) as (keyof typeof settings.formToShow)[]; if (getHasUsedAppBefore()) {
if (!isLocalStorageEmpty()) { const sections = Object.keys(settings.formToShow) as ShowForm[];
const sectionToFormToShow: Record<ShowForm, boolean> = {
workExperiences: resume.workExperiences.length > 0,
educations: resume.educations.length > 0,
projects: resume.projects.length > 0,
skills: resume.skills.descriptions.length > 0,
custom: resume.custom.descriptions.length > 0,
};
for (const section of sections) { for (const section of sections) {
if (isEmpty(resume[section])) { settings.formToShow[section] = sectionToFormToShow[section];
settings.formToShow[section] = false;
}
} }
} }

View File

@ -21,7 +21,7 @@ const CHARS_PER_INTERVAL = 10;
const RESET_INTERVAL_MS = 60 * 1000; // 60s const RESET_INTERVAL_MS = 60 * 1000; // 60s
export const AutoTypingResume = () => { export const AutoTypingResume = () => {
const [resume, setResume] = useState(deepClone(initialResumeState) as Resume); const [resume, setResume] = useState(deepClone(initialResumeState));
const resumeCharIterator = useRef( const resumeCharIterator = useRef(
makeObjectCharIterator(START_HOME_RESUME, END_HOME_RESUME) makeObjectCharIterator(START_HOME_RESUME, END_HOME_RESUME)
); );

View File

@ -1,6 +1,8 @@
/** /**
* Server side deep clone util. * Server side object deep clone util using JSON serialization.
* Not efficient for large objects but good enough for most use cases.
*
* Client side can simply use structuredClone. * Client side can simply use structuredClone.
*/ */
export const deepClone = (object: { [key: string]: any }) => export const deepClone = <T extends { [key: string]: any }>(object: T) =>
JSON.parse(JSON.stringify(object)); JSON.parse(JSON.stringify(object)) as T;

View File

@ -17,8 +17,13 @@ type Object = { [key: string]: any };
* iterator.next().value // {a : "ab"} * iterator.next().value // {a : "ab"}
* iterator.next().value // {a : "abc"} * iterator.next().value // {a : "abc"}
*/ */
export function* makeObjectCharIterator(start: Object, end: Object, level = 0) { export function* makeObjectCharIterator<T extends Object>(
const object = level === 0 ? deepClone(start) : start; start: T,
end: T,
level = 0
) {
// Have to manually cast Object type and return T type due to https://github.com/microsoft/TypeScript/issues/47357
const object: Object = level === 0 ? deepClone(start) : start;
for (const [key, endValue] of Object.entries(end)) { for (const [key, endValue] of Object.entries(end)) {
if (typeof endValue === "object") { if (typeof endValue === "object") {
const recursiveIterator = makeObjectCharIterator( const recursiveIterator = makeObjectCharIterator(
@ -31,12 +36,12 @@ export function* makeObjectCharIterator(start: Object, end: Object, level = 0) {
if (next.done) { if (next.done) {
break; break;
} }
yield deepClone(object); yield deepClone(object) as T;
} }
} else { } else {
for (let i = 1; i <= endValue.length; i++) { for (let i = 1; i <= endValue.length; i++) {
object[key] = endValue.slice(0, i); object[key] = endValue.slice(0, i);
yield deepClone(object); yield deepClone(object) as T;
} }
} }
} }

View File

@ -148,7 +148,9 @@ export const extractProfile = (sections: ResumeSectionToLines) => {
); );
const [summary, summaryScores] = getTextWithHighestFeatureScore( const [summary, summaryScores] = getTextWithHighestFeatureScore(
textItems, textItems,
SUMMARY_FEATURE_SETS SUMMARY_FEATURE_SETS,
undefined,
true
); );
const summaryLines = getSectionLinesByKeywords(sections, ["summary"]); const summaryLines = getSectionLinesByKeywords(sections, ["summary"]);

View File

@ -1,4 +1,4 @@
import type { FeaturedSkill, ResumeSkills } from "lib/redux/types"; import type { ResumeSkills } from "lib/redux/types";
import type { ResumeSectionToLines } from "lib/parse-resume-from-pdf/types"; import type { ResumeSectionToLines } from "lib/parse-resume-from-pdf/types";
import { deepClone } from "lib/deep-clone"; import { deepClone } from "lib/deep-clone";
import { getSectionLinesByKeywords } from "lib/parse-resume-from-pdf/extract-resume-from-sections/lib/get-section-lines"; import { getSectionLinesByKeywords } from "lib/parse-resume-from-pdf/extract-resume-from-sections/lib/get-section-lines";
@ -14,7 +14,7 @@ export const extractSkills = (sections: ResumeSectionToLines) => {
const descriptionsLines = lines.slice(descriptionsLineIdx); const descriptionsLines = lines.slice(descriptionsLineIdx);
const descriptions = getBulletPointsFromLines(descriptionsLines); const descriptions = getBulletPointsFromLines(descriptionsLines);
const featuredSkills = deepClone(initialFeaturedSkills) as FeaturedSkill[]; const featuredSkills = deepClone(initialFeaturedSkills);
if (descriptionsLineIdx !== 0) { if (descriptionsLineIdx !== 0) {
const featuredSkillsLines = lines.slice(0, descriptionsLineIdx); const featuredSkillsLines = lines.slice(0, descriptionsLineIdx);
const featuredSkillsTextItems = featuredSkillsLines const featuredSkillsTextItems = featuredSkillsLines

View File

@ -50,7 +50,8 @@ const computeFeatureScores = (
export const getTextWithHighestFeatureScore = ( export const getTextWithHighestFeatureScore = (
textItems: TextItems, textItems: TextItems,
featureSets: FeatureSet[], featureSets: FeatureSet[],
returnEmptyStringIfHighestScoreIsNotPositive = true returnEmptyStringIfHighestScoreIsNotPositive = true,
returnConcatenatedStringForTextsWithSameHighestScore = false
) => { ) => {
const textScores = computeFeatureScores(textItems, featureSets); const textScores = computeFeatureScores(textItems, featureSets);
@ -61,7 +62,7 @@ export const getTextWithHighestFeatureScore = (
if (score > highestScore) { if (score > highestScore) {
textsWithHighestFeatureScore = []; textsWithHighestFeatureScore = [];
} }
textsWithHighestFeatureScore.push(text.trim()); textsWithHighestFeatureScore.push(text);
highestScore = score; highestScore = score;
} }
} }
@ -69,5 +70,9 @@ export const getTextWithHighestFeatureScore = (
if (returnEmptyStringIfHighestScoreIsNotPositive && highestScore <= 0) if (returnEmptyStringIfHighestScoreIsNotPositive && highestScore <= 0)
return ["", textScores] as const; return ["", textScores] as const;
return [textsWithHighestFeatureScore.join(" "), textScores] as const; const text = !returnConcatenatedStringForTextsWithSameHighestScore
? textsWithHighestFeatureScore[0]
: textsWithHighestFeatureScore.map((s) => s.trim()).join(" ");
return [text, textScores] as const;
}; };

View File

@ -4,7 +4,7 @@ import type { RootState } from "lib/redux/store";
const LOCAL_STORAGE_KEY = "open-resume-state"; const LOCAL_STORAGE_KEY = "open-resume-state";
export function loadStateFromLocalStorage() { export const loadStateFromLocalStorage = () => {
try { try {
const stringifiedState = localStorage.getItem(LOCAL_STORAGE_KEY); const stringifiedState = localStorage.getItem(LOCAL_STORAGE_KEY);
if (!stringifiedState) return undefined; if (!stringifiedState) return undefined;
@ -12,17 +12,15 @@ export function loadStateFromLocalStorage() {
} catch (e) { } catch (e) {
return undefined; return undefined;
} }
} };
export function isLocalStorageEmpty() { export const saveStateToLocalStorage = (state: RootState) => {
return !localStorage.length;
}
export function saveStateToLocalStorage(state: RootState) {
try { try {
const stringifiedState = JSON.stringify(state); const stringifiedState = JSON.stringify(state);
localStorage.setItem(LOCAL_STORAGE_KEY, stringifiedState); localStorage.setItem(LOCAL_STORAGE_KEY, stringifiedState);
} catch (e) { } catch (e) {
// Ignore // Ignore
} }
} };
export const getHasUsedAppBefore = () => Boolean(loadStateFromLocalStorage());

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import { loadStateFromLocalStorage } from "lib/redux/local-storage"; import { getHasUsedAppBefore } from "lib/redux/local-storage";
import { ResumeDropzone } from "components/ResumeDropzone"; import { ResumeDropzone } from "components/ResumeDropzone";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import Link from "next/link"; import Link from "next/link";
@ -12,7 +12,7 @@ export default function ImportResume() {
}; };
useEffect(() => { useEffect(() => {
setHasUsedAppBefore(Boolean(loadStateFromLocalStorage())); setHasUsedAppBefore(getHasUsedAppBefore());
}, []); }, []);
return ( return (

View File

@ -1,9 +1,5 @@
import { Fragment } from "react"; import { Fragment } from "react";
import type { import type { Resume } from "lib/redux/types";
Resume,
ResumeEducation,
ResumeWorkExperience,
} from "lib/redux/types";
import { initialEducation, initialWorkExperience } from "lib/redux/resumeSlice"; import { initialEducation, initialWorkExperience } from "lib/redux/resumeSlice";
import { deepClone } from "lib/deep-clone"; import { deepClone } from "lib/deep-clone";
import { cx } from "lib/cx"; import { cx } from "lib/cx";
@ -45,11 +41,11 @@ const TableRow = ({
export const ResumeTable = ({ resume }: { resume: Resume }) => { export const ResumeTable = ({ resume }: { resume: Resume }) => {
const educations = const educations =
resume.educations.length === 0 resume.educations.length === 0
? [deepClone(initialEducation) as ResumeEducation] ? [deepClone(initialEducation)]
: resume.educations; : resume.educations;
const workExperiences = const workExperiences =
resume.workExperiences.length === 0 resume.workExperiences.length === 0
? [deepClone(initialWorkExperience) as ResumeWorkExperience] ? [deepClone(initialWorkExperience)]
: resume.workExperiences; : resume.workExperiences;
const skills = [...resume.skills.descriptions]; const skills = [...resume.skills.descriptions];
const featuredSkills = resume.skills.featuredSkills const featuredSkills = resume.skills.featuredSkills