From 582b9e10fa29ced68305274304e6ecc891cb4519 Mon Sep 17 00:00:00 2001 From: cirry <812852553@qq.com> Date: Fri, 10 Apr 2026 11:25:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++ .idea/electron-all.iml | 4 +- package-lock.json | 49 +++++++++++++++++++++- package.json | 2 +- src/main/index.js | 56 +++++++++++++++++++++++--- src/preload/index.js | 9 +++++ src/renderer/layouts/DefaultLayout.vue | 10 ++++- src/renderer/router/index.js | 6 +++ 8 files changed, 129 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 8296128..cfcd593 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ yarn-debug.log* yarn-error.log* lerna-debug.log* +.idea +.vite +.vscode + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/.idea/electron-all.iml b/.idea/electron-all.iml index c956989..ae68532 100644 --- a/.idea/electron-all.iml +++ b/.idea/electron-all.iml @@ -1,7 +1,9 @@ - + + + diff --git a/package-lock.json b/package-lock.json index 005f892..1cb4a9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "await-to-js": "^3.0.0", "axios": "^1.13.2", + "bonjour-service": "^1.3.0", "electron-squirrel-startup": "^1.0.1", "element-plus": "^2.13.6", "pinia": "^3.0.4", @@ -2454,6 +2455,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, "node_modules/@listr2/prompt-adapter-inquirer": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.22.tgz", @@ -4137,6 +4144,16 @@ "dev": true, "license": "MIT" }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -5025,6 +5042,18 @@ "p-limit": "^3.1.0 " } }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -6504,7 +6533,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -8832,6 +8860,19 @@ "dev": true, "license": "MIT" }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, "node_modules/mute-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", @@ -10574,6 +10615,12 @@ "dev": true, "license": "MIT" }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, "node_modules/tiny-each-async": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz", diff --git a/package.json b/package.json index 92070c5..f2bb6ae 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "dependencies": { "await-to-js": "^3.0.0", "axios": "^1.13.2", + "bonjour-service": "^1.3.0", "electron-squirrel-startup": "^1.0.1", "element-plus": "^2.13.6", "pinia": "^3.0.4", @@ -52,7 +53,6 @@ }, "lint-staged": { "*.{js,vue}": [ - "eslint --fix", "prettier --write" ] } diff --git a/src/main/index.js b/src/main/index.js index 88e0f2c..967d78d 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -4,9 +4,43 @@ import fs from 'node:fs'; import net from 'node:net'; import { spawn } from 'node:child_process'; import started from 'electron-squirrel-startup'; +import { Bonjour } from 'bonjour-service'; if (started) app.quit(); +// ========== Bonjour 服务发现 ========== +const bonjour = new Bonjour(); +let discoveredServices = new Map(); + +function getDiscoveredServices() { + return Array.from(discoveredServices.values()); +} + +function startBonjourDiscovery() { + const browser = bonjour.find({}); + + browser.on('up', (service) => { + console.log('[bonjour] Service up:', service.name); + discoveredServices.set(service.fqdn, service); + notifyServicesChanged(); + }); + + browser.on('down', (service) => { + console.log('[bonjour] Service down:', service.name); + discoveredServices.delete(service.fqdn); + notifyServicesChanged(); + }); + + return browser; +} + +function notifyServicesChanged() { + const services = getDiscoveredServices(); + BrowserWindow.getAllWindows().forEach((win) => { + win.webContents.send('bonjour:services-updated', services); + }); +} + // ========== OpenCode 服务管理 ========== const DEFAULT_PORT = 4096; let opencodeProcess = null; @@ -36,7 +70,10 @@ function waitForReady(port, timeout = 15000) { const start = Date.now(); const check = () => { const socket = net.createConnection({ port, host: '127.0.0.1' }); - socket.once('connect', () => { socket.end(); resolve(); }); + socket.once('connect', () => { + socket.end(); + resolve(); + }); socket.once('error', () => { socket.destroy(); if (Date.now() - start >= timeout) return reject(new Error('OpenCode 服务启动超时')); @@ -92,7 +129,9 @@ async function startOpencode() { }); opencodeProcess.stdout?.on('data', (d) => console.log(`[opencode] ${d.toString().trim()}`)); - opencodeProcess.stderr?.on('data', (d) => console.error(`[opencode error] ${d.toString().trim()}`)); + opencodeProcess.stderr?.on('data', (d) => + console.error(`[opencode error] ${d.toString().trim()}`) + ); opencodeProcess.once('error', (e) => console.error('[opencode spawn error]', e)); opencodeProcess.once('close', (code) => { console.log(`[opencode exited] code=${code}`); @@ -126,7 +165,10 @@ function stopOpencode() { // ========== IPC Handlers ========== function registerIpcHandlers() { ipcMain.handle('opencode:start', () => startOpencode()); - ipcMain.handle('opencode:stop', () => { stopOpencode(); return buildInfo(); }); + ipcMain.handle('opencode:stop', () => { + stopOpencode(); + return buildInfo(); + }); ipcMain.handle('opencode:info', () => buildInfo()); ipcMain.handle('opencode:port', () => opencodePort); @@ -158,6 +200,9 @@ function registerIpcHandlers() { if (!res.ok) throw new Error(`发送消息失败: ${res.status}`); return res.json(); }); + + // Bonjour + ipcMain.handle('bonjour:get-services', () => getDiscoveredServices()); } // ========== 窗口 ========== @@ -195,14 +240,13 @@ const createWindow = () => { if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { - mainWindow.loadFile( - path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`) - ); + mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); } }; app.whenReady().then(() => { registerIpcHandlers(); + startBonjourDiscovery(); createWindow(); app.on('activate', () => { diff --git a/src/preload/index.js b/src/preload/index.js index 31d970a..a4e752b 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -16,3 +16,12 @@ contextBridge.exposeInMainWorld('opencode', { createSession: (data) => ipcRenderer.invoke('opencode:session:create', data), sendMessage: (sessionId, text) => ipcRenderer.invoke('opencode:session:send', sessionId, text), }); + +contextBridge.exposeInMainWorld('bonjour', { + getServices: () => ipcRenderer.invoke('bonjour:get-services'), + onServicesUpdated: (callback) => { + const listener = (_event, services) => callback(services); + ipcRenderer.on('bonjour:services-updated', listener); + return () => ipcRenderer.removeListener('bonjour:services-updated', listener); + }, +}); diff --git a/src/renderer/layouts/DefaultLayout.vue b/src/renderer/layouts/DefaultLayout.vue index e328624..fff0dc8 100644 --- a/src/renderer/layouts/DefaultLayout.vue +++ b/src/renderer/layouts/DefaultLayout.vue @@ -31,6 +31,10 @@ + + + + @@ -47,7 +51,9 @@
-
+

{{ currentTitle }}

U @@ -68,7 +74,7 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useAppStore } from '@/stores/app'; -import { House, Monitor, Expand, Fold, Edit, ChatDotRound } from '@element-plus/icons-vue'; +import { House, Monitor, Expand, Fold, Edit, ChatDotRound, Search } from '@element-plus/icons-vue'; const route = useRoute(); const appStore = useAppStore(); diff --git a/src/renderer/router/index.js b/src/renderer/router/index.js index c4639ff..cfc857c 100644 --- a/src/renderer/router/index.js +++ b/src/renderer/router/index.js @@ -17,6 +17,12 @@ const routes = [ component: () => import('@/views/chat/ChatView.vue'), meta: { title: 'OpenCode 对话' }, }, + { + path: '/bonjour', + name: 'Bonjour', + component: () => import('@/views/bonjour/BonjourView.vue'), + meta: { title: '发现设备' }, + }, ], }, ];