Ability to save resume and model
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
# **N**otice **M**e **S**enpai
|
||||
|
||||
Firefox addon to aid in applying to jobs.
|
||||
|
||||
|
||||
## Todo
|
||||
|
||||
- Add option to keep model loaded vs load each time
|
||||
- model load progress monitoring in popup
|
||||
- allow file dropping on options ui
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
"browser_style": true
|
||||
},
|
||||
"background": {
|
||||
"scripts": ["background/background.js"]
|
||||
"scripts": [
|
||||
"background/background.js"
|
||||
]
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options/options.html",
|
||||
@@ -23,6 +25,16 @@
|
||||
"permissions": [
|
||||
"storage",
|
||||
"activeTab",
|
||||
"contextMenus"
|
||||
]
|
||||
"contextMenus",
|
||||
"<all_urls>",
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"downloads"
|
||||
],
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "nms@cray.software",
|
||||
"strict_min_version": "58.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,21 @@
|
||||
<p></p>
|
||||
<form>
|
||||
<div class="row">
|
||||
<label for="resumeFile" class="col-4 form-label">Resume</label>
|
||||
<em class="col-8 form-text text-end" id="resumeFileName">No Resume On File</em>
|
||||
<label for="resume" class="col-4 form-label">Resume</label>
|
||||
<em class="col-8 form-text text-end" id="resumeLastUpdated"></em>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="file" class="col-12 form-control" id="resumeFile" name="resumeFile" accept=".pdf"/>
|
||||
<textarea id="resume" name="resume" class="col-12 form-control" rows="5" placeholder="Paste your resume here..."></textarea>
|
||||
</div>
|
||||
<p></p>
|
||||
<div class="row p-3"></div>
|
||||
<div class="row">
|
||||
<label for="mlModel" class="col-4 form-label">Machine Learning Model</label>
|
||||
<select class="col-12 form-select" id="mlModel" name="mlModel">
|
||||
<option value="Llama-3.2-1B-Instruct-q4f32_1-MLC">Llama 3.2 1B Instruct</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row p-3"></div>
|
||||
<div class="row pb-5">
|
||||
<div class="col-8"></div>
|
||||
<input type="submit" class="col-4 btn" value="Save">
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
import { CreateMLCEngine } from "@mlc-ai/web-llm";
|
||||
|
||||
var engine = null
|
||||
var resume = ""
|
||||
var resourcesLoaded = false
|
||||
|
||||
const loadProgress = (progress) => {
|
||||
console.dir(progress)
|
||||
// Trigger popup and stream progress.timeElapsed
|
||||
}
|
||||
|
||||
const loadResources = async () => {
|
||||
let modelName = (await browser.storage.local.get("model")).model || "Llama-3.2-1B-Instruct-q4f32_1-MLC"
|
||||
console.log(`Loading model: ${modelName}`)
|
||||
engine = await CreateMLCEngine(modelName, { initProgressCallback: loadProgress });
|
||||
console.log("Model loaded")
|
||||
resume = (await browser.storage.local.get("resume")).resume || "No resume, halt and ask for resume."
|
||||
resourcesLoaded = true
|
||||
}
|
||||
|
||||
const runMatch = async (inputText) => {
|
||||
console.log("Run Match")
|
||||
if (!resourcesLoaded) await loadResources()
|
||||
const query = [
|
||||
{ role: "system", content: "You are a resume-job description matching assistant." },
|
||||
{ role: "user", content: `This is my resume: ${resume}` },
|
||||
{ role: "user", content: `This is the job description: ${inputText}` }
|
||||
]
|
||||
const response = await engine.chat.completions.create({
|
||||
messages: query
|
||||
})
|
||||
|
||||
console.dir(response)
|
||||
|
||||
}
|
||||
|
||||
// Add context menu items
|
||||
browser.contextMenus.create({
|
||||
@@ -10,5 +45,6 @@ browser.contextMenus.onClicked.addListener((info, tab) => {
|
||||
if (info.menuItemId === "nms-cmenu" && info.selectionText) {
|
||||
// Handle context menu click with selected text
|
||||
console.log("Context menu clicked with selection:", info.selectionText);
|
||||
runMatch(info.selectionText)
|
||||
}
|
||||
})
|
||||
|
||||
26
libs/base64.js
Normal file
26
libs/base64.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// Base64 UTF-8 Encode/Decode Utility Functions
|
||||
// Source: https://www.digitalocean.com/community/tutorials/how-to-encode-and-decode-strings-with-base64-in-javascript
|
||||
|
||||
|
||||
module.exports = {
|
||||
// Function to encode a UTF-8 string to Base64
|
||||
utf8ToBase64: (str) => {
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(str)
|
||||
|
||||
const binaryString = String.fromCharCode.apply(null, data)
|
||||
return btoa(binaryString)
|
||||
},
|
||||
|
||||
// Function to decode a Base64 string to UTF-8
|
||||
base64ToUtf8: (b64) => {
|
||||
const binaryString = atob(b64)
|
||||
// Create a Uint8Array from the binary string.
|
||||
const bytes = new Uint8Array(binaryString.length)
|
||||
for (let i = 0 ; i < binaryString.length ; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i)
|
||||
}
|
||||
const decoder = new TextDecoder()
|
||||
return decoder.decode(bytes)
|
||||
}
|
||||
}
|
||||
@@ -1 +1,44 @@
|
||||
import "../style.scss";
|
||||
import "../style.scss"
|
||||
import * as base64 from "../libs/base64.js"
|
||||
|
||||
const saveOptions = async (saveEvent) => {
|
||||
saveEvent.preventDefault()
|
||||
if (document.querySelector("#resume").value !== "" && (await browser.storage.local.get("resume")).resume !== base64.utf8ToBase64(document.querySelector("#resume").value)) {
|
||||
const LastEdited = new Date().toLocaleString()
|
||||
browser.storage.local.set({
|
||||
resume: base64.utf8ToBase64(document.querySelector("#resume").value),
|
||||
lastEdited: LastEdited
|
||||
})
|
||||
document.querySelector("#resumeLastUpdated").textContent = `Last Edited: ${LastEdited}`
|
||||
}
|
||||
|
||||
browser.storage.local.set({
|
||||
model: document.querySelector("#mlModel").value
|
||||
})
|
||||
}
|
||||
|
||||
const restoreOptions = async () => {
|
||||
const defaultModel = "Llama-3.2-1B-Instruct-q4f32_1-MLC"
|
||||
try {
|
||||
let resume = await browser.storage.local.get("resume")
|
||||
if (resume.resume) {
|
||||
document.querySelector("#resume").textContent = base64.base64ToUtf8(resume.resume)
|
||||
let lastEdited = await browser.storage.local.get("lastEdited")
|
||||
if (lastEdited.lastEdited) {
|
||||
document.querySelector("#resumeLastUpdated").textContent = `Last Edited: ${lastEdited.lastEdited}`
|
||||
}
|
||||
}
|
||||
|
||||
let model = await browser.storage.local.get("model") || defaultModel
|
||||
model = model === "" ? defaultModel : model
|
||||
// document.querySelector("#mlModel").value = model
|
||||
document.querySelectorAll("#mlModel.option").forEach((element) => {
|
||||
element.selected = (element.value === model)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error restoring options:", error)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", restoreOptions)
|
||||
document.querySelector("form").addEventListener("submit", saveOptions)
|
||||
|
||||
23
package-lock.json
generated
23
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@mlc-ai/web-llm": "^0.2.79",
|
||||
"bootstrap": "^5.3.8",
|
||||
"css-loader": "^7.1.2",
|
||||
"nord": "^0.2.1",
|
||||
@@ -73,6 +74,15 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mlc-ai/web-llm": {
|
||||
"version": "0.2.79",
|
||||
"resolved": "https://registry.npmjs.org/@mlc-ai/web-llm/-/web-llm-0.2.79.tgz",
|
||||
"integrity": "sha512-Hy1ZHQ0o2bZGZoVnGK48+fts/ZSKwLe96xjvqL/6C59Mem9HoHTcFE07NC2E23mRmhd01tL655N6CPeYmwWgwQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"loglevel": "^1.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
@@ -1303,6 +1313,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/loglevel": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
|
||||
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/loglevel"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"build": "webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mlc-ai/web-llm": "^0.2.79",
|
||||
"bootstrap": "^5.3.8",
|
||||
"css-loader": "^7.1.2",
|
||||
"nord": "^0.2.1",
|
||||
|
||||
@@ -11,7 +11,7 @@ module.exports = {
|
||||
filename: "[name]/[name].js"
|
||||
},
|
||||
mode: "none",
|
||||
watch: true,
|
||||
watch: false,
|
||||
watchOptions: {
|
||||
ignored: '**/node_modules',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user