mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-22 17:00:14 +08:00
perf: optimize QR code generation and connection info for phone LAN export (#11086)
* Increase QR code margin for better scanning reliability - Change QRCodeSVG marginSize from 2 to 4 pixels - Maintains same QR code size (160px) and error correction level (Q) - Improves readability and scanning success rate on mobile devices * Optimize QR code generation and connection info for phone LAN export - Increase QR code size to 180px and reduce error correction to 'L' for better mobile scanning - Replace hardcoded logo path with AppLogo config and increase logo size to 60px - Simplify connection info by removing candidates array and using only essential IP/port data * Optimize QR code data structure for LAN connection - Compress IP addresses to numeric format to reduce QR code complexity - Use compact array format instead of verbose JSON object structure - Remove debug logging to streamline connection flow * feat: 更新 WebSocket 状态和候选者响应类型,优化连接信息处理 * Increase QR code size and error correction for better scanning - Increase QR code size from 180px to 300px for improved readability - Change error correction level from L (low) to H (high) for better reliability - Reduce logo size from 60px to 40px to accommodate larger QR data - Increase margin size from 1 to 2 for better border clearance * 调整二维码大小和图标尺寸以优化扫描体验 * fix(i18n): Auto update translations for PR #11086 * fix(i18n): Auto update translations for PR #11086 * fix(i18n): Auto update translations for PR #11086 --------- Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
parent
dc06c103e0
commit
28bc89ac7c
@ -31,3 +31,16 @@ export type WebviewKeyEvent = {
|
|||||||
shift: boolean
|
shift: boolean
|
||||||
alt: boolean
|
alt: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WebSocketStatusResponse {
|
||||||
|
isRunning: boolean
|
||||||
|
port?: number
|
||||||
|
ip?: string
|
||||||
|
clientConnected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebSocketCandidatesResponse {
|
||||||
|
host: string
|
||||||
|
interface: string
|
||||||
|
priority: number
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { WebSocketCandidatesResponse, WebSocketStatusResponse } from '@shared/config/types'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { networkInterfaces } from 'os'
|
import { networkInterfaces } from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
@ -202,12 +203,7 @@ class WebSocketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStatus = async (): Promise<{
|
public getStatus = async (): Promise<WebSocketStatusResponse> => {
|
||||||
isRunning: boolean
|
|
||||||
port?: number
|
|
||||||
ip?: string
|
|
||||||
clientConnected: boolean
|
|
||||||
}> => {
|
|
||||||
return {
|
return {
|
||||||
isRunning: this.isStarted,
|
isRunning: this.isStarted,
|
||||||
port: this.isStarted ? this.port : undefined,
|
port: this.isStarted ? this.port : undefined,
|
||||||
@ -216,13 +212,7 @@ class WebSocketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllCandidates = async (): Promise<
|
public getAllCandidates = async (): Promise<WebSocketCandidatesResponse[]> => {
|
||||||
Array<{
|
|
||||||
host: string
|
|
||||||
interface: string
|
|
||||||
priority: number
|
|
||||||
}>
|
|
||||||
> => {
|
|
||||||
const interfaces = networkInterfaces()
|
const interfaces = networkInterfaces()
|
||||||
|
|
||||||
// 按优先级排序的网络接口名称模式
|
// 按优先级排序的网络接口名称模式
|
||||||
|
|||||||
@ -3,7 +3,9 @@ import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@herou
|
|||||||
import { Progress } from '@heroui/progress'
|
import { Progress } from '@heroui/progress'
|
||||||
import { Spinner } from '@heroui/spinner'
|
import { Spinner } from '@heroui/spinner'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { SettingHelpText, SettingRow } from '@renderer/pages/settings'
|
import { SettingHelpText, SettingRow } from '@renderer/pages/settings'
|
||||||
|
import { WebSocketCandidatesResponse } from '@shared/config/types'
|
||||||
import { QRCodeSVG } from 'qrcode.react'
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -38,12 +40,12 @@ const ScanQRCode: React.FC<{ qrCodeValue: string }> = ({ qrCodeValue }) => {
|
|||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
marginSize={2}
|
marginSize={2}
|
||||||
value={qrCodeValue}
|
value={qrCodeValue}
|
||||||
level="Q"
|
level="H"
|
||||||
size={160}
|
size={200}
|
||||||
imageSettings={{
|
imageSettings={{
|
||||||
src: '/src/assets/images/logo.png',
|
src: AppLogo,
|
||||||
width: 40,
|
width: 60,
|
||||||
height: 40,
|
height: 60,
|
||||||
excavate: true
|
excavate: true
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -198,17 +200,28 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
const { port, ip } = await window.api.webSocket.status()
|
const { port, ip } = await window.api.webSocket.status()
|
||||||
|
|
||||||
if (ip && port) {
|
if (ip && port) {
|
||||||
const candidates = await window.api.webSocket.getAllCandidates()
|
const candidatesData = await window.api.webSocket.getAllCandidates()
|
||||||
const connectionInfo = {
|
|
||||||
type: 'cherry-studio-app',
|
const optimizeConnectionInfo = () => {
|
||||||
candidates,
|
const ipToNumber = (ip: string) => {
|
||||||
selectedHost: ip,
|
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet), 0)
|
||||||
port,
|
}
|
||||||
timestamp: Date.now()
|
|
||||||
|
const compressedData = [
|
||||||
|
'CSA',
|
||||||
|
ipToNumber(ip),
|
||||||
|
candidatesData.map((candidate: WebSocketCandidatesResponse) => ipToNumber(candidate.host)),
|
||||||
|
port, // 端口号
|
||||||
|
Date.now() % 86400000
|
||||||
|
]
|
||||||
|
|
||||||
|
return compressedData
|
||||||
}
|
}
|
||||||
setQrCodeValue(JSON.stringify(connectionInfo))
|
|
||||||
|
const compressedData = optimizeConnectionInfo()
|
||||||
|
const qrCodeValue = JSON.stringify(compressedData)
|
||||||
|
setQrCodeValue(qrCodeValue)
|
||||||
setConnectionPhase('waiting_qr_scan')
|
setConnectionPhase('waiting_qr_scan')
|
||||||
logger.info(`QR code generated: ${ip}:${port} with ${candidates.length} IP candidates`)
|
|
||||||
} else {
|
} else {
|
||||||
setError(t('settings.data.export_to_phone.lan.error.no_ip'))
|
setError(t('settings.data.export_to_phone.lan.error.no_ip'))
|
||||||
setConnectionPhase('error')
|
setConnectionPhase('error')
|
||||||
|
|||||||
@ -2923,15 +2923,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Ein KI-Assistent für Kreative",
|
"description": "Ein KI-Assistent für Kreative",
|
||||||
"downloading": "Update wird heruntergeladen...",
|
"downloading": "Update wird heruntergeladen...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Unternehmen"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Feedback",
|
"button": "Feedback",
|
||||||
"title": "Feedback"
|
"title": "Feedback"
|
||||||
},
|
},
|
||||||
"label": "Über uns",
|
"label": "Über uns",
|
||||||
"license": {
|
|
||||||
"button": "Anzeigen",
|
|
||||||
"title": "Lizenz"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Anzeigen",
|
"button": "Anzeigen",
|
||||||
"title": "Changelog"
|
"title": "Changelog"
|
||||||
|
|||||||
@ -2923,15 +2923,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Ένα AI ασιστάντα που έχει σχεδιαστεί για δημιουργούς",
|
"description": "Ένα AI ασιστάντα που έχει σχεδιαστεί για δημιουργούς",
|
||||||
"downloading": "Λήψη ενημερώσεων...",
|
"downloading": "Λήψη ενημερώσεων...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Επιχείρηση"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Σχόλια και Παρατηρήσεις",
|
"button": "Σχόλια και Παρατηρήσεις",
|
||||||
"title": "Αποστολή σχολίων"
|
"title": "Αποστολή σχολίων"
|
||||||
},
|
},
|
||||||
"label": "Περί μας",
|
"label": "Περί μας",
|
||||||
"license": {
|
|
||||||
"button": "Προβολή",
|
|
||||||
"title": "Licenses"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Προβολή",
|
"button": "Προβολή",
|
||||||
"title": "Ημερολόγιο Ενημερώσεων"
|
"title": "Ημερολόγιο Ενημερώσεων"
|
||||||
|
|||||||
@ -2923,15 +2923,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Una asistente de IA creada para los creadores",
|
"description": "Una asistente de IA creada para los creadores",
|
||||||
"downloading": "Descargando actualización...",
|
"downloading": "Descargando actualización...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Empresa"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Enviar feedback",
|
"button": "Enviar feedback",
|
||||||
"title": "Enviar comentarios"
|
"title": "Enviar comentarios"
|
||||||
},
|
},
|
||||||
"label": "Acerca de nosotros",
|
"label": "Acerca de nosotros",
|
||||||
"license": {
|
|
||||||
"button": "Ver",
|
|
||||||
"title": "Licencia"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Ver",
|
"button": "Ver",
|
||||||
"title": "Registro de cambios"
|
"title": "Registro de cambios"
|
||||||
|
|||||||
@ -2923,15 +2923,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Un assistant IA conçu pour les créateurs",
|
"description": "Un assistant IA conçu pour les créateurs",
|
||||||
"downloading": "Téléchargement de la mise à jour en cours...",
|
"downloading": "Téléchargement de la mise à jour en cours...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Entreprise"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Faire un retour",
|
"button": "Faire un retour",
|
||||||
"title": "Retour d'information"
|
"title": "Retour d'information"
|
||||||
},
|
},
|
||||||
"label": "À propos de nous",
|
"label": "À propos de nous",
|
||||||
"license": {
|
|
||||||
"button": "Afficher",
|
|
||||||
"title": "Licence"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Afficher",
|
"button": "Afficher",
|
||||||
"title": "Journal des mises à jour"
|
"title": "Journal des mises à jour"
|
||||||
|
|||||||
@ -2923,15 +2923,14 @@
|
|||||||
},
|
},
|
||||||
"description": "クリエイターのための強力なAIアシスタント",
|
"description": "クリエイターのための強力なAIアシスタント",
|
||||||
"downloading": "ダウンロード中...",
|
"downloading": "ダウンロード中...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "エンタープライズ"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "フィードバック",
|
"button": "フィードバック",
|
||||||
"title": "フィードバック"
|
"title": "フィードバック"
|
||||||
},
|
},
|
||||||
"label": "について",
|
"label": "について",
|
||||||
"license": {
|
|
||||||
"button": "ライセンス",
|
|
||||||
"title": "ライセンス"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "リリース",
|
"button": "リリース",
|
||||||
"title": "リリースノート"
|
"title": "リリースノート"
|
||||||
|
|||||||
@ -2923,15 +2923,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Um assistente de IA criado para criadores",
|
"description": "Um assistente de IA criado para criadores",
|
||||||
"downloading": "Baixando atualizações...",
|
"downloading": "Baixando atualizações...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Empresa"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Feedback",
|
"button": "Feedback",
|
||||||
"title": "Enviar feedback"
|
"title": "Enviar feedback"
|
||||||
},
|
},
|
||||||
"label": "Sobre Nós",
|
"label": "Sobre Nós",
|
||||||
"license": {
|
|
||||||
"button": "Ver",
|
|
||||||
"title": "Licença"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Ver",
|
"button": "Ver",
|
||||||
"title": "Registro de alterações"
|
"title": "Registro de alterações"
|
||||||
|
|||||||
@ -2923,15 +2923,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Мощный AI-ассистент для созидания",
|
"description": "Мощный AI-ассистент для созидания",
|
||||||
"downloading": "Загрузка...",
|
"downloading": "Загрузка...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Предприятие"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Обратная связь",
|
"button": "Обратная связь",
|
||||||
"title": "Обратная связь"
|
"title": "Обратная связь"
|
||||||
},
|
},
|
||||||
"label": "О программе и обратная связь",
|
"label": "О программе и обратная связь",
|
||||||
"license": {
|
|
||||||
"button": "Лицензия",
|
|
||||||
"title": "Лицензия"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Релизы",
|
"button": "Релизы",
|
||||||
"title": "Заметки о релизах"
|
"title": "Заметки о релизах"
|
||||||
@ -3043,7 +3042,7 @@
|
|||||||
"confirm": {
|
"confirm": {
|
||||||
"button": "Выберите файл резервной копии"
|
"button": "Выберите файл резервной копии"
|
||||||
},
|
},
|
||||||
"content": "Экспорт части данных, включая чат и настройки. Пожалуйста, обратите внимание, что процесс резервного копирования может занять некоторое время. Благодарим за ваше терпение.",
|
"content": "Экспорт части данных, включая историю чатов и настройки. Обратите внимание, процесс резервного копирования может занять некоторое время, благодарим за ваше терпение.",
|
||||||
"lan": {
|
"lan": {
|
||||||
"auto_close_tip": "Автоматическое закрытие через {{seconds}} секунд...",
|
"auto_close_tip": "Автоматическое закрытие через {{seconds}} секунд...",
|
||||||
"confirm_close_message": "Передача файла в процессе. Закрытие прервет передачу. Вы уверены, что хотите принудительно закрыть?",
|
"confirm_close_message": "Передача файла в процессе. Закрытие прервет передачу. Вы уверены, что хотите принудительно закрыть?",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user