commit 3ab976b930f128be4c4f48db8a03a71b91fa10ec Author: 雨霖铃 Date: Tue Nov 11 01:07:43 2025 +0800 feat(extension): 初始化Chrome扩展项目结构与基础功能 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93d7e2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Dependencies +node_modules/ + +# Build outputs +dist/ + +# Environment variables +.env + +# IDE files +.vscode/ +.idea/ + +# OS generated files +.DS_Store +Thumbs.db + +# Log files +*.log + +# Package files +*.zip +*.tar.gz \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e10c80f --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Using the Gemini API in a Chrome Extension. + +This sample demonstrates how to use the Gemini Cloud API in a Chrome Extension. + +## Overview + +The extension provides a chat interface for the Gemini API. To learn more about the API head over to [https://ai.google.dev/](https://ai.google.dev/). + +## Running this extension + +1. Clone this repository. +2. Download the Gemini API client by running: + ```sh + npm install + ``` +3. [Retrieve an API key](https://ai.google.dev/gemini-api/docs/api-key) and update [functional-samples/ai.gemini-in-the-cloud/sidepanel/index.js](functional-samples/ai.gemini-in-the-cloud/sidepanel/index.js) (only for testing). +4. Compile the JS bundle for the sidepanel implementation by running: + ```sh + npm run build + ``` +5. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked). +6. Click the extension icon. +7. Interact with the prompt API in the sidebar. diff --git a/background.js b/background.js new file mode 100644 index 0000000..3e05c8f --- /dev/null +++ b/background.js @@ -0,0 +1,6 @@ +chrome.action.onClicked.addListener(async (tab) => { + await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + files: ["content.js"] + }); +}); diff --git a/content.js b/content.js new file mode 100644 index 0000000..b588763 --- /dev/null +++ b/content.js @@ -0,0 +1,85 @@ +(() => { + const SIDEBAR_ID = 'floating-sidebar'; + const TOGGLE_ID = 'floating-toggle-btn'; + + // 若侧边栏已存在则移除 + const existing = document.getElementById(SIDEBAR_ID); + const existingBtn = document.getElementById(TOGGLE_ID); + if (existing && existingBtn) { + existing.remove(); + existingBtn.remove(); + return; + } + + // 创建 iframe + const iframe = document.createElement('iframe'); + iframe.id = SIDEBAR_ID; + iframe.src = chrome.runtime.getURL('sidepanel/index.html'); + + Object.assign(iframe.style, { + position: 'fixed', + top: '0', + right: '0', + width: '360px', + height: '100%', + border: 'none', + zIndex: '2147483647', + boxShadow: '-4px 0 8px rgba(0,0,0,0.15)', + backgroundColor: 'white', + transform: 'translateX(0)', + transition: 'transform 0.3s ease', + }); + + document.body.appendChild(iframe); + + // 创建控制按钮 + const toggleBtn = document.createElement('div'); + toggleBtn.id = TOGGLE_ID; + toggleBtn.textContent = '≡'; + Object.assign(toggleBtn.style, { + position: 'fixed', + bottom: '20px', + right: '20px', + width: '48px', + height: '48px', + backgroundColor: '#0078d7', + color: 'white', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '22px', + cursor: 'pointer', + boxShadow: '0 2px 6px rgba(0,0,0,0.3)', + zIndex: '2147483648', + transition: 'background-color 0.2s ease', + }); + + toggleBtn.addEventListener('mouseenter', () => { + toggleBtn.style.backgroundColor = '#005fa3'; + }); + toggleBtn.addEventListener('mouseleave', () => { + toggleBtn.style.backgroundColor = '#0078d7'; + }); + + document.body.appendChild(toggleBtn); + + // 折叠 / 展开逻辑 + let visible = true; + toggleBtn.addEventListener('click', () => { + visible = !visible; + iframe.style.transform = visible ? 'translateX(0)' : 'translateX(100%)'; + }); + // 如果使用 window.postMessage + window.addEventListener('message', function (event) { + // 检查消息来源是否可信 (非常重要!) + if (event.origin !== 'chrome-extension://njacfkelcmfnpdbnbkpenogmokmglkcn') { + return; + } + if (event.data.action === 'closeSidebar') { + console.log('Received closeSidebar message'); + visible = !visible; + iframe.style.transform = visible ? 'translateX(0)' : 'translateX(100%)'; + } + }); +})(); diff --git a/images/icon128.png b/images/icon128.png new file mode 100644 index 0000000..7879061 Binary files /dev/null and b/images/icon128.png differ diff --git a/images/icon16.png b/images/icon16.png new file mode 100644 index 0000000..a8eb458 Binary files /dev/null and b/images/icon16.png differ diff --git a/images/icon32.png b/images/icon32.png new file mode 100644 index 0000000..838c426 Binary files /dev/null and b/images/icon32.png differ diff --git a/images/icon48.png b/images/icon48.png new file mode 100644 index 0000000..932bdfe Binary files /dev/null and b/images/icon48.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..68e5b5d --- /dev/null +++ b/manifest.json @@ -0,0 +1,25 @@ +{ + "manifest_version": 3, + "name": "Floating Sidebar with Toggle Button", + "version": "1.1", + "description": "带有可折叠隐藏按钮的浮动侧边栏", + "permissions": ["activeTab", "scripting"], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_title": "打开侧边栏" + }, + "web_accessible_resources": [ + { + "resources": ["sidepanel/*"], + "matches": [""] + } + ], + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..df1ad3c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,288 @@ +{ + "name": "Chrome Extensions Gemini Demo", + "version": "1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "Chrome Extensions Gemini Demo", + "version": "1.0", + "devDependencies": { + "@google/generative-ai": "0.15.0", + "rollup": "4.22.4" + } + }, + "node_modules/@google/generative-ai": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.15.0.tgz", + "integrity": "sha512-zs37judcTYFJf1U7tnuqnh7gdzF6dcWj9pNRxjA5JTONRoiQ0htrRdbefRFiewOIfXwhun5t9hbd2ray7812eQ==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c6c3c3c --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "Chrome Extensions Gemini Demo", + "version": "1.0", + "scripts": { + "build": "rollup sidepanel/index.js --file dist/sidepanel.bundle.js --format iife" + }, + "private": true, + "devDependencies": { + "@google/generative-ai": "0.15.0", + "rollup": "4.22.4" + } +} diff --git a/sidepanel/index.css b/sidepanel/index.css new file mode 100644 index 0000000..818edbb --- /dev/null +++ b/sidepanel/index.css @@ -0,0 +1,48 @@ +/* sidepanel/style.css */ +.navbar { + display: flex; + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; + padding: 0.5rem; +} + +.nav-button { + flex: 1; + padding: 0.5rem 1rem; + background-color: transparent; + border: none; + cursor: pointer; + text-align: center; + transition: background-color 0.2s; +} + +.nav-button:hover { + background-color: #e9ecef; +} + +.nav-button.active { + background-color: #007bff; + color: white; +} + +.close-button { + padding: 0.25rem 0.75rem; + background-color: #dc3545; + color: white; + border: none; + border-radius: 0.25rem; + cursor: pointer; + margin-left: auto; +} + +.content { + padding: 1rem; +} + +.page { + display: none; +} + +.page.active { + display: block; +} \ No newline at end of file diff --git a/sidepanel/index.html b/sidepanel/index.html new file mode 100644 index 0000000..2758aa0 --- /dev/null +++ b/sidepanel/index.html @@ -0,0 +1,150 @@ + + + + + + + 测试工具 + + + +
+ + + + +
+ +
+
+

当前时间戳

+

+ 1762793622638 + 毫秒 +

+
+ + + +
+
+ +
+
+ + 时间戳转换 +
+
+ + + +
+
+ +
+
+
+ + + + Asia/Shanghai +
+ +
+ +
+
+ +
+
+ + 日期时间转换 +
+
+ +
+
+
+ + + + Asia/Shanghai +
+ +
+ +
+
+ + + +
+
+
+ + +
+

其他工具页面

+

这里可以放置其他工具的内容。

+
+
+
+ + + + \ No newline at end of file diff --git a/sidepanel/index.js b/sidepanel/index.js new file mode 100644 index 0000000..158f5e9 --- /dev/null +++ b/sidepanel/index.js @@ -0,0 +1,37 @@ +document.getElementById('close').addEventListener('click', () => { + window.parent.postMessage({ action: 'indexind' }, '*'); +}); + +// sidepanel/index.js +// 页面切换逻辑 +document.getElementById("nav-timestamp").addEventListener("click", () => { + switchPage("timestamp"); +}); + +document.getElementById("nav-other").addEventListener("click", () => { + switchPage("other"); +}); + +function switchPage(pageName) { + // 隐藏所有页面 + document.querySelectorAll(".page").forEach(page => { + page.classList.remove("active"); + }); + + // 移除所有导航按钮的 active 状态 + document.querySelectorAll(".nav-button").forEach(button => { + button.classList.remove("active"); + }); + + // 显示目标页面 + document.getElementById(`page-${pageName}`).classList.add("active"); + + // 激活对应的导航按钮 + document.getElementById(`nav-${pageName}`).classList.add("active"); +} + +// 关闭按钮逻辑 (使用消息传递) +document.getElementById("close").addEventListener("click", () => { + // 向父页面发送消息请求关闭 + window.parent.postMessage({ action: "closeSidebar" }, "*"); +}); \ No newline at end of file