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:
parent
2906db90b1
commit
f566681358
18
package-lock.json
generated
18
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"]);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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());
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user