mirror of
https://github.com/aaronjmars/opendia.git
synced 2025-12-29 16:16:00 +00:00
Update to version 1.0.6 with Firefox support and improved extension management
- Add Firefox extension support with Manifest V2 - Update all version numbers to 1.0.6 - Build Chrome and Firefox extension packages - Clean up extension directory structure - Update README with Firefox installation instructions - Update server messages to mention Chrome/Firefox support - Add .gitignore for extension directory - Create release packages for both browsers 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2047
opendia-extension/src/background/background.js
Normal file
2047
opendia-extension/src/background/background.js
Normal file
File diff suppressed because it is too large
Load Diff
2897
opendia-extension/src/content/content.js
Normal file
2897
opendia-extension/src/content/content.js
Normal file
File diff suppressed because it is too large
Load Diff
8
opendia-extension/src/polyfill/browser-polyfill.min.js
vendored
Normal file
8
opendia-extension/src/polyfill/browser-polyfill.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
375
opendia-extension/src/popup/popup.html
Normal file
375
opendia-extension/src/popup/popup.html
Normal file
@@ -0,0 +1,375 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>OpenDia</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--success-color: #22c55e;
|
||||
--error-color: #ef4444;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #1f2937;
|
||||
--border-color: #e5e7eb;
|
||||
--hover-color: #1d4ed8;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 380px;
|
||||
padding: 20px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
color: var(--text-color);
|
||||
background: linear-gradient(135deg, #0081F7 0%, #FF75CA 50%, #FFAE87 100%);
|
||||
margin: 0;
|
||||
min-height: 300px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(20px);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
body > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 12px;
|
||||
background: linear-gradient(135deg, #0081F7, #FF75CA);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 8px rgba(0, 129, 247, 0.3);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logo video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 12px;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.connected {
|
||||
background-color: var(--success-color);
|
||||
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
.disconnected {
|
||||
background-color: var(--error-color);
|
||||
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.info {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
padding: 16px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
cursor: help;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 2px;
|
||||
text-decoration-color: #9ca3af;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(-5px);
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
color: white;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: normal;
|
||||
white-space: pre-wrap;
|
||||
width: 320px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.info-row .tooltip .tooltip-content {
|
||||
left: -350px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltip-content {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
button {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
color: #0081F7;
|
||||
border: 1px solid rgba(0, 129, 247, 0.2);
|
||||
padding: 10px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
transition: all 0.3s ease;
|
||||
flex: 1;
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 117, 202, 0.3), transparent);
|
||||
transition: left 0.6s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-color: rgba(255, 117, 202, 0.4);
|
||||
box-shadow: 0 6px 20px rgba(255, 117, 202, 0.2);
|
||||
}
|
||||
|
||||
button:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.safety-mode {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
padding: 16px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.safety-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.safety-label {
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.safety-toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.safety-toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.safety-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #e5e7eb;
|
||||
transition: 0.3s;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(0, 129, 247, 0.2);
|
||||
}
|
||||
|
||||
.safety-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
background-color: white;
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
input:checked + .safety-slider {
|
||||
background: linear-gradient(135deg, #0081F7, #1d4ed8);
|
||||
border-color: rgba(0, 129, 247, 0.4);
|
||||
}
|
||||
|
||||
input:checked + .safety-slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.safety-slider:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="logo">
|
||||
<video autoplay loop muted playsinline>
|
||||
<source src="../../logo.webm" type="video/webm">
|
||||
<source src="../../logo.mp4" type="video/mp4">
|
||||
<span>OD</span>
|
||||
</video>
|
||||
</div>
|
||||
<h2>OpenDia</h2>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
<div class="status-indicator" id="statusIndicator"></div>
|
||||
<span id="statusText" class="tooltip">
|
||||
Checking connection...
|
||||
<span class="tooltip-content">
|
||||
Start server with: npx opendia
|
||||
Auto-discovery will find the correct ports.
|
||||
Existing processes are automatically terminated on startup
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Server</span>
|
||||
<span class="info-value" id="serverUrl">Auto-Discovery</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Available Tools</span>
|
||||
<span class="info-value" id="toolCount">Loading...</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Current Page</span>
|
||||
<span class="info-value" id="currentPage">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="safety-mode">
|
||||
<div class="safety-row">
|
||||
<span class="safety-label tooltip">
|
||||
Safety Mode
|
||||
<span class="tooltip-content">
|
||||
When enabled, blocks write/edit tools: element_click, element_fill
|
||||
</span>
|
||||
</span>
|
||||
<label class="safety-toggle">
|
||||
<input type="checkbox" id="safetyMode">
|
||||
<span class="safety-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="reconnectBtn">Reconnect</button>
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
179
opendia-extension/src/popup/popup.js
Normal file
179
opendia-extension/src/popup/popup.js
Normal file
@@ -0,0 +1,179 @@
|
||||
// OpenDia Popup
|
||||
// Import WebExtension polyfill for cross-browser compatibility
|
||||
if (typeof browser === 'undefined' && typeof chrome !== 'undefined') {
|
||||
globalThis.browser = chrome;
|
||||
}
|
||||
|
||||
// Cross-browser compatibility layer
|
||||
const runtimeAPI = browser.runtime;
|
||||
const tabsAPI = browser.tabs;
|
||||
const storageAPI = browser.storage;
|
||||
let statusIndicator = document.getElementById("statusIndicator");
|
||||
let statusText = document.getElementById("statusText");
|
||||
let toolCount = document.getElementById("toolCount");
|
||||
let currentPage = document.getElementById("currentPage");
|
||||
let serverUrl = document.getElementById("serverUrl");
|
||||
|
||||
// Get dynamic tool count from background script
|
||||
function updateToolCount() {
|
||||
const toolsList = [
|
||||
"page_analyze", "page_extract_content", "element_click", "element_fill",
|
||||
"element_get_state", "page_navigate", "page_wait_for", "page_scroll",
|
||||
"tab_create", "tab_close", "tab_list", "tab_switch",
|
||||
"get_bookmarks", "add_bookmark", "get_history", "get_selected_text", "get_page_links"
|
||||
];
|
||||
|
||||
if (runtimeAPI?.id) {
|
||||
runtimeAPI.sendMessage({ action: "getToolCount" }, (response) => {
|
||||
if (!runtimeAPI.lastError && response?.toolCount) {
|
||||
toolCount.innerHTML = `<span class="tooltip">${response.toolCount}
|
||||
<span class="tooltip-content">Available MCP Tools:\npage_analyze • page_extract_content • element_click • element_fill • element_get_state • page_navigate • page_wait_for • page_scroll • tab_create • tab_close • tab_list • tab_switch • get_bookmarks • add_bookmark • get_history • get_selected_text • get_page_links</span>
|
||||
</span>`;
|
||||
} else {
|
||||
// Fallback to calculating from background script
|
||||
toolCount.innerHTML = `<span class="tooltip">17
|
||||
<span class="tooltip-content">Available MCP Tools:\npage_analyze • page_extract_content • element_click • element_fill • element_get_state • page_navigate • page_wait_for • page_scroll • tab_create • tab_close • tab_list • tab_switch • get_bookmarks • add_bookmark • get_history • get_selected_text • get_page_links</span>
|
||||
</span>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check connection status and get page info
|
||||
function checkStatus() {
|
||||
if (runtimeAPI?.id) {
|
||||
runtimeAPI.sendMessage({ action: "getStatus" }, (response) => {
|
||||
if (runtimeAPI.lastError) {
|
||||
updateStatus(false);
|
||||
} else {
|
||||
updateStatus(response?.connected || false);
|
||||
}
|
||||
});
|
||||
|
||||
// Update tool count
|
||||
updateToolCount();
|
||||
|
||||
// Get current page info
|
||||
tabsAPI.query({active: true, currentWindow: true}, (tabs) => {
|
||||
if (tabs[0]) {
|
||||
const url = new URL(tabs[0].url);
|
||||
currentPage.textContent = url.hostname;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
updateStatus(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Check status on load and periodically
|
||||
checkStatus();
|
||||
setInterval(checkStatus, 2000);
|
||||
|
||||
// Update server URL display
|
||||
function updateServerUrl() {
|
||||
if (runtimeAPI?.id) {
|
||||
runtimeAPI.sendMessage({ action: "getPorts" }, (response) => {
|
||||
if (!runtimeAPI.lastError && response?.websocketUrl) {
|
||||
serverUrl.textContent = response.websocketUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update server URL periodically
|
||||
updateServerUrl();
|
||||
setInterval(updateServerUrl, 5000);
|
||||
|
||||
// Update UI based on connection status
|
||||
function updateStatus(connected) {
|
||||
if (connected) {
|
||||
statusIndicator.className = "status-indicator connected";
|
||||
statusText.innerHTML = `Connected to MCP server
|
||||
<span class="tooltip-content">Connected successfully! Server auto-discovery is working. Default ports: WebSocket=5555, HTTP=5556</span>`;
|
||||
} else {
|
||||
statusIndicator.className = "status-indicator disconnected";
|
||||
statusText.innerHTML = `Disconnected from MCP server
|
||||
<span class="tooltip-content">Start server with: npx opendia. Auto-discovery will find the correct ports. Existing processes are automatically terminated on startup</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Reconnect button
|
||||
document.getElementById("reconnectBtn").addEventListener("click", () => {
|
||||
if (runtimeAPI?.id) {
|
||||
runtimeAPI.sendMessage({ action: "reconnect" }, (response) => {
|
||||
if (!runtimeAPI.lastError) {
|
||||
setTimeout(checkStatus, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Safety Mode Management
|
||||
const safetyModeToggle = document.getElementById("safetyMode");
|
||||
|
||||
// Load safety mode state from storage
|
||||
storageAPI.local.get(['safetyMode'], (result) => {
|
||||
const safetyEnabled = result.safetyMode || false; // Default to false (safety off)
|
||||
safetyModeToggle.checked = safetyEnabled;
|
||||
});
|
||||
|
||||
// Handle safety mode toggle changes
|
||||
safetyModeToggle.addEventListener('change', () => {
|
||||
const safetyEnabled = safetyModeToggle.checked;
|
||||
|
||||
// Save to storage
|
||||
storageAPI.local.set({ safetyMode: safetyEnabled });
|
||||
|
||||
// Notify background script
|
||||
runtimeAPI.sendMessage({
|
||||
action: "setSafetyMode",
|
||||
enabled: safetyEnabled
|
||||
});
|
||||
});
|
||||
|
||||
// Listen for updates from background script
|
||||
runtimeAPI.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === "statusUpdate") {
|
||||
updateStatus(message.connected);
|
||||
}
|
||||
});
|
||||
|
||||
// Video speed control based on mouse movement
|
||||
const logoVideo = document.querySelector('.logo video');
|
||||
let mouseTimeout;
|
||||
let lastMouseX = 0;
|
||||
let lastMouseY = 0;
|
||||
let mouseSpeed = 0;
|
||||
|
||||
if (logoVideo) {
|
||||
// Track mouse movement and calculate speed
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
const deltaX = e.clientX - lastMouseX;
|
||||
const deltaY = e.clientY - lastMouseY;
|
||||
|
||||
// Calculate mouse speed (distance moved)
|
||||
mouseSpeed = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
// Update video playback rate based on mouse speed
|
||||
// Faster mouse = faster video (clamped between 0.2x and 10x)
|
||||
// Very sensitive to mouse movement (divided by 15 for more responsiveness)
|
||||
const playbackRate = Math.min(10, Math.max(0.2, 1 + (mouseSpeed / 15)));
|
||||
logoVideo.playbackRate = playbackRate;
|
||||
|
||||
lastMouseX = e.clientX;
|
||||
lastMouseY = e.clientY;
|
||||
|
||||
// Clear existing timeout
|
||||
clearTimeout(mouseTimeout);
|
||||
|
||||
// Reset to normal speed after mouse stops
|
||||
mouseTimeout = setTimeout(() => {
|
||||
logoVideo.playbackRate = 1;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Set initial playback rate
|
||||
logoVideo.playbackRate = 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user