mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 22:10:21 +08:00
冲突
This commit is contained in:
parent
bbe08e2a6c
commit
d64b4f3210
@ -64,6 +64,11 @@ export enum IpcChannel {
|
|||||||
Aes_Encrypt = 'aes:encrypt',
|
Aes_Encrypt = 'aes:encrypt',
|
||||||
Aes_Decrypt = 'aes:decrypt',
|
Aes_Decrypt = 'aes:decrypt',
|
||||||
|
|
||||||
|
// search window
|
||||||
|
SearchWindow_Open = 'search-window:open',
|
||||||
|
SearchWindow_Close = 'search-window:close',
|
||||||
|
SearchWindow_OpenUrl = 'search-window:open-url',
|
||||||
|
|
||||||
Gemini_UploadFile = 'gemini:upload-file',
|
Gemini_UploadFile = 'gemini:upload-file',
|
||||||
Gemini_Base64File = 'gemini:base64-file',
|
Gemini_Base64File = 'gemini:base64-file',
|
||||||
Gemini_RetrieveFile = 'gemini:retrieve-file',
|
Gemini_RetrieveFile = 'gemini:retrieve-file',
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Edge ASR (External)</title>
|
<title>Browser ASR (External)</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
@ -28,8 +28,8 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>Edge ASR 中继页面</h1>
|
<h1>浏览器语音识别中继页面</h1>
|
||||||
<p>这个页面需要在 Edge 浏览器中保持打开,以便 Electron 应用使用其语音识别功能。</p>
|
<p>这个页面需要在浏览器中保持打开,以便应用使用其语音识别功能。</p>
|
||||||
<div id="status">正在连接到服务器...</div>
|
<div id="status">正在连接到服务器...</div>
|
||||||
<div id="result"></div>
|
<div id="result"></div>
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ const path = require('path') // Need path module
|
|||||||
const app = express()
|
const app = express()
|
||||||
const port = 8080 // Define the port
|
const port = 8080 // Define the port
|
||||||
|
|
||||||
// 提供网页给 Edge 浏览器
|
// 提供网页给浏览器
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
// Use path.join for cross-platform compatibility
|
// Use path.join for cross-platform compatibility
|
||||||
res.sendFile(path.join(__dirname, 'index.html'))
|
res.sendFile(path.join(__dirname, 'index.html'))
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import * as NutstoreService from './services/NutstoreService'
|
|||||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||||
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
||||||
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
||||||
|
import { searchService } from './services/SearchService'
|
||||||
import { TrayService } from './services/TrayService'
|
import { TrayService } from './services/TrayService'
|
||||||
import { windowService } from './services/WindowService'
|
import { windowService } from './services/WindowService'
|
||||||
import { getResourcePath } from './utils'
|
import { getResourcePath } from './utils'
|
||||||
@ -297,6 +298,17 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
NutstoreService.getDirectoryContents(token, path)
|
NutstoreService.getDirectoryContents(token, path)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// search window
|
||||||
|
ipcMain.handle(IpcChannel.SearchWindow_Open, async (_, uid: string) => {
|
||||||
|
await searchService.openSearchWindow(uid)
|
||||||
|
})
|
||||||
|
ipcMain.handle(IpcChannel.SearchWindow_Close, async (_, uid: string) => {
|
||||||
|
await searchService.closeSearchWindow(uid)
|
||||||
|
})
|
||||||
|
ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, async (_, uid: string, url: string) => {
|
||||||
|
return await searchService.openUrlInSearchWindow(uid, url)
|
||||||
|
})
|
||||||
|
|
||||||
// 启动ASR服务器
|
// 启动ASR服务器
|
||||||
ipcMain.handle('start-asr-server', async () => {
|
ipcMain.handle('start-asr-server', async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
100
src/main/services/SearchService.ts
Normal file
100
src/main/services/SearchService.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { BrowserWindow } from 'electron'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { is } from '@electron-toolkit/utils'
|
||||||
|
import { isMac } from '@main/constant'
|
||||||
|
|
||||||
|
class SearchService {
|
||||||
|
private searchWindows: Map<string, BrowserWindow> = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开搜索窗口
|
||||||
|
* @param uid 窗口唯一标识符
|
||||||
|
*/
|
||||||
|
public async openSearchWindow(uid: string): Promise<void> {
|
||||||
|
// 如果窗口已经存在,则激活它
|
||||||
|
if (this.searchWindows.has(uid)) {
|
||||||
|
const existingWindow = this.searchWindows.get(uid)
|
||||||
|
if (existingWindow && !existingWindow.isDestroyed()) {
|
||||||
|
if (existingWindow.isMinimized()) {
|
||||||
|
existingWindow.restore()
|
||||||
|
}
|
||||||
|
existingWindow.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 如果窗口已销毁,则从Map中移除
|
||||||
|
this.searchWindows.delete(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新窗口
|
||||||
|
const searchWindow = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
show: false,
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
...(isMac ? { titleBarStyle: 'hidden' } : {}),
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, '../preload/index.js'),
|
||||||
|
sandbox: false,
|
||||||
|
webSecurity: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置窗口标题
|
||||||
|
searchWindow.setTitle(`搜索窗口 - ${uid}`)
|
||||||
|
|
||||||
|
// 加载搜索页面
|
||||||
|
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||||
|
searchWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/#/search?uid=${uid}`)
|
||||||
|
} else {
|
||||||
|
searchWindow.loadFile(join(__dirname, '../renderer/index.html'), {
|
||||||
|
hash: `/search?uid=${uid}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口准备好后显示
|
||||||
|
searchWindow.once('ready-to-show', () => {
|
||||||
|
searchWindow.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 窗口关闭时从Map中移除
|
||||||
|
searchWindow.on('closed', () => {
|
||||||
|
this.searchWindows.delete(uid)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 存储窗口引用
|
||||||
|
this.searchWindows.set(uid, searchWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭搜索窗口
|
||||||
|
* @param uid 窗口唯一标识符
|
||||||
|
*/
|
||||||
|
public async closeSearchWindow(uid: string): Promise<void> {
|
||||||
|
const window = this.searchWindows.get(uid)
|
||||||
|
if (window && !window.isDestroyed()) {
|
||||||
|
window.close()
|
||||||
|
}
|
||||||
|
this.searchWindows.delete(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在搜索窗口中打开URL
|
||||||
|
* @param uid 窗口唯一标识符
|
||||||
|
* @param url 要打开的URL
|
||||||
|
*/
|
||||||
|
public async openUrlInSearchWindow(uid: string, url: string): Promise<boolean> {
|
||||||
|
const window = this.searchWindows.get(uid)
|
||||||
|
if (window && !window.isDestroyed()) {
|
||||||
|
try {
|
||||||
|
await window.loadURL(url)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load URL in search window: ${error}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const searchService = new SearchService()
|
||||||
5
src/preload/index.d.ts
vendored
5
src/preload/index.d.ts
vendored
@ -175,6 +175,11 @@ declare global {
|
|||||||
decryptToken: (token: string) => Promise<{ username: string; access_token: string }>
|
decryptToken: (token: string) => Promise<{ username: string; access_token: string }>
|
||||||
getDirectoryContents: (token: string, path: string) => Promise<any>
|
getDirectoryContents: (token: string, path: string) => Promise<any>
|
||||||
}
|
}
|
||||||
|
searchWindow: {
|
||||||
|
open: (uid: string) => Promise<void>
|
||||||
|
close: (uid: string) => Promise<void>
|
||||||
|
openUrl: (uid: string, url: string) => Promise<boolean>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,6 +169,11 @@ const api = {
|
|||||||
decryptToken: (token: string) => ipcRenderer.invoke(IpcChannel.Nutstore_DecryptToken, token),
|
decryptToken: (token: string) => ipcRenderer.invoke(IpcChannel.Nutstore_DecryptToken, token),
|
||||||
getDirectoryContents: (token: string, path: string) =>
|
getDirectoryContents: (token: string, path: string) =>
|
||||||
ipcRenderer.invoke(IpcChannel.Nutstore_GetDirectoryContents, token, path)
|
ipcRenderer.invoke(IpcChannel.Nutstore_GetDirectoryContents, token, path)
|
||||||
|
},
|
||||||
|
searchWindow: {
|
||||||
|
open: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Open, uid),
|
||||||
|
close: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid),
|
||||||
|
openUrl: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Edge ASR (External)</title>
|
<title>Browser ASR (External)</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
@ -28,8 +28,8 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h1>Edge ASR 中继页面</h1>
|
<h1>浏览器语音识别中继页面</h1>
|
||||||
<p>这个页面需要在 Edge 浏览器中保持打开,以便 Electron 应用使用其语音识别功能。</p>
|
<p>这个页面需要在浏览器中保持打开,以便应用使用其语音识别功能。</p>
|
||||||
<div id="status">正在连接到服务器...</div>
|
<div id="status">正在连接到服务器...</div>
|
||||||
<div id="result"></div>
|
<div id="result"></div>
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ function getIndexHtmlPath() {
|
|||||||
return path.join(process.cwd(), 'index.html');
|
return path.join(process.cwd(), 'index.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提供网页给 Edge 浏览器
|
// 提供网页给浏览器
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
const indexPath = getIndexHtmlPath();
|
const indexPath = getIndexHtmlPath();
|
||||||
console.log(`Serving index.html from: ${indexPath}`);
|
console.log(`Serving index.html from: ${indexPath}`);
|
||||||
|
|||||||
@ -1347,7 +1347,7 @@
|
|||||||
"api_settings": "API Settings",
|
"api_settings": "API Settings",
|
||||||
"service_type": "Service Type",
|
"service_type": "Service Type",
|
||||||
"service_type.openai": "OpenAI",
|
"service_type.openai": "OpenAI",
|
||||||
"service_type.edge": "Edge TTS",
|
"service_type.edge": "Browser TTS",
|
||||||
"service_type.refresh": "Refresh TTS service type settings",
|
"service_type.refresh": "Refresh TTS service type settings",
|
||||||
"service_type.refreshed": "TTS service type settings refreshed",
|
"service_type.refreshed": "TTS service type settings refreshed",
|
||||||
"api_key": "API Key",
|
"api_key": "API Key",
|
||||||
@ -1393,7 +1393,7 @@
|
|||||||
"model": "Model",
|
"model": "Model",
|
||||||
"browser.info": "Use the browser's built-in speech recognition feature, no additional setup required",
|
"browser.info": "Use the browser's built-in speech recognition feature, no additional setup required",
|
||||||
"local.info": "Use local server and browser for speech recognition, need to start the server and open the browser page first",
|
"local.info": "Use local server and browser for speech recognition, need to start the server and open the browser page first",
|
||||||
"local.browser_tip": "Please open this page in Edge browser and keep the browser window open",
|
"local.browser_tip": "Please open this page in your browser and keep the browser window open",
|
||||||
"local.test_connection": "Test Connection",
|
"local.test_connection": "Test Connection",
|
||||||
"local.connection_success": "Connection successful",
|
"local.connection_success": "Connection successful",
|
||||||
"local.connection_failed": "Connection failed, please make sure the server is running",
|
"local.connection_failed": "Connection failed, please make sure the server is running",
|
||||||
|
|||||||
@ -1347,14 +1347,14 @@
|
|||||||
"api_settings": "API設定",
|
"api_settings": "API設定",
|
||||||
"service_type": "サービスタイプ",
|
"service_type": "サービスタイプ",
|
||||||
"service_type.openai": "OpenAI",
|
"service_type.openai": "OpenAI",
|
||||||
"service_type.edge": "Edge TTS",
|
"service_type.edge": "ブラウザ TTS",
|
||||||
"test": "テスト",
|
"test": "テスト",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "音声合成に失敗しました",
|
"failed": "音声合成に失敗しました",
|
||||||
"not_enabled": "音声合成が有効になっていません",
|
"not_enabled": "音声合成が有効になっていません",
|
||||||
"no_edge_voice": "Edge TTSの音声が選択されていません"
|
"no_edge_voice": "ブラウザ TTSの音声が選択されていません"
|
||||||
},
|
},
|
||||||
"help": "OpenAIのTTS APIを使用するには、APIキーが必要です。Edge TTSはブラウザの機能を使用するため、APIキーは不要です。",
|
"help": "OpenAIのTTS APIを使用するには、APIキーが必要です。ブラウザ TTSはブラウザの機能を使用するため、APIキーは不要です。",
|
||||||
"learn_more": "詳細はこちら"
|
"learn_more": "詳細はこちら"
|
||||||
},
|
},
|
||||||
"asr": {
|
"asr": {
|
||||||
@ -1372,7 +1372,7 @@
|
|||||||
"model": "モデル",
|
"model": "モデル",
|
||||||
"browser.info": "ブラウザの内蔵音声認識機能を使用します。追加設定は不要です",
|
"browser.info": "ブラウザの内蔵音声認識機能を使用します。追加設定は不要です",
|
||||||
"local.info": "ローカルサーバーとブラウザを使用して音声認識を行います。サーバーを起動してブラウザページを開く必要があります",
|
"local.info": "ローカルサーバーとブラウザを使用して音声認識を行います。サーバーを起動してブラウザページを開く必要があります",
|
||||||
"local.browser_tip": "このページをEdgeブラウザで開き、ブラウザウィンドウを開いたままにしてください",
|
"local.browser_tip": "このページをブラウザで開き、ブラウザウィンドウを開いたままにしてください",
|
||||||
"local.test_connection": "接続テスト",
|
"local.test_connection": "接続テスト",
|
||||||
"local.connection_success": "接続成功",
|
"local.connection_success": "接続成功",
|
||||||
"local.connection_failed": "接続失敗。サーバーが起動していることを確認してください",
|
"local.connection_failed": "接続失敗。サーバーが起動していることを確認してください",
|
||||||
|
|||||||
@ -1353,14 +1353,14 @@
|
|||||||
"api_settings": "API设置",
|
"api_settings": "API设置",
|
||||||
"service_type": "服务类型",
|
"service_type": "服务类型",
|
||||||
"service_type.openai": "OpenAI",
|
"service_type.openai": "OpenAI",
|
||||||
"service_type.edge": "Edge TTS",
|
"service_type.edge": "浏览器 TTS",
|
||||||
"service_type.refresh": "刷新TTS服务类型设置",
|
"service_type.refresh": "刷新TTS服务类型设置",
|
||||||
"service_type.refreshed": "已刷新TTS服务类型设置",
|
"service_type.refreshed": "已刷新TTS服务类型设置",
|
||||||
"api_key": "API密钥",
|
"api_key": "API密钥",
|
||||||
"api_key.placeholder": "请输入OpenAI API密钥",
|
"api_key.placeholder": "请输入OpenAI API密钥",
|
||||||
"api_url": "API地址",
|
"api_url": "API地址",
|
||||||
"api_url.placeholder": "例如:https://api.openai.com/v1/audio/speech",
|
"api_url.placeholder": "例如:https://api.openai.com/v1/audio/speech",
|
||||||
"edge_voice": "Edge TTS音色",
|
"edge_voice": "浏览器 TTS音色",
|
||||||
"edge_voice.loading": "加载中...",
|
"edge_voice.loading": "加载中...",
|
||||||
"edge_voice.refresh": "刷新可用音色列表",
|
"edge_voice.refresh": "刷新可用音色列表",
|
||||||
"edge_voice.not_found": "未找到匹配的音色",
|
"edge_voice.not_found": "未找到匹配的音色",
|
||||||
@ -1386,7 +1386,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"not_enabled": "语音合成功能未启用",
|
"not_enabled": "语音合成功能未启用",
|
||||||
"no_api_key": "未设置API密钥",
|
"no_api_key": "未设置API密钥",
|
||||||
"no_edge_voice": "未选择Edge TTS音色",
|
"no_edge_voice": "未选择浏览器 TTS音色",
|
||||||
"browser_not_support": "浏览器不支持语音合成"
|
"browser_not_support": "浏览器不支持语音合成"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1405,7 +1405,7 @@
|
|||||||
"model": "模型",
|
"model": "模型",
|
||||||
"browser.info": "使用浏览器内置的语音识别功能,无需额外设置",
|
"browser.info": "使用浏览器内置的语音识别功能,无需额外设置",
|
||||||
"local.info": "使用本地服务器和浏览器进行语音识别,需要先启动服务器并打开浏览器页面",
|
"local.info": "使用本地服务器和浏览器进行语音识别,需要先启动服务器并打开浏览器页面",
|
||||||
"local.browser_tip": "请在Edge浏览器中打开此页面,并保持浏览器窗口打开",
|
"local.browser_tip": "请在浏览器中打开此页面,并保持浏览器窗口打开",
|
||||||
"local.test_connection": "测试连接",
|
"local.test_connection": "测试连接",
|
||||||
"local.connection_success": "连接成功",
|
"local.connection_success": "连接成功",
|
||||||
"local.connection_failed": "连接失败,请确保服务器已启动",
|
"local.connection_failed": "连接失败,请确保服务器已启动",
|
||||||
|
|||||||
@ -121,7 +121,7 @@ const TTSSettings: FC = () => {
|
|||||||
// 浏览器可用的语音列表
|
// 浏览器可用的语音列表
|
||||||
const [availableVoices, setAvailableVoices] = useState<{ label: string; value: string }[]>([])
|
const [availableVoices, setAvailableVoices] = useState<{ label: string; value: string }[]>([])
|
||||||
|
|
||||||
// 预定义的Edge TTS音色列表
|
// 预定义的浏览器 TTS音色列表
|
||||||
const predefinedVoices = [
|
const predefinedVoices = [
|
||||||
{ label: '小晓 (女声, 中文)', value: 'zh-CN-XiaoxiaoNeural' },
|
{ label: '小晓 (女声, 中文)', value: 'zh-CN-XiaoxiaoNeural' },
|
||||||
{ label: '云扬 (男声, 中文)', value: 'zh-CN-YunyangNeural' },
|
{ label: '云扬 (男声, 中文)', value: 'zh-CN-YunyangNeural' },
|
||||||
@ -494,7 +494,7 @@ const TTSSettings: FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Edge TTS设置 */}
|
{/* 浏览器 TTS设置 */}
|
||||||
{ttsServiceType === 'edge' && (
|
{ttsServiceType === 'edge' && (
|
||||||
<Form.Item label={t('settings.tts.edge_voice')} style={{ marginBottom: 16 }}>
|
<Form.Item label={t('settings.tts.edge_voice')} style={{ marginBottom: 16 }}>
|
||||||
<VoiceSelectContainer>
|
<VoiceSelectContainer>
|
||||||
|
|||||||
@ -112,14 +112,14 @@ export interface SettingsState {
|
|||||||
enableDataCollection: boolean
|
enableDataCollection: boolean
|
||||||
// TTS配置
|
// TTS配置
|
||||||
ttsEnabled: boolean
|
ttsEnabled: boolean
|
||||||
ttsServiceType: string // TTS服务类型:openai或edge
|
ttsServiceType: string // TTS服务类型:openai或浏览器
|
||||||
ttsApiKey: string
|
ttsApiKey: string
|
||||||
ttsApiUrl: string
|
ttsApiUrl: string
|
||||||
ttsVoice: string
|
ttsVoice: string
|
||||||
ttsModel: string
|
ttsModel: string
|
||||||
ttsCustomVoices: string[]
|
ttsCustomVoices: string[]
|
||||||
ttsCustomModels: string[]
|
ttsCustomModels: string[]
|
||||||
// Edge TTS配置
|
// 浏览器 TTS配置
|
||||||
ttsEdgeVoice: string
|
ttsEdgeVoice: string
|
||||||
// TTS过滤选项
|
// TTS过滤选项
|
||||||
ttsFilterOptions: {
|
ttsFilterOptions: {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user