- 新增 electron/main.js: 主进程(窗口管理、系统托盘、原生菜单、IPC) - 新增 electron/preload.js: contextBridge 安全 API 暴露 - 新增 electron/desktop-bridge.js: 渲染进程桥接层 + Web 降级 - 新增 electron/desktop-integration.js: 菜单/托盘事件响应、原生文件对话框 - 更新 index.html: 集成桌面脚本 - 新增 package.json: Electron + electron-builder 配置 - 新增 DESKTOP_README.md: 桌面版开发文档 - 支持: NSIS 安装包 + 便携版、系统托盘、原生菜单、窗口记忆 - 安全: contextIsolation + IPC 白名单、单实例锁
122 lines
4.2 KiB
JavaScript
122 lines
4.2 KiB
JavaScript
/**
|
|
* Metona Ollama Desktop - 桌面端集成
|
|
*
|
|
* 在 DOM 加载后执行,为现有 Web 应用添加桌面特性:
|
|
* 1. 主进程菜单/托盘事件响应
|
|
* 2. 原生文件对话框替换浏览器 input[type=file]
|
|
* 3. 桌面端快捷键增强
|
|
* 4. 标题栏集成
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
const bridge = window.__metonaBridge;
|
|
if (!bridge || !bridge.isDesktop) {
|
|
// 非桌面环境,跳过
|
|
return;
|
|
}
|
|
|
|
console.log('[Desktop Integration] 初始化桌面集成...');
|
|
|
|
// ── 1. 菜单/托盘事件响应 ──
|
|
bridge.onMenuAction((action) => {
|
|
switch (action) {
|
|
case 'new-chat':
|
|
document.querySelector('#btnNewChat')?.click();
|
|
break;
|
|
case 'import':
|
|
document.querySelector('#btnImportSessions')?.click();
|
|
break;
|
|
case 'export':
|
|
document.querySelector('#btnExportAll')?.click();
|
|
break;
|
|
case 'help':
|
|
document.querySelector('#btnHelp')?.click();
|
|
break;
|
|
}
|
|
});
|
|
|
|
bridge.onTrayAction((action) => {
|
|
switch (action) {
|
|
case 'new-chat':
|
|
document.querySelector('#btnNewChat')?.click();
|
|
break;
|
|
}
|
|
});
|
|
|
|
// ── 2. 增强文件上传 - 使用原生文件对话框 ──
|
|
function setupNativeFilePicker(btnId, inputId, fileFilters) {
|
|
const btn = document.querySelector(btnId);
|
|
const input = document.querySelector(inputId);
|
|
if (!btn || !input) return;
|
|
|
|
// 拦截点击事件,使用原生对话框
|
|
btn.addEventListener('click', async (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const filePaths = await bridge.openFile(fileFilters);
|
|
if (!filePaths || filePaths.length === 0) return;
|
|
|
|
// 读取所有文件
|
|
for (const filePath of filePaths) {
|
|
const result = await bridge.readFile(filePath);
|
|
if (result.success) {
|
|
// 创建 File 对象注入到原有流程
|
|
const blob = new Blob([result.content], { type: 'text/plain' });
|
|
const file = new File([blob], result.name, { type: 'text/plain' });
|
|
|
|
// 使用 DataTransfer 注入文件到 input,触发 change 事件
|
|
const dt = new DataTransfer();
|
|
dt.items.add(file);
|
|
input.files = dt.files;
|
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
}
|
|
}, true); // capture phase, 优先于原有事件
|
|
}
|
|
|
|
// DOM 就绪后设置
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', setupEnhancements);
|
|
} else {
|
|
setupEnhancements();
|
|
}
|
|
|
|
function setupEnhancements() {
|
|
// 代码/文本文件上传
|
|
setupNativeFilePicker(
|
|
'#btnAttachFile',
|
|
'#textFileInput',
|
|
[
|
|
{ name: '代码文件', extensions: ['py', 'js', 'ts', 'tsx', 'java', 'go', 'rs', 'cpp', 'c', 'h', 'rb', 'php', 'sh', 'swift', 'kt', 'lua'] },
|
|
{ name: '配置文件', extensions: ['json', 'yaml', 'yml', 'toml', 'xml', 'ini', 'conf'] },
|
|
{ name: '文档', extensions: ['txt', 'md', 'log', 'csv', 'html', 'css'] },
|
|
{ name: '所有文件', extensions: ['*'] }
|
|
]
|
|
);
|
|
}
|
|
|
|
// ── 3. 标题更新 - 显示连接/模型状态 ──
|
|
const origTitle = document.title;
|
|
window.addEventListener('metona:model-changed', (e) => {
|
|
if (e.detail?.model) {
|
|
document.title = `${e.detail.model} - Metona Ollama`;
|
|
} else {
|
|
document.title = origTitle;
|
|
}
|
|
});
|
|
|
|
// ── 4. 桌面端键盘快捷键 ──
|
|
document.addEventListener('keydown', (e) => {
|
|
// Ctrl+N 新建会话(与菜单同步)
|
|
if (e.ctrlKey && e.key === 'n') {
|
|
e.preventDefault();
|
|
document.querySelector('#btnNewChat')?.click();
|
|
}
|
|
});
|
|
|
|
console.log('[Desktop Integration] 桌面集成完成 ✓');
|
|
})();
|