|
|
|
|
@@ -57,7 +57,9 @@ const state = {
|
|
|
|
|
selectedTaskId: "",
|
|
|
|
|
selectedEnvId: "",
|
|
|
|
|
selectedProfileId: "",
|
|
|
|
|
alwaysShowOutput: false
|
|
|
|
|
alwaysShowOutput: false,
|
|
|
|
|
activeTabId: null,
|
|
|
|
|
pendingConfigRefresh: false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async function switchState(stateName) {
|
|
|
|
|
@@ -118,6 +120,7 @@ function applyPopupDraft(draft) {
|
|
|
|
|
} else if (typeof draft.siteTextSelector === "string") {
|
|
|
|
|
state.siteTextTarget = { kind: "css", selector: draft.siteTextSelector };
|
|
|
|
|
}
|
|
|
|
|
updateCounts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function matchUrl(url, pattern) {
|
|
|
|
|
@@ -677,6 +680,10 @@ function setAnalyzing(isAnalyzing) {
|
|
|
|
|
updateTaskSelectState();
|
|
|
|
|
updateEnvSelectState();
|
|
|
|
|
updateProfileSelectState();
|
|
|
|
|
if (!isAnalyzing && state.pendingConfigRefresh) {
|
|
|
|
|
state.pendingConfigRefresh = false;
|
|
|
|
|
scheduleConfigRefresh();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateOutputVisibility() {
|
|
|
|
|
@@ -686,12 +693,93 @@ function updateOutputVisibility() {
|
|
|
|
|
outputSection.classList.toggle("hidden", shouldHide);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getSelectedTask() {
|
|
|
|
|
if (state.forcedTask) return state.forcedTask;
|
|
|
|
|
const selectedId = taskSelect?.value || state.selectedTaskId;
|
|
|
|
|
return state.tasks.find((item) => item.id === selectedId) || state.tasks[0] || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getSelectedProfile() {
|
|
|
|
|
const selectedId = profileSelect?.value || state.selectedProfileId;
|
|
|
|
|
return (
|
|
|
|
|
state.profiles.find((item) => item.id === selectedId) ||
|
|
|
|
|
state.profiles[0] ||
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getSelectedEnv() {
|
|
|
|
|
const selectedId = envSelect?.value || state.selectedEnvId;
|
|
|
|
|
return state.envs.find((item) => item.id === selectedId) || state.envs[0] || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildTotalPromptText() {
|
|
|
|
|
const task = getSelectedTask();
|
|
|
|
|
const profile = getSelectedProfile();
|
|
|
|
|
const env = getSelectedEnv();
|
|
|
|
|
const systemPrompt = env?.systemPrompt || "";
|
|
|
|
|
const userPrompt = buildUserMessage(
|
|
|
|
|
profile?.text || "",
|
|
|
|
|
task?.text || "",
|
|
|
|
|
state.siteText || ""
|
|
|
|
|
);
|
|
|
|
|
return systemPrompt ? `${systemPrompt}\n\n${userPrompt}` : userPrompt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateSiteTextCount() {
|
|
|
|
|
postingCountEl.textContent = `Site Text: ${state.siteText.length} chars`;
|
|
|
|
|
const length = (state.siteText || "").length;
|
|
|
|
|
postingCountEl.textContent = `Site Text: ${length} chars`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updatePromptCount(count) {
|
|
|
|
|
promptCountEl.textContent = `Task: ${count} chars`;
|
|
|
|
|
const total =
|
|
|
|
|
typeof count === "number" ? count : buildTotalPromptText().length;
|
|
|
|
|
promptCountEl.textContent = `Total: ${total} chars`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateCounts() {
|
|
|
|
|
updateSiteTextCount();
|
|
|
|
|
updatePromptCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let siteContentRefreshTimer = null;
|
|
|
|
|
function scheduleSiteContentRefresh() {
|
|
|
|
|
if (siteContentRefreshTimer) return;
|
|
|
|
|
siteContentRefreshTimer = window.setTimeout(() => {
|
|
|
|
|
siteContentRefreshTimer = null;
|
|
|
|
|
void refreshSiteContentCounts();
|
|
|
|
|
}, 250);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let configRefreshTimer = null;
|
|
|
|
|
function scheduleConfigRefresh() {
|
|
|
|
|
if (state.isAnalyzing) {
|
|
|
|
|
state.pendingConfigRefresh = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (configRefreshTimer) return;
|
|
|
|
|
configRefreshTimer = window.setTimeout(() => {
|
|
|
|
|
configRefreshTimer = null;
|
|
|
|
|
void loadConfig();
|
|
|
|
|
}, 250);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshSiteContentCounts() {
|
|
|
|
|
if (state.isAnalyzing) return;
|
|
|
|
|
if (state.currentPopupState !== "normal") return;
|
|
|
|
|
if (!state.siteTextTarget) return;
|
|
|
|
|
try {
|
|
|
|
|
const response = await sendToActiveTab({
|
|
|
|
|
type: "EXTRACT_BY_SELECTOR",
|
|
|
|
|
target: state.siteTextTarget
|
|
|
|
|
});
|
|
|
|
|
if (!response?.ok) return;
|
|
|
|
|
state.siteText = response.extracted || "";
|
|
|
|
|
state.siteTextTarget = response.target || state.siteTextTarget;
|
|
|
|
|
updateCounts();
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore refresh failures; counts will update on next explicit extract.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderTasks(tasks) {
|
|
|
|
|
@@ -792,6 +880,7 @@ function setEnvironmentSelection(envId) {
|
|
|
|
|
envSelect.value = target;
|
|
|
|
|
}
|
|
|
|
|
state.selectedEnvId = target;
|
|
|
|
|
updatePromptCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setProfileSelection(profileId) {
|
|
|
|
|
@@ -803,6 +892,7 @@ function setProfileSelection(profileId) {
|
|
|
|
|
profileSelect.value = target;
|
|
|
|
|
}
|
|
|
|
|
state.selectedProfileId = target;
|
|
|
|
|
updatePromptCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function selectTask(taskId, { resetEnv } = { resetEnv: false }) {
|
|
|
|
|
@@ -814,6 +904,7 @@ function selectTask(taskId, { resetEnv } = { resetEnv: false }) {
|
|
|
|
|
setEnvironmentSelection(getTaskDefaultEnvId(task));
|
|
|
|
|
setProfileSelection(getTaskDefaultProfileId(task));
|
|
|
|
|
}
|
|
|
|
|
updatePromptCount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function persistSelections() {
|
|
|
|
|
@@ -900,6 +991,7 @@ function ensurePort() {
|
|
|
|
|
async function loadConfig() {
|
|
|
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
|
|
|
const currentUrl = tabs[0]?.url || "";
|
|
|
|
|
state.activeTabId = tabs[0]?.id || null;
|
|
|
|
|
|
|
|
|
|
const { lastPopupState, [POPUP_DRAFT_KEY]: popupDraft } = await getStorage([
|
|
|
|
|
"lastPopupState",
|
|
|
|
|
@@ -968,6 +1060,9 @@ async function loadConfig() {
|
|
|
|
|
state.currentWorkspace = activeWorkspace;
|
|
|
|
|
currentWorkspaceName.textContent = activeWorkspace.name || "Global";
|
|
|
|
|
}
|
|
|
|
|
if (state.currentSite && !state.siteTextTarget) {
|
|
|
|
|
state.siteTextTarget = normalizeStoredExtractTarget(state.currentSite);
|
|
|
|
|
}
|
|
|
|
|
if (stored.theme) {
|
|
|
|
|
state.globalTheme = stored.theme;
|
|
|
|
|
}
|
|
|
|
|
@@ -1005,6 +1100,7 @@ async function loadConfig() {
|
|
|
|
|
state.selectedTaskId = "";
|
|
|
|
|
setEnvironmentSelection(effectiveEnvs[0]?.id || "");
|
|
|
|
|
setProfileSelection(effectiveProfiles[0]?.id || "");
|
|
|
|
|
updateCounts();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1040,6 +1136,10 @@ async function loadConfig() {
|
|
|
|
|
await persistSelections();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateCounts();
|
|
|
|
|
if (state.currentSite) {
|
|
|
|
|
await refreshSiteContentCounts();
|
|
|
|
|
}
|
|
|
|
|
maybeRunDefaultTask();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1068,8 +1168,7 @@ async function handleExtract() {
|
|
|
|
|
|
|
|
|
|
state.siteText = response.extracted || "";
|
|
|
|
|
state.siteTextTarget = response.target || target;
|
|
|
|
|
updateSiteTextCount();
|
|
|
|
|
updatePromptCount(0);
|
|
|
|
|
updateCounts();
|
|
|
|
|
setStatus("Text extracted.");
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
@@ -1199,7 +1298,7 @@ async function handleAnalyze() {
|
|
|
|
|
task.text || "",
|
|
|
|
|
state.siteText
|
|
|
|
|
);
|
|
|
|
|
updatePromptCount(promptText.length);
|
|
|
|
|
updatePromptCount();
|
|
|
|
|
|
|
|
|
|
state.outputRaw = "";
|
|
|
|
|
renderOutput();
|
|
|
|
|
@@ -1298,6 +1397,7 @@ async function runMinimalExtraction(text, minLength = 5) {
|
|
|
|
|
state.siteText = response.extracted;
|
|
|
|
|
state.siteTextTarget = response.target || { kind: "textScope", text: trimmed };
|
|
|
|
|
extractedPreview.textContent = state.siteText;
|
|
|
|
|
updateCounts();
|
|
|
|
|
await fillSiteDefaultsFromTab();
|
|
|
|
|
switchState("review");
|
|
|
|
|
await persistPopupDraft();
|
|
|
|
|
@@ -1336,6 +1436,7 @@ extractFullBtn.addEventListener("click", async () => {
|
|
|
|
|
state.siteText = response.extracted;
|
|
|
|
|
state.siteTextTarget = target;
|
|
|
|
|
extractedPreview.textContent = state.siteText;
|
|
|
|
|
updateCounts();
|
|
|
|
|
await fillSiteDefaultsFromTab();
|
|
|
|
|
switchState("review");
|
|
|
|
|
await persistPopupDraft();
|
|
|
|
|
@@ -1372,6 +1473,7 @@ retryExtractBtn.addEventListener("click", () => {
|
|
|
|
|
if (workspaceSelect) workspaceSelect.value = "global";
|
|
|
|
|
state.siteText = "";
|
|
|
|
|
state.siteTextTarget = null;
|
|
|
|
|
updateCounts();
|
|
|
|
|
setMinimalStatus("");
|
|
|
|
|
void clearPopupDraft();
|
|
|
|
|
setStatus("Ready.");
|
|
|
|
|
@@ -1427,7 +1529,7 @@ confirmSiteBtn.addEventListener("click", async () => {
|
|
|
|
|
currentWorkspaceName.textContent = state.currentWorkspace.name || "Global";
|
|
|
|
|
await loadConfig();
|
|
|
|
|
await switchState("normal");
|
|
|
|
|
updateSiteTextCount();
|
|
|
|
|
updateCounts();
|
|
|
|
|
setStatus("Site saved.");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -1450,8 +1552,7 @@ profileSelect.addEventListener("change", () => {
|
|
|
|
|
void persistSelections();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
updateSiteTextCount();
|
|
|
|
|
updatePromptCount(0);
|
|
|
|
|
updateCounts();
|
|
|
|
|
renderOutput();
|
|
|
|
|
setAnalyzing(false);
|
|
|
|
|
void loadTheme();
|
|
|
|
|
@@ -1597,4 +1698,27 @@ chrome.storage.onChanged.addListener((changes) => {
|
|
|
|
|
state.alwaysShowOutput = Boolean(changes.alwaysShowOutput.newValue);
|
|
|
|
|
updateOutputVisibility();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const configKeys = [
|
|
|
|
|
"tasks",
|
|
|
|
|
"envConfigs",
|
|
|
|
|
"profiles",
|
|
|
|
|
"shortcuts",
|
|
|
|
|
"workspaces",
|
|
|
|
|
"sites",
|
|
|
|
|
"theme",
|
|
|
|
|
"alwaysShowOutput"
|
|
|
|
|
];
|
|
|
|
|
if (configKeys.some((key) => changes[key])) {
|
|
|
|
|
scheduleConfigRefresh();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
chrome.runtime.onMessage.addListener((message, sender) => {
|
|
|
|
|
if (message?.type !== "SITE_CONTENT_CHANGED") return;
|
|
|
|
|
const senderTabId = sender?.tab?.id || null;
|
|
|
|
|
if (state.activeTabId && senderTabId && senderTabId !== state.activeTabId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
scheduleSiteContentRefresh();
|
|
|
|
|
});
|
|
|
|
|
|