flow-extension/popup.js
type-two 501431bf59 Flow rinse: browser downloads, fast-fail, autoStart, launcher + self-healing launchd
- background.js: chrome.downloads via page session (no CSP/base64)
- content.js: fast-fail on Flow error banner, image vs video timeouts, autoStart
- local_server_example.py: /enqueue + /save, downloads/ output, resume-skip
- launch.sh: idempotent server-up + caffeinate + single-tab focus
- install.sh + plist: self-healing agent every 10 min (TCC-safe App Support copy)
- flowclient.py: enqueue/wait_for helper for game projects
- README: full setup + macOS TCC gotcha

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 01:20:32 +10:00

160 lines
5.2 KiB
JavaScript

// Default Settings
const DEFAULT_SETTINGS = {
serverUrl: 'http://localhost:8017',
isRunning: false,
promptSel: '',
submitSel: '',
resultSel: '',
autoStart: false,
pollInterval: 4000,
logs: []
};
// UI Elements
const serverUrlInput = document.getElementById('server-url');
const toggleBtn = document.getElementById('toggle-btn');
const globalStatus = document.getElementById('global-status');
const statusLabel = globalStatus.querySelector('.status-label');
const logConsole = document.getElementById('log-console');
const clearLogsBtn = document.getElementById('clear-logs');
const autoStartInput = document.getElementById('auto-start');
// Advanced inputs
const selPromptInput = document.getElementById('sel-prompt');
const selGenerateInput = document.getElementById('sel-generate');
const selModelInput = document.getElementById('sel-model');
const pollIntervalInput = document.getElementById('poll-interval');
// Load settings on startup
chrome.storage.local.get(DEFAULT_SETTINGS, (settings) => {
serverUrlInput.value = settings.serverUrl;
autoStartInput.checked = !!settings.autoStart;
selPromptInput.value = settings.promptSel;
selGenerateInput.value = settings.submitSel;
selModelInput.value = settings.resultSel;
pollIntervalInput.value = settings.pollInterval;
updateToggleUI(settings.isRunning);
renderLogs(settings.logs);
updateStatusUI(settings.isRunning ? 'running' : 'idle');
});
// Helper: Append log to UI console
function appendLogToUI(log) {
const entry = document.createElement('div');
entry.className = `log-entry ${log.level}-log`;
const time = new Date(log.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
entry.innerText = `[${time}] ${log.text}`;
logConsole.appendChild(entry);
logConsole.scrollTop = logConsole.scrollHeight;
}
// Render entire log list
function renderLogs(logs) {
logConsole.innerHTML = '';
if (!logs || logs.length === 0) {
const entry = document.createElement('div');
entry.className = 'log-entry system-log';
entry.innerText = '[System] No logs yet. Click Start to run.';
logConsole.appendChild(entry);
return;
}
logs.forEach(appendLogToUI);
}
// Update Toggle Button Style
function updateToggleUI(isRunning) {
if (isRunning) {
toggleBtn.textContent = 'Stop Automation';
toggleBtn.className = 'btn btn-primary stop-state';
} else {
toggleBtn.textContent = 'Start Automation';
toggleBtn.className = 'btn btn-primary start-state';
}
}
// Update Global Status indicator
function updateStatusUI(status) {
globalStatus.className = 'status-indicator';
if (status === 'running') {
globalStatus.classList.add('active');
statusLabel.textContent = 'Running';
} else if (status === 'idle') {
statusLabel.textContent = 'Idle';
} else if (status === 'error') {
globalStatus.classList.add('error');
statusLabel.textContent = 'Error';
} else if (status === 'working') {
globalStatus.classList.add('active');
statusLabel.textContent = 'Generating...';
}
}
// Input Change Handlers
function saveSetting(key, value) {
chrome.storage.local.set({ [key]: value });
}
serverUrlInput.addEventListener('change', (e) => saveSetting('serverUrl', e.target.value));
autoStartInput.addEventListener('change', (e) => saveSetting('autoStart', e.target.checked));
selPromptInput.addEventListener('change', (e) => saveSetting('promptSel', e.target.value));
selGenerateInput.addEventListener('change', (e) => saveSetting('submitSel', e.target.value));
selModelInput.addEventListener('change', (e) => saveSetting('resultSel', e.target.value));
pollIntervalInput.addEventListener('change', (e) => saveSetting('pollInterval', parseInt(e.target.value, 10) || 4000));
// Clear Logs
clearLogsBtn.addEventListener('click', () => {
chrome.storage.local.set({ logs: [] }, () => {
logConsole.innerHTML = '';
});
});
// Toggle Run Loop
toggleBtn.addEventListener('click', () => {
chrome.storage.local.get('isRunning', (res) => {
const nextState = !res.isRunning;
chrome.storage.local.set({ isRunning: nextState }, () => {
updateToggleUI(nextState);
updateStatusUI(nextState ? 'running' : 'idle');
// Log event
const logMsg = nextState ? 'Automation started.' : 'Automation stopped.';
addSystemLog(logMsg);
});
});
});
// Add System Log Helper
function addSystemLog(text) {
chrome.storage.local.get({ logs: [] }, (res) => {
const newLog = {
level: 'system',
text: text,
timestamp: Date.now()
};
const updated = [...res.logs, newLog].slice(-100); // limit to 100 logs
chrome.storage.local.set({ logs: updated }, () => {
appendLogToUI(newLog);
});
});
}
// Listen for storage changes to sync UI across multiple popup openings
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local') {
if (changes.isRunning) {
updateToggleUI(changes.isRunning.newValue);
updateStatusUI(changes.isRunning.newValue ? 'running' : 'idle');
}
if (changes.status) {
updateStatusUI(changes.status.newValue);
}
if (changes.logs) {
// Re-render logs if they changed (e.g. from background content script)
renderLogs(changes.logs.newValue);
}
}
});