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

View File

@ -14,7 +14,6 @@
"@heroicons/react": "^2.0.18",
"@react-pdf/renderer": "^3.1.10",
"@reduxjs/toolkit": "^1.9.5",
"@types/lodash": "^4.14.195",
"@types/node": "20.2.5",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
@ -22,7 +21,6 @@
"autoprefixer": "10.4.14",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"lodash": "^4.17.21",
"next": "13.4.4",
"pdfjs": "^2.5.0",
"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 { parseResumeFromPdf } from "lib/parse-resume-from-pdf";
import {
isLocalStorageEmpty,
getHasUsedAppBefore,
saveStateToLocalStorage,
} 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 addPdfSrc from "public/assets/add-pdf.svg";
import Image from "next/image";
import { cx } from "lib/cx";
import isEmpty from "lodash/isEmpty";
import cloneDeep from "lodash/cloneDeep";
import { deepClone } from "lib/deep-clone";
const defaultFileState = {
name: "",
@ -74,15 +73,20 @@ export const ResumeDropzone = ({
const onImportClick = async () => {
const resume = await parseResumeFromPdf(file.fileUrl);
let settings = cloneDeep(initialSettings);
const sections = Object.keys(
settings.formToShow
) as (keyof typeof settings.formToShow)[];
if (!isLocalStorageEmpty()) {
const settings = deepClone(initialSettings);
// Set formToShow settings based on uploaded resume if users have used the app before
if (getHasUsedAppBefore()) {
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) {
if (isEmpty(resume[section])) {
settings.formToShow[section] = false;
}
settings.formToShow[section] = sectionToFormToShow[section];
}
}

View File

@ -21,7 +21,7 @@ const CHARS_PER_INTERVAL = 10;
const RESET_INTERVAL_MS = 60 * 1000; // 60s
export const AutoTypingResume = () => {
const [resume, setResume] = useState(deepClone(initialResumeState) as Resume);
const [resume, setResume] = useState(deepClone(initialResumeState));
const resumeCharIterator = useRef(
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.
*/
export const deepClone = (object: { [key: string]: any }) =>
JSON.parse(JSON.stringify(object));
export const deepClone = <T extends { [key: string]: any }>(object: T) =>
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 : "abc"}
*/
export function* makeObjectCharIterator(start: Object, end: Object, level = 0) {
const object = level === 0 ? deepClone(start) : start;
export function* makeObjectCharIterator<T extends Object>(
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)) {
if (typeof endValue === "object") {
const recursiveIterator = makeObjectCharIterator(
@ -31,12 +36,12 @@ export function* makeObjectCharIterator(start: Object, end: Object, level = 0) {
if (next.done) {
break;
}
yield deepClone(object);
yield deepClone(object) as T;
}
} else {
for (let i = 1; i <= endValue.length; 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(
textItems,
SUMMARY_FEATURE_SETS
SUMMARY_FEATURE_SETS,
undefined,
true
);
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 { deepClone } from "lib/deep-clone";
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 descriptions = getBulletPointsFromLines(descriptionsLines);
const featuredSkills = deepClone(initialFeaturedSkills) as FeaturedSkill[];
const featuredSkills = deepClone(initialFeaturedSkills);
if (descriptionsLineIdx !== 0) {
const featuredSkillsLines = lines.slice(0, descriptionsLineIdx);
const featuredSkillsTextItems = featuredSkillsLines

View File

@ -50,7 +50,8 @@ const computeFeatureScores = (
export const getTextWithHighestFeatureScore = (
textItems: TextItems,
featureSets: FeatureSet[],
returnEmptyStringIfHighestScoreIsNotPositive = true
returnEmptyStringIfHighestScoreIsNotPositive = true,
returnConcatenatedStringForTextsWithSameHighestScore = false
) => {
const textScores = computeFeatureScores(textItems, featureSets);
@ -61,7 +62,7 @@ export const getTextWithHighestFeatureScore = (
if (score > highestScore) {
textsWithHighestFeatureScore = [];
}
textsWithHighestFeatureScore.push(text.trim());
textsWithHighestFeatureScore.push(text);
highestScore = score;
}
}
@ -69,5 +70,9 @@ export const getTextWithHighestFeatureScore = (
if (returnEmptyStringIfHighestScoreIsNotPositive && highestScore <= 0)
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";
export function loadStateFromLocalStorage() {
export const loadStateFromLocalStorage = () => {
try {
const stringifiedState = localStorage.getItem(LOCAL_STORAGE_KEY);
if (!stringifiedState) return undefined;
@ -12,17 +12,15 @@ export function loadStateFromLocalStorage() {
} catch (e) {
return undefined;
}
}
};
export function isLocalStorageEmpty() {
return !localStorage.length;
}
export function saveStateToLocalStorage(state: RootState) {
export const saveStateToLocalStorage = (state: RootState) => {
try {
const stringifiedState = JSON.stringify(state);
localStorage.setItem(LOCAL_STORAGE_KEY, stringifiedState);
} catch (e) {
// Ignore
}
}
};
export const getHasUsedAppBefore = () => Boolean(loadStateFromLocalStorage());

View File

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

View File

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