New release: added background persistence, page checking, and also a button injected to the WW posting toolbar

This commit is contained in:
2026-01-17 00:40:49 -05:00
parent 2e6dc5c028
commit 24370c0826
4 changed files with 292 additions and 56 deletions

View File

@@ -15,14 +15,15 @@ const copyRawBtn = document.getElementById("copyRawBtn");
const clearOutputBtn = document.getElementById("clearOutputBtn");
const OUTPUT_STORAGE_KEY = "lastOutput";
let persistTimer = null;
const AUTO_RUN_KEY = "autoRunDefaultTask";
const state = {
postingText: "",
tasks: [],
port: null,
isAnalyzing: false,
outputRaw: ""
outputRaw: "",
autoRunPending: false
};
function getStorage(keys) {
@@ -230,20 +231,9 @@ function renderOutput() {
}
function persistOutputNow() {
if (persistTimer) {
clearTimeout(persistTimer);
persistTimer = null;
}
return chrome.storage.local.set({ [OUTPUT_STORAGE_KEY]: state.outputRaw });
}
function schedulePersistOutput() {
if (persistTimer) clearTimeout(persistTimer);
persistTimer = setTimeout(() => {
void persistOutputNow();
}, 250);
}
function setStatus(message) {
statusEl.textContent = message;
}
@@ -263,6 +253,7 @@ function setAnalyzing(isAnalyzing) {
buttonRow.classList.toggle("hidden", isAnalyzing);
stopRow.classList.toggle("hidden", !isAnalyzing);
}
updateTaskSelectState();
}
function updatePostingCount() {
@@ -282,17 +273,30 @@ function renderTasks(tasks) {
option.textContent = "No tasks configured";
option.value = "";
taskSelect.appendChild(option);
taskSelect.disabled = true;
updateTaskSelectState();
return;
}
taskSelect.disabled = false;
for (const task of tasks) {
const option = document.createElement("option");
option.value = task.id;
option.textContent = task.name || "Untitled task";
taskSelect.appendChild(option);
}
updateTaskSelectState();
}
function updateTaskSelectState() {
const hasTasks = state.tasks.length > 0;
taskSelect.disabled = state.isAnalyzing || !hasTasks;
}
function isWaterlooWorksUrl(url) {
try {
return new URL(url).hostname === "waterlooworks.uwaterloo.ca";
} catch {
return false;
}
}
function sendToActiveTab(message) {
@@ -304,6 +308,11 @@ function sendToActiveTab(message) {
return;
}
if (!isWaterlooWorksUrl(tab.url || "")) {
reject(new Error("Open waterlooworks.uwaterloo.ca to use this."));
return;
}
chrome.tabs.sendMessage(tab.id, message, (response) => {
const error = chrome.runtime.lastError;
if (error) {
@@ -328,28 +337,34 @@ function ensurePort() {
if (message?.type === "DELTA") {
state.outputRaw += message.text;
renderOutput();
schedulePersistOutput();
return;
}
if (message?.type === "SYNC") {
state.outputRaw = message.text || "";
renderOutput();
if (message.streaming) {
setAnalyzing(true);
setStatus("Analyzing...");
}
return;
}
if (message?.type === "DONE") {
setAnalyzing(false);
setStatus("Done");
void persistOutputNow();
return;
}
if (message?.type === "ABORTED") {
setAnalyzing(false);
setStatus("Aborted.");
void persistOutputNow();
return;
}
if (message?.type === "ERROR") {
setAnalyzing(false);
setStatus(message.message || "Error during analysis.");
void persistOutputNow();
}
});
@@ -365,6 +380,7 @@ function ensurePort() {
async function loadTasks() {
const { tasks = [] } = await getStorage(["tasks"]);
renderTasks(tasks);
maybeRunDefaultTask();
}
async function loadTheme() {
@@ -398,6 +414,13 @@ async function handleAnalyze() {
return;
}
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const tab = tabs[0];
if (!tab?.url || !isWaterlooWorksUrl(tab.url)) {
setStatus("Open waterlooworks.uwaterloo.ca to run tasks.");
return;
}
const taskId = taskSelect.value;
const task = state.tasks.find((item) => item.id === taskId);
if (!task) {
@@ -436,7 +459,6 @@ async function handleAnalyze() {
state.outputRaw = "";
renderOutput();
void persistOutputNow();
setAnalyzing(true);
setStatus("Analyzing...");
@@ -452,7 +474,8 @@ async function handleAnalyze() {
systemPrompt: systemPrompt || "",
resume: resume || "",
taskText: task.text || "",
postingText: state.postingText
postingText: state.postingText,
tabId: tab.id
}
});
}
@@ -526,9 +549,42 @@ async function loadSavedOutput() {
renderOutput();
}
async function loadAutoRunRequest() {
const stored = await getStorage([AUTO_RUN_KEY]);
if (stored[AUTO_RUN_KEY]) {
state.autoRunPending = true;
await chrome.storage.local.remove(AUTO_RUN_KEY);
}
maybeRunDefaultTask();
}
function maybeRunDefaultTask() {
if (!state.autoRunPending) return;
if (state.isAnalyzing) return;
if (!state.tasks.length) return;
taskSelect.value = state.tasks[0].id;
state.autoRunPending = false;
void handleExtractAndAnalyze();
}
loadSavedOutput();
loadAutoRunRequest();
ensurePort();
chrome.storage.onChanged.addListener((changes) => {
if (changes[AUTO_RUN_KEY]?.newValue) {
state.autoRunPending = true;
void chrome.storage.local.remove(AUTO_RUN_KEY);
maybeRunDefaultTask();
}
if (changes[OUTPUT_STORAGE_KEY]?.newValue !== undefined) {
if (!state.isAnalyzing || !state.port) {
state.outputRaw = changes[OUTPUT_STORAGE_KEY].newValue || "";
renderOutput();
}
}
if (changes.theme) {
applyTheme(changes.theme.newValue || "system");
}