feat: 添加发现设备页面
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,6 +6,10 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
|
.idea
|
||||||
|
.vite
|
||||||
|
.vscode
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
|||||||
4
.idea/electron-all.iml
generated
4
.idea/electron-all.iml
generated
@@ -1,7 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="WEB_MODULE" version="4">
|
<module type="WEB_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.vite" />
|
||||||
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
49
package-lock.json
generated
49
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"await-to-js": "^3.0.0",
|
"await-to-js": "^3.0.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"bonjour-service": "^1.3.0",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"element-plus": "^2.13.6",
|
"element-plus": "^2.13.6",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
@@ -2454,6 +2455,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@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": {
|
"node_modules/@listr2/prompt-adapter-inquirer": {
|
||||||
"version": "2.0.22",
|
"version": "2.0.22",
|
||||||
"resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.22.tgz",
|
"resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-2.0.22.tgz",
|
||||||
@@ -4137,6 +4144,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/boolean": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
||||||
@@ -5025,6 +5042,18 @@
|
|||||||
"p-limit": "^3.1.0 "
|
"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": {
|
"node_modules/dot-prop": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
|
||||||
@@ -6504,7 +6533,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-diff": {
|
"node_modules/fast-diff": {
|
||||||
@@ -8832,6 +8860,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/mute-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
|
||||||
@@ -10574,6 +10615,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/tiny-each-async": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"await-to-js": "^3.0.0",
|
"await-to-js": "^3.0.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"bonjour-service": "^1.3.0",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"element-plus": "^2.13.6",
|
"element-plus": "^2.13.6",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
@@ -52,7 +53,6 @@
|
|||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,vue}": [
|
"*.{js,vue}": [
|
||||||
"eslint --fix",
|
|
||||||
"prettier --write"
|
"prettier --write"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,43 @@ import fs from 'node:fs';
|
|||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import started from 'electron-squirrel-startup';
|
import started from 'electron-squirrel-startup';
|
||||||
|
import { Bonjour } from 'bonjour-service';
|
||||||
|
|
||||||
if (started) app.quit();
|
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 服务管理 ==========
|
// ========== OpenCode 服务管理 ==========
|
||||||
const DEFAULT_PORT = 4096;
|
const DEFAULT_PORT = 4096;
|
||||||
let opencodeProcess = null;
|
let opencodeProcess = null;
|
||||||
@@ -36,7 +70,10 @@ function waitForReady(port, timeout = 15000) {
|
|||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const check = () => {
|
const check = () => {
|
||||||
const socket = net.createConnection({ port, host: '127.0.0.1' });
|
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.once('error', () => {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
if (Date.now() - start >= timeout) return reject(new Error('OpenCode 服务启动超时'));
|
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.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('error', (e) => console.error('[opencode spawn error]', e));
|
||||||
opencodeProcess.once('close', (code) => {
|
opencodeProcess.once('close', (code) => {
|
||||||
console.log(`[opencode exited] code=${code}`);
|
console.log(`[opencode exited] code=${code}`);
|
||||||
@@ -126,7 +165,10 @@ function stopOpencode() {
|
|||||||
// ========== IPC Handlers ==========
|
// ========== IPC Handlers ==========
|
||||||
function registerIpcHandlers() {
|
function registerIpcHandlers() {
|
||||||
ipcMain.handle('opencode:start', () => startOpencode());
|
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:info', () => buildInfo());
|
||||||
ipcMain.handle('opencode:port', () => opencodePort);
|
ipcMain.handle('opencode:port', () => opencodePort);
|
||||||
|
|
||||||
@@ -158,6 +200,9 @@ function registerIpcHandlers() {
|
|||||||
if (!res.ok) throw new Error(`发送消息失败: ${res.status}`);
|
if (!res.ok) throw new Error(`发送消息失败: ${res.status}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bonjour
|
||||||
|
ipcMain.handle('bonjour:get-services', () => getDiscoveredServices());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 窗口 ==========
|
// ========== 窗口 ==========
|
||||||
@@ -195,14 +240,13 @@ const createWindow = () => {
|
|||||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.loadFile(
|
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));
|
||||||
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
registerIpcHandlers();
|
registerIpcHandlers();
|
||||||
|
startBonjourDiscovery();
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
|
|||||||
@@ -16,3 +16,12 @@ contextBridge.exposeInMainWorld('opencode', {
|
|||||||
createSession: (data) => ipcRenderer.invoke('opencode:session:create', data),
|
createSession: (data) => ipcRenderer.invoke('opencode:session:create', data),
|
||||||
sendMessage: (sessionId, text) => ipcRenderer.invoke('opencode:session:send', sessionId, text),
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -31,6 +31,10 @@
|
|||||||
<el-icon><ChatDotRound /></el-icon>
|
<el-icon><ChatDotRound /></el-icon>
|
||||||
<template #title>OpenCode 对话</template>
|
<template #title>OpenCode 对话</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/bonjour">
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
<template #title>发现设备</template>
|
||||||
|
</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
|
|
||||||
<!-- 折叠按钮 -->
|
<!-- 折叠按钮 -->
|
||||||
@@ -47,7 +51,9 @@
|
|||||||
<!-- 主内容区 -->
|
<!-- 主内容区 -->
|
||||||
<div class="flex flex-col flex-1 overflow-hidden">
|
<div class="flex flex-col flex-1 overflow-hidden">
|
||||||
<!-- 顶部栏 -->
|
<!-- 顶部栏 -->
|
||||||
<header class="flex items-center justify-between h-14 px-6 bg-white border-b border-gray-200 shrink-0">
|
<header
|
||||||
|
class="flex items-center justify-between h-14 px-6 bg-white border-b border-gray-200 shrink-0"
|
||||||
|
>
|
||||||
<h1 class="text-base font-medium text-gray-700">{{ currentTitle }}</h1>
|
<h1 class="text-base font-medium text-gray-700">{{ currentTitle }}</h1>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<el-avatar :size="32" class="bg-blue-500">U</el-avatar>
|
<el-avatar :size="32" class="bg-blue-500">U</el-avatar>
|
||||||
@@ -68,7 +74,7 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useAppStore } from '@/stores/app';
|
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 route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ const routes = [
|
|||||||
component: () => import('@/views/chat/ChatView.vue'),
|
component: () => import('@/views/chat/ChatView.vue'),
|
||||||
meta: { title: 'OpenCode 对话' },
|
meta: { title: 'OpenCode 对话' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/bonjour',
|
||||||
|
name: 'Bonjour',
|
||||||
|
component: () => import('@/views/bonjour/BonjourView.vue'),
|
||||||
|
meta: { title: '发现设备' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user