Files
Ai-Interview-Assistant-Chro…/settings.js
2026-02-13 19:24:20 +01:00

1297 lines
52 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function() {
const closeSettingsBtn = document.getElementById('closeSettings');
const advancedModeToggle = document.getElementById('advancedModeToggle');
const webhookUrlInput = document.getElementById('webhookUrl');
const webhookPayloadInput = document.getElementById('webhookPayload');
const debugModeToggle = document.getElementById('debugModeToggle');
const webhookStatus = document.getElementById('webhookStatus');
const testWebhookBtn = document.getElementById('testWebhook');
const mcpServerUrlInput = document.getElementById('mcpServerUrl');
const mcpApiKeyInput = document.getElementById('mcpApiKey');
const toggleMcpKeyBtn = document.getElementById('toggleMcpKey');
const mcpStatus = document.getElementById('mcpStatus');
const testMcpBtn = document.getElementById('testMcp');
const cloudProviderInput = document.getElementById('cloudProvider');
const cloudEndpointInput = document.getElementById('cloudEndpoint');
const cloudStatus = document.getElementById('cloudStatus');
const testCloudBtn = document.getElementById('testCloud');
const gcalToolNameInput = document.getElementById('gcalToolName');
const gcalCalendarIdInput = document.getElementById('gcalCalendarId');
const gcalMaxResultsInput = document.getElementById('gcalMaxResults');
const gcalStatus = document.getElementById('gcalStatus');
const fetchCalendarBtn = document.getElementById('fetchCalendar');
const gcalOutput = document.getElementById('gcalOutput');
const refreshMcpToolsBtn = document.getElementById('refreshMcpTools');
const mcpToolsStatus = document.getElementById('mcpToolsStatus');
const mcpToolSelect = document.getElementById('mcpToolSelect');
const mcpToolArgs = document.getElementById('mcpToolArgs');
const runMcpToolBtn = document.getElementById('runMcpTool');
const mcpToolOutput = document.getElementById('mcpToolOutput');
const presetNameInput = document.getElementById('presetName');
const presetDescriptionInput = document.getElementById('presetDescription');
const presetTagsInput = document.getElementById('presetTags');
const presetToolNameInput = document.getElementById('presetToolName');
const presetArgsInput = document.getElementById('presetArgs');
const savePresetBtn = document.getElementById('savePreset');
const clearPresetBtn = document.getElementById('clearPreset');
const presetStatus = document.getElementById('presetStatus');
const presetSelect = document.getElementById('presetSelect');
const runPresetBtn = document.getElementById('runPreset');
const deletePresetBtn = document.getElementById('deletePreset');
const presetOutput = document.getElementById('presetOutput');
const automationList = document.getElementById('automationList');
const addAutomationBtn = document.getElementById('addAutomation');
const automationEditor = document.getElementById('automationEditor');
const automationEditorStatus = document.getElementById('automationEditorStatus');
const automationNameInput = document.getElementById('automationName');
const automationTypeSelect = document.getElementById('automationType');
const automationEnabledToggle = document.getElementById('automationEnabled');
const automationTriggerStart = document.getElementById('automationTriggerStart');
const automationTriggerEnd = document.getElementById('automationTriggerEnd');
const automationTriggerManual = document.getElementById('automationTriggerManual');
const automationRequireApproval = document.getElementById('automationRequireApproval');
const automationActionsSection = document.getElementById('automationActionsSection');
const automationStandupSection = document.getElementById('automationStandupSection');
const automationActionLabel = document.getElementById('automationActionLabel');
const automationActionType = document.getElementById('automationActionType');
const automationActionMcpFields = document.getElementById('automationActionMcpFields');
const automationActionWebhookFields = document.getElementById('automationActionWebhookFields');
const automationActionTool = document.getElementById('automationActionTool');
const automationActionArgs = document.getElementById('automationActionArgs');
const automationActionWebhookUrl = document.getElementById('automationActionWebhookUrl');
const automationActionWebhookMethod = document.getElementById('automationActionWebhookMethod');
const automationActionWebhookHeaders = document.getElementById('automationActionWebhookHeaders');
const automationActionWebhookBody = document.getElementById('automationActionWebhookBody');
const automationActionWebhookRetryCount = document.getElementById('automationActionWebhookRetryCount');
const addAutomationActionBtn = document.getElementById('addAutomationAction');
const automationActionSelect = document.getElementById('automationActionSelect');
const removeAutomationActionBtn = document.getElementById('removeAutomationAction');
const standupDiscordTool = document.getElementById('standupDiscordTool');
const standupDiscordArgs = document.getElementById('standupDiscordArgs');
const standupNextcloudTool = document.getElementById('standupNextcloudTool');
const standupNextcloudArgs = document.getElementById('standupNextcloudArgs');
const saveAutomationBtn = document.getElementById('saveAutomation');
const testAutomationBtn = document.getElementById('testAutomation');
const runAutomationNowBtn = document.getElementById('runAutomationNow');
const deleteAutomationBtn = document.getElementById('deleteAutomation');
const automationRunStatus = document.getElementById('automationRunStatus');
const automationOutput = document.getElementById('automationOutput');
const placeholderStatus = document.getElementById('placeholderStatus');
const DEFAULT_SETTINGS = {
advancedMode: false,
debugMode: false,
webhookUrl: '',
webhookPayload: '',
mcpServerUrl: '',
mcpApiKey: '',
cloudProvider: '',
cloudEndpoint: '',
gcalToolName: '',
gcalCalendarId: 'primary',
gcalMaxResults: 5,
customToolPresets: [],
automations: []
};
let activeAutomationId = null;
let activeAutomationActionId = '';
let lastFocusedInput = null;
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
if (advancedModeToggle) advancedModeToggle.checked = Boolean(settings.advancedMode);
if (debugModeToggle) debugModeToggle.checked = Boolean(settings.debugMode);
if (webhookUrlInput) webhookUrlInput.value = settings.webhookUrl || '';
if (webhookPayloadInput) webhookPayloadInput.value = settings.webhookPayload || '';
if (mcpServerUrlInput) mcpServerUrlInput.value = settings.mcpServerUrl || '';
if (mcpApiKeyInput) mcpApiKeyInput.value = settings.mcpApiKey || '';
if (cloudProviderInput) cloudProviderInput.value = settings.cloudProvider || '';
if (cloudEndpointInput) cloudEndpointInput.value = settings.cloudEndpoint || '';
if (gcalToolNameInput) gcalToolNameInput.value = settings.gcalToolName || '';
if (gcalCalendarIdInput) gcalCalendarIdInput.value = settings.gcalCalendarId || 'primary';
if (gcalMaxResultsInput) gcalMaxResultsInput.value = settings.gcalMaxResults || 5;
updatePresetSelect(settings.customToolPresets || []);
hydrateAutomations(settings.automations || []);
});
function saveSettings(partial) {
chrome.storage.sync.get(['advancedSettings'], (result) => {
const nextSettings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}), ...partial };
chrome.storage.sync.set({ advancedSettings: nextSettings });
});
}
function isValidUrl(value) {
try {
const url = new URL(value);
return Boolean(url.protocol && url.hostname);
} catch (error) {
return false;
}
}
function setStatus(el, text, type) {
if (!el) return;
el.textContent = text;
el.className = 'status-message';
if (type) el.classList.add(type);
}
function setOutput(el, text) {
if (!el) return;
el.textContent = text || '';
}
function setPlaceholderStatus(text, type) {
if (!placeholderStatus) return;
placeholderStatus.textContent = text;
placeholderStatus.className = 'status-message';
if (type) placeholderStatus.classList.add(type);
}
function updatePresetSelect(presets) {
if (!presetSelect) return;
presetSelect.innerHTML = '';
if (!presets || presets.length === 0) {
const option = document.createElement('option');
option.value = '';
option.textContent = 'No presets saved';
presetSelect.appendChild(option);
return;
}
presets.forEach((preset) => {
const option = document.createElement('option');
option.value = preset.id;
option.textContent = preset.name;
presetSelect.appendChild(option);
});
}
function hydrateAutomations(automations) {
renderAutomationList(automations);
if (!activeAutomationId && automations.length > 0) {
activeAutomationId = automations[0].id;
}
renderAutomationEditor(automations);
}
function renderAutomationList(automations) {
if (!automationList) return;
automationList.innerHTML = '';
if (!automations.length) {
const empty = document.createElement('div');
empty.className = 'status-message';
empty.textContent = 'No automations yet.';
automationList.appendChild(empty);
return;
}
automations.forEach((automation) => {
const item = document.createElement('button');
item.type = 'button';
item.className = `automation-item${automation.id === activeAutomationId ? ' active' : ''}`;
item.textContent = automation.name || 'Untitled Automation';
item.addEventListener('click', () => {
activeAutomationId = automation.id;
renderAutomationList(automations);
renderAutomationEditor(automations);
});
automationList.appendChild(item);
});
}
function renderAutomationEditor(automations) {
if (!automationEditor || !automationEditorStatus) return;
const automation = automations.find((item) => item.id === activeAutomationId);
if (!automation) {
automationEditor.style.display = 'none';
automationEditorStatus.textContent = 'Select an automation to edit.';
return;
}
automationEditor.style.display = 'block';
automationEditorStatus.textContent = '';
if (automationNameInput) automationNameInput.value = automation.name || '';
if (automationTypeSelect) automationTypeSelect.value = automation.kind || 'actions';
if (automationEnabledToggle) automationEnabledToggle.checked = Boolean(automation.enabled);
if (automationTriggerStart) automationTriggerStart.checked = Boolean(automation.triggers?.sessionStart);
if (automationTriggerEnd) automationTriggerEnd.checked = Boolean(automation.triggers?.sessionEnd);
if (automationTriggerManual) automationTriggerManual.checked = Boolean(automation.triggers?.manual);
if (automationRequireApproval) automationRequireApproval.checked = Boolean(automation.requireApproval);
const isStandup = automation.kind === 'standup';
if (automationActionsSection) automationActionsSection.style.display = isStandup ? 'none' : 'block';
if (automationStandupSection) automationStandupSection.style.display = isStandup ? 'block' : 'none';
if (isStandup) {
if (standupDiscordTool) standupDiscordTool.value = automation.standup?.discordToolName || '';
if (standupDiscordArgs) standupDiscordArgs.value = automation.standup?.discordArgsTemplate || '';
if (standupNextcloudTool) standupNextcloudTool.value = automation.standup?.nextcloudToolName || '';
if (standupNextcloudArgs) standupNextcloudArgs.value = automation.standup?.nextcloudArgsTemplate || '';
activeAutomationActionId = '';
clearAutomationActionForm();
} else {
updateAutomationActionSelect(automation.actions || []);
populateAutomationActionEditor(automation.actions || []);
}
}
function updateAutomationActionSelect(actions) {
if (!automationActionSelect) return;
automationActionSelect.innerHTML = '';
if (!actions || actions.length === 0) {
activeAutomationActionId = '';
const option = document.createElement('option');
option.value = '';
option.textContent = 'No actions configured';
automationActionSelect.appendChild(option);
return;
}
actions.forEach((action) => {
const option = document.createElement('option');
option.value = action.id;
const typeLabel = action.type === 'webhook' ? 'Webhook' : 'MCP';
option.textContent = `${action.label} (${typeLabel})`;
automationActionSelect.appendChild(option);
});
const selectedStillExists = actions.some((action) => action.id === activeAutomationActionId);
activeAutomationActionId = selectedStillExists ? activeAutomationActionId : actions[0].id;
automationActionSelect.value = activeAutomationActionId;
}
function populateAutomationActionEditor(actions) {
if (!actions || !actions.length || !automationActionSelect) {
clearAutomationActionForm();
return;
}
const selectedId = automationActionSelect.value || activeAutomationActionId;
const action = actions.find((item) => item.id === selectedId) || actions[0];
if (!action) {
clearAutomationActionForm();
return;
}
activeAutomationActionId = action.id;
if (automationActionSelect.value !== action.id) {
automationActionSelect.value = action.id;
}
if (automationActionLabel) automationActionLabel.value = action.label || '';
if (automationActionType) automationActionType.value = action.type || 'mcp';
if (action.type === 'webhook') {
if (automationActionWebhookUrl) automationActionWebhookUrl.value = action.webhookUrl || '';
if (automationActionWebhookMethod) automationActionWebhookMethod.value = action.method || 'POST';
if (automationActionWebhookHeaders) automationActionWebhookHeaders.value = JSON.stringify(action.headers || {}, null, 2);
if (automationActionWebhookBody) automationActionWebhookBody.value = action.bodyTemplate || '';
if (automationActionWebhookRetryCount) automationActionWebhookRetryCount.value = String(action.retryCount ?? 0);
if (automationActionTool) automationActionTool.value = '';
if (automationActionArgs) automationActionArgs.value = '';
} else {
if (automationActionTool) automationActionTool.value = action.toolName || '';
if (automationActionArgs) automationActionArgs.value = JSON.stringify(action.args || {}, null, 2);
if (automationActionWebhookUrl) automationActionWebhookUrl.value = '';
if (automationActionWebhookMethod) automationActionWebhookMethod.value = 'POST';
if (automationActionWebhookHeaders) automationActionWebhookHeaders.value = '';
if (automationActionWebhookBody) automationActionWebhookBody.value = '';
if (automationActionWebhookRetryCount) automationActionWebhookRetryCount.value = '0';
}
updateActionTypeUI();
}
function updateAutomation(automationId, updater) {
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const automations = Array.isArray(settings.automations) ? settings.automations : [];
const index = automations.findIndex((item) => item.id === automationId);
if (index === -1) return;
const updated = updater(automations[index]);
automations[index] = updated;
settings.automations = automations;
chrome.storage.sync.set({ advancedSettings: settings }, () => {
hydrateAutomations(automations);
});
});
}
function persistCurrentAutomationFromForm() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => {
const next = { ...automation };
if (automationNameInput) next.name = automationNameInput.value.trim();
if (automationTypeSelect) next.kind = automationTypeSelect.value;
if (automationEnabledToggle) next.enabled = automationEnabledToggle.checked;
next.triggers = {
sessionStart: Boolean(automationTriggerStart?.checked),
sessionEnd: Boolean(automationTriggerEnd?.checked),
manual: Boolean(automationTriggerManual?.checked)
};
next.requireApproval = Boolean(automationRequireApproval?.checked);
if (next.kind === 'standup') {
next.standup = {
discordToolName: standupDiscordTool?.value.trim() || '',
discordArgsTemplate: standupDiscordArgs?.value || '',
nextcloudToolName: standupNextcloudTool?.value.trim() || '',
nextcloudArgsTemplate: standupNextcloudArgs?.value || ''
};
}
return next;
});
}
function persistSelectedAutomationActionFromForm() {
if (!activeAutomationId || !activeAutomationActionId) return;
updateAutomation(activeAutomationId, (automation) => {
const actions = Array.isArray(automation.actions) ? [...automation.actions] : [];
const index = actions.findIndex((action) => action.id === activeAutomationActionId);
if (index === -1) return automation;
const existing = actions[index];
const type = automationActionType ? automationActionType.value : existing.type || 'mcp';
const label = automationActionLabel ? automationActionLabel.value.trim() : existing.label || '';
if (type === 'webhook') {
let headers = existing.headers || {};
if (automationActionWebhookHeaders) {
const parsedHeaders = readJsonArgs(
automationActionWebhookHeaders.value || '',
automationRunStatus,
'Webhook headers must be valid JSON.'
);
if (parsedHeaders !== null) {
headers = parsedHeaders;
}
}
const retryRaw = Number(automationActionWebhookRetryCount ? automationActionWebhookRetryCount.value : existing.retryCount || 0);
const retryCount = Number.isFinite(retryRaw) ? Math.max(0, Math.min(5, Math.floor(retryRaw))) : 0;
actions[index] = {
...existing,
type: 'webhook',
label,
webhookUrl: automationActionWebhookUrl ? automationActionWebhookUrl.value.trim() : existing.webhookUrl || '',
method: automationActionWebhookMethod ? automationActionWebhookMethod.value : existing.method || 'POST',
headers,
bodyTemplate: automationActionWebhookBody ? automationActionWebhookBody.value : existing.bodyTemplate || '',
retryCount
};
} else {
let args = existing.args || {};
if (automationActionArgs) {
const parsedArgs = readJsonArgs(
automationActionArgs.value || '',
automationRunStatus,
'Action args must be valid JSON.'
);
if (parsedArgs !== null) {
args = parsedArgs;
}
}
actions[index] = {
...existing,
type: 'mcp',
label,
toolName: automationActionTool ? automationActionTool.value.trim() : existing.toolName || '',
args
};
}
return { ...automation, actions };
});
}
function readJsonArgs(text, statusEl, errorMessage) {
if (!text.trim()) return {};
try {
return JSON.parse(text);
} catch (error) {
setStatus(statusEl, errorMessage, 'error');
return null;
}
}
function validateJsonField(inputEl, statusEl, label) {
if (!inputEl) return;
const value = inputEl.value.trim();
if (!value) {
setStatus(statusEl, '', '');
return;
}
try {
JSON.parse(value);
setStatus(statusEl, `${label} JSON is valid.`, 'success');
} catch (error) {
setStatus(statusEl, `${label} JSON is invalid.`, 'error');
}
}
function updateActionTypeUI() {
const type = automationActionType ? automationActionType.value : 'mcp';
if (automationActionMcpFields) automationActionMcpFields.style.display = type === 'mcp' ? 'block' : 'none';
if (automationActionWebhookFields) automationActionWebhookFields.style.display = type === 'webhook' ? 'block' : 'none';
}
function clearAutomationActionForm() {
if (automationActionLabel) automationActionLabel.value = '';
if (automationActionType) automationActionType.value = 'mcp';
if (automationActionTool) automationActionTool.value = '';
if (automationActionArgs) automationActionArgs.value = '';
if (automationActionWebhookUrl) automationActionWebhookUrl.value = '';
if (automationActionWebhookMethod) automationActionWebhookMethod.value = 'POST';
if (automationActionWebhookHeaders) automationActionWebhookHeaders.value = '';
if (automationActionWebhookBody) automationActionWebhookBody.value = '';
if (automationActionWebhookRetryCount) automationActionWebhookRetryCount.value = '0';
updateActionTypeUI();
}
if (advancedModeToggle) {
advancedModeToggle.addEventListener('change', function() {
saveSettings({ advancedMode: this.checked });
});
}
if (debugModeToggle) {
debugModeToggle.addEventListener('change', function() {
saveSettings({ debugMode: this.checked });
});
}
if (webhookUrlInput) {
webhookUrlInput.addEventListener('input', function() {
saveSettings({ webhookUrl: this.value.trim() });
if (this.value.trim() && !isValidUrl(this.value.trim())) {
setStatus(webhookStatus, 'Invalid URL format.', 'error');
} else {
setStatus(webhookStatus, '', '');
}
});
}
if (webhookPayloadInput) {
webhookPayloadInput.addEventListener('input', function() {
saveSettings({ webhookPayload: this.value });
validateJsonField(webhookPayloadInput, webhookStatus, 'Webhook payload');
});
}
if (mcpServerUrlInput) {
mcpServerUrlInput.addEventListener('input', function() {
saveSettings({ mcpServerUrl: this.value.trim() });
if (this.value.trim() && !isValidUrl(this.value.trim())) {
setStatus(mcpStatus, 'Invalid URL format.', 'error');
} else {
setStatus(mcpStatus, '', '');
}
});
}
if (mcpApiKeyInput) {
mcpApiKeyInput.addEventListener('input', function() {
saveSettings({ mcpApiKey: this.value.trim() });
});
}
if (toggleMcpKeyBtn && mcpApiKeyInput) {
toggleMcpKeyBtn.addEventListener('click', function() {
const isHidden = mcpApiKeyInput.type === 'password';
mcpApiKeyInput.type = isHidden ? 'text' : 'password';
toggleMcpKeyBtn.textContent = isHidden ? 'Hide' : 'Show';
});
}
if (cloudProviderInput) {
cloudProviderInput.addEventListener('input', function() {
saveSettings({ cloudProvider: this.value.trim() });
});
}
if (cloudEndpointInput) {
cloudEndpointInput.addEventListener('input', function() {
saveSettings({ cloudEndpoint: this.value.trim() });
if (this.value.trim() && !isValidUrl(this.value.trim())) {
setStatus(cloudStatus, 'Invalid URL format.', 'error');
} else {
setStatus(cloudStatus, '', '');
}
});
}
if (gcalToolNameInput) {
gcalToolNameInput.addEventListener('input', function() {
saveSettings({ gcalToolName: this.value.trim() });
});
}
if (gcalCalendarIdInput) {
gcalCalendarIdInput.addEventListener('input', function() {
saveSettings({ gcalCalendarId: this.value.trim() || 'primary' });
});
}
if (gcalMaxResultsInput) {
gcalMaxResultsInput.addEventListener('input', function() {
const parsed = Number(this.value);
saveSettings({ gcalMaxResults: Number.isFinite(parsed) && parsed > 0 ? parsed : 5 });
});
}
if (testWebhookBtn) {
testWebhookBtn.addEventListener('click', async function() {
const url = webhookUrlInput ? webhookUrlInput.value.trim() : '';
if (!isValidUrl(url)) {
setStatus(webhookStatus, 'Enter a valid webhook URL first.', 'error');
return;
}
setStatus(webhookStatus, 'Sending test payload...', '');
try {
let payload = {
type: 'test',
source: 'ai-assistant',
timestamp: new Date().toISOString()
};
if (webhookPayloadInput && webhookPayloadInput.value.trim()) {
payload = JSON.parse(webhookPayloadInput.value);
}
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const debugEnabled = debugModeToggle ? debugModeToggle.checked : false;
if (!response.ok) {
const errorText = await response.text();
const message = debugEnabled && errorText
? `Webhook responded with ${response.status}: ${errorText}`
: `Webhook responded with ${response.status}.`;
setStatus(webhookStatus, message, 'error');
return;
}
if (debugEnabled) {
const responseText = await response.text();
if (responseText) {
setStatus(webhookStatus, `Webhook test successful: ${responseText}`, 'success');
return;
}
}
setStatus(webhookStatus, 'Webhook test successful.', 'success');
} catch (error) {
const debugEnabled = debugModeToggle ? debugModeToggle.checked : false;
const message = debugEnabled && error.stack
? `${error.message}\n${error.stack}`
: error.message || 'Webhook test failed.';
setStatus(webhookStatus, message, 'error');
}
});
}
if (testMcpBtn) {
testMcpBtn.addEventListener('click', async function() {
const url = mcpServerUrlInput ? mcpServerUrlInput.value.trim() : '';
if (!isValidUrl(url)) {
setStatus(mcpStatus, 'Enter a valid MCP server URL first.', 'error');
return;
}
setStatus(mcpStatus, 'Checking MCP server...', '');
try {
let healthUrl = url.replace(/\/$/, '');
if (healthUrl.endsWith('/mcp/health')) {
// leave as is
} else if (healthUrl.endsWith('/mcp')) {
healthUrl = `${healthUrl}/health`;
} else if (!healthUrl.endsWith('/health')) {
healthUrl = `${healthUrl}/mcp/health`;
}
const headers = {};
const apiKey = mcpApiKeyInput ? mcpApiKeyInput.value.trim() : '';
if (apiKey) {
headers['x-mcp-api-key'] = apiKey;
headers.Authorization = `Bearer ${apiKey}`;
}
const response = await fetch(healthUrl, { method: 'GET', headers });
if (!response.ok) {
setStatus(mcpStatus, `MCP server responded with ${response.status}.`, 'error');
return;
}
setStatus(mcpStatus, 'MCP server is reachable.', 'success');
} catch (error) {
setStatus(mcpStatus, error.message || 'MCP test failed.', 'error');
}
});
}
if (testCloudBtn) {
testCloudBtn.addEventListener('click', async function() {
const endpoint = cloudEndpointInput ? cloudEndpointInput.value.trim() : '';
if (!endpoint || !isValidUrl(endpoint)) {
setStatus(cloudStatus, 'Enter a valid cloud endpoint first.', 'error');
return;
}
setStatus(cloudStatus, 'Validating cloud endpoint...', '');
try {
const response = await fetch(endpoint, { method: 'GET' });
if (!response.ok) {
setStatus(cloudStatus, `Cloud endpoint responded with ${response.status}.`, 'error');
return;
}
setStatus(cloudStatus, 'Cloud endpoint is reachable.', 'success');
} catch (error) {
setStatus(cloudStatus, error.message || 'Cloud test failed.', 'error');
}
});
}
if (fetchCalendarBtn) {
fetchCalendarBtn.addEventListener('click', function() {
const toolName = gcalToolNameInput ? gcalToolNameInput.value.trim() : '';
if (!toolName) {
setStatus(gcalStatus, 'Set the Google Calendar tool name first.', 'error');
return;
}
const calendarId = gcalCalendarIdInput ? gcalCalendarIdInput.value.trim() || 'primary' : 'primary';
const maxResultsRaw = gcalMaxResultsInput ? Number(gcalMaxResultsInput.value) : 5;
const maxResults = Number.isFinite(maxResultsRaw) && maxResultsRaw > 0 ? maxResultsRaw : 5;
setStatus(gcalStatus, 'Fetching events...', '');
setOutput(gcalOutput, '');
chrome.runtime.sendMessage(
{
action: 'mcp:callTool',
toolName,
args: {
calendarId,
maxResults,
timeMin: new Date().toISOString()
}
},
(response) => {
if (chrome.runtime.lastError || !response) {
setStatus(gcalStatus, 'Failed to fetch events.', 'error');
return;
}
if (!response.success) {
setStatus(gcalStatus, response.error || 'Failed to fetch events.', 'error');
return;
}
setStatus(gcalStatus, 'Events loaded.', 'success');
setOutput(gcalOutput, JSON.stringify(response.result, null, 2));
}
);
});
}
if (refreshMcpToolsBtn) {
refreshMcpToolsBtn.addEventListener('click', function() {
setStatus(mcpToolsStatus, 'Fetching tools...', '');
chrome.runtime.sendMessage({ action: 'mcp:listTools' }, (response) => {
if (chrome.runtime.lastError || !response) {
setStatus(mcpToolsStatus, 'Failed to fetch tools.', 'error');
return;
}
if (!response.success) {
setStatus(mcpToolsStatus, response.error || 'Failed to fetch tools.', 'error');
return;
}
if (mcpToolSelect) {
mcpToolSelect.innerHTML = '';
const tools = response.tools || [];
if (!tools.length) {
const option = document.createElement('option');
option.value = '';
option.textContent = 'No tools available';
mcpToolSelect.appendChild(option);
} else {
tools.forEach((tool) => {
const option = document.createElement('option');
option.value = tool.name || '';
option.textContent = tool.name || 'Unnamed tool';
mcpToolSelect.appendChild(option);
});
}
}
setStatus(mcpToolsStatus, `Loaded ${response.tools?.length || 0} tools.`, 'success');
});
});
}
if (runMcpToolBtn) {
runMcpToolBtn.addEventListener('click', function() {
const toolName = mcpToolSelect ? mcpToolSelect.value : '';
if (!toolName) {
setStatus(mcpToolsStatus, 'Select a tool first.', 'error');
return;
}
let args = {};
if (mcpToolArgs && mcpToolArgs.value.trim()) {
try {
args = JSON.parse(mcpToolArgs.value);
} catch (error) {
setStatus(mcpToolsStatus, 'Arguments must be valid JSON.', 'error');
return;
}
}
setStatus(mcpToolsStatus, 'Running tool...', '');
setOutput(mcpToolOutput, '');
chrome.runtime.sendMessage({ action: 'mcp:callTool', toolName, args }, (response) => {
if (chrome.runtime.lastError || !response) {
setStatus(mcpToolsStatus, 'Tool call failed.', 'error');
return;
}
if (!response.success) {
setStatus(mcpToolsStatus, response.error || 'Tool call failed.', 'error');
return;
}
setStatus(mcpToolsStatus, 'Tool executed.', 'success');
setOutput(mcpToolOutput, JSON.stringify(response.result, null, 2));
});
});
}
if (mcpToolArgs) {
mcpToolArgs.addEventListener('input', function() {
validateJsonField(mcpToolArgs, mcpToolsStatus, 'Tool args');
});
}
if (savePresetBtn) {
savePresetBtn.addEventListener('click', function() {
const name = presetNameInput ? presetNameInput.value.trim() : '';
const toolName = presetToolNameInput ? presetToolNameInput.value.trim() : '';
if (!name || !toolName) {
setStatus(presetStatus, 'Preset name and tool name are required.', 'error');
return;
}
let args = {};
if (presetArgsInput && presetArgsInput.value.trim()) {
const parsed = readJsonArgs(presetArgsInput.value, presetStatus, 'Preset arguments must be valid JSON.');
if (parsed === null) return;
args = parsed;
}
const tags = presetTagsInput
? presetTagsInput.value.split(',').map((tag) => tag.trim()).filter(Boolean)
: [];
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : [];
const existingIndex = presets.findIndex((item) => item.name.toLowerCase() === name.toLowerCase());
const nextPreset = {
id: existingIndex >= 0 ? presets[existingIndex].id : `${Date.now()}_${Math.random().toString(16).slice(2)}`,
name,
description: presetDescriptionInput ? presetDescriptionInput.value.trim() : '',
tags,
toolName,
args
};
if (existingIndex >= 0) {
presets[existingIndex] = nextPreset;
} else {
presets.push(nextPreset);
}
settings.customToolPresets = presets;
chrome.storage.sync.set({ advancedSettings: settings }, () => {
updatePresetSelect(presets);
setStatus(presetStatus, existingIndex >= 0 ? 'Preset updated.' : 'Preset saved.', 'success');
});
});
});
}
if (presetArgsInput) {
presetArgsInput.addEventListener('input', function() {
validateJsonField(presetArgsInput, presetStatus, 'Preset args');
});
}
if (clearPresetBtn) {
clearPresetBtn.addEventListener('click', function() {
if (presetNameInput) presetNameInput.value = '';
if (presetDescriptionInput) presetDescriptionInput.value = '';
if (presetTagsInput) presetTagsInput.value = '';
if (presetToolNameInput) presetToolNameInput.value = '';
if (presetArgsInput) presetArgsInput.value = '';
setStatus(presetStatus, '', '');
});
}
if (presetSelect) {
presetSelect.addEventListener('change', function() {
const id = presetSelect.value;
if (!id) return;
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : [];
const preset = presets.find((item) => item.id === id);
if (!preset) return;
if (presetNameInput) presetNameInput.value = preset.name;
if (presetDescriptionInput) presetDescriptionInput.value = preset.description || '';
if (presetTagsInput) presetTagsInput.value = Array.isArray(preset.tags) ? preset.tags.join(', ') : '';
if (presetToolNameInput) presetToolNameInput.value = preset.toolName;
if (presetArgsInput) presetArgsInput.value = JSON.stringify(preset.args || {}, null, 2);
});
});
}
if (runPresetBtn) {
runPresetBtn.addEventListener('click', function() {
const id = presetSelect ? presetSelect.value : '';
if (!id) {
setStatus(presetStatus, 'Select a preset first.', 'error');
return;
}
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : [];
const preset = presets.find((item) => item.id === id);
if (!preset) {
setStatus(presetStatus, 'Preset not found.', 'error');
return;
}
setStatus(presetStatus, 'Running preset...', '');
setOutput(presetOutput, '');
chrome.runtime.sendMessage(
{ action: 'mcp:callTool', toolName: preset.toolName, args: preset.args || {} },
(response) => {
if (chrome.runtime.lastError || !response) {
setStatus(presetStatus, 'Preset failed.', 'error');
return;
}
if (!response.success) {
setStatus(presetStatus, response.error || 'Preset failed.', 'error');
return;
}
setStatus(presetStatus, 'Preset executed.', 'success');
setOutput(presetOutput, JSON.stringify(response.result, null, 2));
}
);
});
});
}
if (deletePresetBtn) {
deletePresetBtn.addEventListener('click', function() {
const id = presetSelect ? presetSelect.value : '';
if (!id) {
setStatus(presetStatus, 'Select a preset first.', 'error');
return;
}
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const presets = Array.isArray(settings.customToolPresets) ? settings.customToolPresets : [];
const nextPresets = presets.filter((item) => item.id !== id);
settings.customToolPresets = nextPresets;
chrome.storage.sync.set({ advancedSettings: settings }, () => {
updatePresetSelect(nextPresets);
if (presetNameInput) presetNameInput.value = '';
if (presetDescriptionInput) presetDescriptionInput.value = '';
if (presetTagsInput) presetTagsInput.value = '';
if (presetToolNameInput) presetToolNameInput.value = '';
if (presetArgsInput) presetArgsInput.value = '';
setOutput(presetOutput, '');
setStatus(presetStatus, 'Preset deleted.', 'success');
});
});
});
}
if (addAutomationBtn) {
addAutomationBtn.addEventListener('click', function() {
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const automations = Array.isArray(settings.automations) ? settings.automations : [];
const newAutomation = {
id: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
name: 'New automation',
kind: 'actions',
enabled: true,
triggers: { sessionStart: false, sessionEnd: true, manual: true },
requireApproval: true,
actions: [],
standup: {
discordToolName: '',
discordArgsTemplate: '',
nextcloudToolName: '',
nextcloudArgsTemplate: '{\n \"path\": \"notes/daily/standup-{{date}}.txt\",\n \"content\": \"Daily Standup - {{date_human}}\\\\n\\\\nSummary\\\\n{{summary_full}}\\\\n\\\\nAction Items\\\\n{{action_items}}\\\\n\\\\nBlockers\\\\n{{blockers}}\\\\n\\\\nDecisions\\\\n{{decisions}}\"\n}'
}
};
automations.push(newAutomation);
settings.automations = automations;
chrome.storage.sync.set({ advancedSettings: settings }, () => {
activeAutomationId = newAutomation.id;
hydrateAutomations(automations);
});
});
});
}
if (automationNameInput) {
automationNameInput.addEventListener('input', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({ ...automation, name: this.value.trim() }));
});
}
if (automationTypeSelect) {
automationTypeSelect.addEventListener('change', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({ ...automation, kind: this.value }));
});
}
if (automationEnabledToggle) {
automationEnabledToggle.addEventListener('change', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({ ...automation, enabled: this.checked }));
});
}
if (automationTriggerStart) {
automationTriggerStart.addEventListener('change', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({
...automation,
triggers: { ...automation.triggers, sessionStart: this.checked }
}));
});
}
if (automationTriggerEnd) {
automationTriggerEnd.addEventListener('change', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({
...automation,
triggers: { ...automation.triggers, sessionEnd: this.checked }
}));
});
}
if (automationTriggerManual) {
automationTriggerManual.addEventListener('change', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({
...automation,
triggers: { ...automation.triggers, manual: this.checked }
}));
});
}
if (automationRequireApproval) {
automationRequireApproval.addEventListener('change', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({ ...automation, requireApproval: this.checked }));
});
}
if (addAutomationActionBtn) {
addAutomationActionBtn.addEventListener('click', function() {
if (!activeAutomationId) return;
const label = automationActionLabel ? automationActionLabel.value.trim() : '';
const type = automationActionType ? automationActionType.value : 'mcp';
if (!label) {
setStatus(automationRunStatus, 'Action label is required.', 'error');
return;
}
let nextAction = null;
if (type === 'mcp') {
const toolName = automationActionTool ? automationActionTool.value.trim() : '';
if (!toolName) {
setStatus(automationRunStatus, 'MCP tool name is required.', 'error');
return;
}
const args = readJsonArgs(automationActionArgs?.value || '', automationRunStatus, 'Action args must be valid JSON.');
if (args === null) return;
nextAction = {
id: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
type: 'mcp',
label,
toolName,
args
};
} else {
const url = automationActionWebhookUrl ? automationActionWebhookUrl.value.trim() : '';
const method = automationActionWebhookMethod ? automationActionWebhookMethod.value : 'POST';
const headers = readJsonArgs(
automationActionWebhookHeaders?.value || '',
automationRunStatus,
'Webhook headers must be valid JSON.'
);
if (headers === null) return;
const bodyTemplate = automationActionWebhookBody ? automationActionWebhookBody.value.trim() : '';
const retryRaw = Number(automationActionWebhookRetryCount ? automationActionWebhookRetryCount.value : 0);
const retryCount = Number.isFinite(retryRaw) ? Math.max(0, Math.min(5, Math.floor(retryRaw))) : 0;
nextAction = {
id: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
type: 'webhook',
label,
webhookUrl: url,
method,
headers,
bodyTemplate,
retryCount
};
}
updateAutomation(activeAutomationId, (automation) => {
const actions = Array.isArray(automation.actions) ? automation.actions : [];
actions.push(nextAction);
return { ...automation, actions };
});
activeAutomationActionId = nextAction.id;
setStatus(automationRunStatus, 'Action added.', 'success');
});
}
if (automationActionArgs) {
automationActionArgs.addEventListener('input', function() {
validateJsonField(automationActionArgs, automationRunStatus, 'Action args');
});
automationActionArgs.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionWebhookHeaders) {
automationActionWebhookHeaders.addEventListener('input', function() {
validateJsonField(automationActionWebhookHeaders, automationRunStatus, 'Webhook headers');
});
automationActionWebhookHeaders.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionType) {
automationActionType.addEventListener('change', function() {
updateActionTypeUI();
persistSelectedAutomationActionFromForm();
});
}
if (automationActionLabel) {
automationActionLabel.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionTool) {
automationActionTool.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionWebhookUrl) {
automationActionWebhookUrl.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionWebhookMethod) {
automationActionWebhookMethod.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionWebhookBody) {
automationActionWebhookBody.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionWebhookRetryCount) {
automationActionWebhookRetryCount.addEventListener('change', function() {
persistSelectedAutomationActionFromForm();
});
}
if (automationActionSelect) {
automationActionSelect.addEventListener('change', function() {
activeAutomationActionId = this.value || '';
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const automations = Array.isArray(settings.automations) ? settings.automations : [];
const automation = automations.find((item) => item.id === activeAutomationId);
if (!automation) return;
populateAutomationActionEditor(automation.actions || []);
});
});
}
if (removeAutomationActionBtn) {
removeAutomationActionBtn.addEventListener('click', function() {
if (!activeAutomationId) return;
const id = automationActionSelect ? automationActionSelect.value : '';
if (!id) {
setStatus(automationRunStatus, 'Select an action first.', 'error');
return;
}
updateAutomation(activeAutomationId, (automation) => {
const actions = Array.isArray(automation.actions) ? automation.actions : [];
return { ...automation, actions: actions.filter((action) => action.id !== id) };
});
activeAutomationActionId = '';
});
}
if (standupDiscordTool) {
standupDiscordTool.addEventListener('input', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({
...automation,
standup: { ...automation.standup, discordToolName: this.value.trim() }
}));
});
}
if (standupDiscordArgs) {
standupDiscordArgs.addEventListener('input', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({
...automation,
standup: { ...automation.standup, discordArgsTemplate: this.value }
}));
validateJsonField(standupDiscordArgs, automationRunStatus, 'Standup Discord args');
});
}
if (standupNextcloudTool) {
standupNextcloudTool.addEventListener('input', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({
...automation,
standup: { ...automation.standup, nextcloudToolName: this.value.trim() }
}));
});
}
if (standupNextcloudArgs) {
standupNextcloudArgs.addEventListener('input', function() {
if (!activeAutomationId) return;
updateAutomation(activeAutomationId, (automation) => ({
...automation,
standup: { ...automation.standup, nextcloudArgsTemplate: this.value }
}));
validateJsonField(standupNextcloudArgs, automationRunStatus, 'Standup Nextcloud args');
});
}
if (runAutomationNowBtn) {
runAutomationNowBtn.addEventListener('click', function() {
if (!activeAutomationId) return;
persistCurrentAutomationFromForm();
persistSelectedAutomationActionFromForm();
setStatus(automationRunStatus, 'Running automation...', '');
setOutput(automationOutput, '');
chrome.runtime.sendMessage({ action: 'automation:run', trigger: 'manual', automationId: activeAutomationId }, (response) => {
if (chrome.runtime.lastError || !response) {
setStatus(automationRunStatus, 'Automation failed.', 'error');
return;
}
if (!response.success) {
setStatus(automationRunStatus, response.error || 'Automation failed.', 'error');
return;
}
setStatus(automationRunStatus, 'Automation completed.', 'success');
setOutput(automationOutput, JSON.stringify(response.results || response.result || {}, null, 2));
});
});
}
if (saveAutomationBtn) {
saveAutomationBtn.addEventListener('click', function() {
if (!activeAutomationId) return;
persistCurrentAutomationFromForm();
persistSelectedAutomationActionFromForm();
setStatus(automationRunStatus, 'Automation saved.', 'success');
});
}
if (testAutomationBtn) {
testAutomationBtn.addEventListener('click', function() {
if (!activeAutomationId) return;
persistCurrentAutomationFromForm();
persistSelectedAutomationActionFromForm();
setStatus(automationRunStatus, 'Running test...', '');
setOutput(automationOutput, '');
chrome.runtime.sendMessage(
{ action: 'automation:run', trigger: 'manual', automationId: activeAutomationId, testMode: true },
(response) => {
if (chrome.runtime.lastError || !response) {
setStatus(automationRunStatus, 'Test failed.', 'error');
return;
}
if (!response.success) {
setStatus(automationRunStatus, response.error || 'Test failed.', 'error');
return;
}
setStatus(automationRunStatus, 'Test completed.', 'success');
setOutput(automationOutput, JSON.stringify(response.results || response.result || {}, null, 2));
}
);
});
}
if (deleteAutomationBtn) {
deleteAutomationBtn.addEventListener('click', function() {
if (!activeAutomationId) return;
chrome.storage.sync.get(['advancedSettings'], (result) => {
const settings = { ...DEFAULT_SETTINGS, ...(result.advancedSettings || {}) };
const automations = Array.isArray(settings.automations) ? settings.automations : [];
const nextAutomations = automations.filter((item) => item.id !== activeAutomationId);
settings.automations = nextAutomations;
chrome.storage.sync.set({ advancedSettings: settings }, () => {
activeAutomationId = nextAutomations[0]?.id || null;
hydrateAutomations(nextAutomations);
setStatus(automationRunStatus, 'Automation deleted.', 'success');
setOutput(automationOutput, '');
});
});
});
}
document.querySelectorAll('input[type=\"text\"], textarea').forEach((field) => {
field.addEventListener('focus', () => {
lastFocusedInput = field;
});
});
document.querySelectorAll('.placeholder-chip').forEach((chip) => {
chip.addEventListener('click', async function() {
const placeholder = chip.getAttribute('data-placeholder');
if (!placeholder) return;
if (!lastFocusedInput) {
if (standupDiscordArgs) {
standupDiscordArgs.focus();
lastFocusedInput = standupDiscordArgs;
} else if (standupNextcloudArgs) {
standupNextcloudArgs.focus();
lastFocusedInput = standupNextcloudArgs;
}
}
if (lastFocusedInput && typeof lastFocusedInput.selectionStart === 'number') {
const start = lastFocusedInput.selectionStart;
const end = lastFocusedInput.selectionEnd;
const value = lastFocusedInput.value || '';
lastFocusedInput.value = value.slice(0, start) + placeholder + value.slice(end);
lastFocusedInput.selectionStart = lastFocusedInput.selectionEnd = start + placeholder.length;
lastFocusedInput.dispatchEvent(new Event('input', { bubbles: true }));
setPlaceholderStatus(`Inserted ${placeholder}.`, 'success');
return;
}
try {
await navigator.clipboard.writeText(placeholder);
setPlaceholderStatus(`Copied ${placeholder} to clipboard.`, 'success');
} catch (error) {
setPlaceholderStatus('Failed to copy placeholder.', 'error');
}
});
});
clearAutomationActionForm();
if (closeSettingsBtn) {
closeSettingsBtn.addEventListener('click', function() {
window.close();
});
}
});