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",
|
"@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",
|
||||||
|
@ -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",
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"]);
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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());
|
||||||
|
@ -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 (
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user