style: set eol to lf, code formatting (#7923)

* chore(gitattributes): set eol to lf

* chore: git renormalize

* style: reformatting

* chore: keep eslint prettier plugin consistent on eol
This commit is contained in:
one
2025-07-08 09:50:33 +08:00
committed by GitHub
parent 4111ee4c58
commit 4ac8a38834
21 changed files with 7089 additions and 7096 deletions

View File

@@ -1,9 +1,9 @@
root = true root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true

1
.gitattributes vendored
View File

@@ -1,2 +1,3 @@
* text=auto eol=lf
/.yarn/** linguist-vendored /.yarn/** linguist-vendored
/.yarn/releases/* binary /.yarn/releases/* binary

View File

@@ -73,4 +73,4 @@ body:
id: additional id: additional
attributes: attributes:
label: 附加信息 label: 附加信息
description: 任何能让我们对您的问题有更多了解的信息,包括截图或相关链接 description: 任何能让我们对您的问题有更多了解的信息,包括截图或相关链接

View File

@@ -73,4 +73,4 @@ body:
id: additional id: additional
attributes: attributes:
label: Additional Information label: Additional Information
description: Any other information that could help us better understand your question, including screenshots or relevant links description: Any other information that could help us better understand your question, including screenshots or relevant links

View File

@@ -9,115 +9,115 @@ labels:
# skips and removes # skips and removes
- name: skip all - name: skip all
content: content:
regexes: "[Ss]kip (?:[Aa]ll |)[Ll]abels?" regexes: '[Ss]kip (?:[Aa]ll |)[Ll]abels?'
- name: remove all - name: remove all
content: content:
regexes: "[Rr]emove (?:[Aa]ll |)[Ll]abels?" regexes: '[Rr]emove (?:[Aa]ll |)[Ll]abels?'
- name: skip kind/bug - name: skip kind/bug
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
- name: remove kind/bug - name: remove kind/bug
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
- name: skip kind/enhancement - name: skip kind/enhancement
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
- name: remove kind/enhancement - name: remove kind/enhancement
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
- name: skip kind/question - name: skip kind/question
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)kind/question(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
- name: remove kind/question - name: remove kind/question
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)kind/question(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
- name: skip area/Connectivity - name: skip area/Connectivity
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
- name: remove area/Connectivity - name: remove area/Connectivity
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
- name: skip area/UI/UX - name: skip area/UI/UX
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
- name: remove area/UI/UX - name: remove area/UI/UX
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
- name: skip kind/documentation - name: skip kind/documentation
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
- name: remove kind/documentation - name: remove kind/documentation
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
- name: skip client:linux - name: skip client:linux
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)client:linux(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
- name: remove client:linux - name: remove client:linux
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)client:linux(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
- name: skip client:mac - name: skip client:mac
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)client:mac(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
- name: remove client:mac - name: remove client:mac
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)client:mac(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
- name: skip client:win - name: skip client:win
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)client:win(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
- name: remove client:win - name: remove client:win
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)client:win(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
- name: skip sig/Assistant - name: skip sig/Assistant
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
- name: remove sig/Assistant - name: remove sig/Assistant
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
- name: skip sig/Data - name: skip sig/Data
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
- name: remove sig/Data - name: remove sig/Data
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
- name: skip sig/MCP - name: skip sig/MCP
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
- name: remove sig/MCP - name: remove sig/MCP
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
- name: skip sig/RAG - name: skip sig/RAG
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
- name: remove sig/RAG - name: remove sig/RAG
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
- name: skip lgtm - name: skip lgtm
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)lgtm(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
- name: remove lgtm - name: remove lgtm
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)lgtm(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
- name: skip License - name: skip License
content: content:
regexes: "[Ss]kip (?:[Ll]abels? |)(?:`|)License(?:`|)" regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)License(?:`|)'
- name: remove License - name: remove License
content: content:
regexes: "[Rr]emove (?:[Ll]abels? |)(?:`|)License(?:`|)" regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)License(?:`|)'
# `Dev Team` # `Dev Team`
- name: Dev Team - name: Dev Team
@@ -129,7 +129,7 @@ labels:
# Area labels # Area labels
- name: area/Connectivity - name: area/Connectivity
content: area/Connectivity content: area/Connectivity
regexes: "代理|[Pp]roxy" regexes: '代理|[Pp]roxy'
skip-if: skip-if:
- skip all - skip all
- skip area/Connectivity - skip area/Connectivity
@@ -139,7 +139,7 @@ labels:
- name: area/UI/UX - name: area/UI/UX
content: area/UI/UX content: area/UI/UX
regexes: "界面|[Uu][Ii]|重叠|按钮|图标|组件|渲染|菜单|栏目|头像|主题|样式|[Cc][Ss][Ss]" regexes: '界面|[Uu][Ii]|重叠|按钮|图标|组件|渲染|菜单|栏目|头像|主题|样式|[Cc][Ss][Ss]'
skip-if: skip-if:
- skip all - skip all
- skip area/UI/UX - skip area/UI/UX
@@ -150,7 +150,7 @@ labels:
# Kind labels # Kind labels
- name: kind/documentation - name: kind/documentation
content: kind/documentation content: kind/documentation
regexes: "文档|教程|[Dd]oc(s|umentation)|[Rr]eadme" regexes: '文档|教程|[Dd]oc(s|umentation)|[Rr]eadme'
skip-if: skip-if:
- skip all - skip all
- skip kind/documentation - skip kind/documentation
@@ -161,7 +161,7 @@ labels:
# Client labels # Client labels
- name: client:linux - name: client:linux
content: client:linux content: client:linux
regexes: "(?:[Ll]inux|[Uu]buntu|[Dd]ebian)" regexes: '(?:[Ll]inux|[Uu]buntu|[Dd]ebian)'
skip-if: skip-if:
- skip all - skip all
- skip client:linux - skip client:linux
@@ -171,7 +171,7 @@ labels:
- name: client:mac - name: client:mac
content: client:mac content: client:mac
regexes: "(?:[Mm]ac|[Mm]acOS|[Oo]SX)" regexes: '(?:[Mm]ac|[Mm]acOS|[Oo]SX)'
skip-if: skip-if:
- skip all - skip all
- skip client:mac - skip client:mac
@@ -181,7 +181,7 @@ labels:
- name: client:win - name: client:win
content: client:win content: client:win
regexes: "(?:[Ww]in|[Ww]indows)" regexes: '(?:[Ww]in|[Ww]indows)'
skip-if: skip-if:
- skip all - skip all
- skip client:win - skip client:win
@@ -192,7 +192,7 @@ labels:
# SIG labels # SIG labels
- name: sig/Assistant - name: sig/Assistant
content: sig/Assistant content: sig/Assistant
regexes: "快捷助手|[Aa]ssistant" regexes: '快捷助手|[Aa]ssistant'
skip-if: skip-if:
- skip all - skip all
- skip sig/Assistant - skip sig/Assistant
@@ -202,7 +202,7 @@ labels:
- name: sig/Data - name: sig/Data
content: sig/Data content: sig/Data
regexes: "[Ww]ebdav|坚果云|备份|同步|数据|Obsidian|Notion|Joplin|思源" regexes: '[Ww]ebdav|坚果云|备份|同步|数据|Obsidian|Notion|Joplin|思源'
skip-if: skip-if:
- skip all - skip all
- skip sig/Data - skip sig/Data
@@ -212,7 +212,7 @@ labels:
- name: sig/MCP - name: sig/MCP
content: sig/MCP content: sig/MCP
regexes: "[Mm][Cc][Pp]" regexes: '[Mm][Cc][Pp]'
skip-if: skip-if:
- skip all - skip all
- skip sig/MCP - skip sig/MCP
@@ -222,7 +222,7 @@ labels:
- name: sig/RAG - name: sig/RAG
content: sig/RAG content: sig/RAG
regexes: "知识库|[Rr][Aa][Gg]" regexes: '知识库|[Rr][Aa][Gg]'
skip-if: skip-if:
- skip all - skip all
- skip sig/RAG - skip sig/RAG
@@ -233,7 +233,7 @@ labels:
# Other labels # Other labels
- name: lgtm - name: lgtm
content: lgtm content: lgtm
regexes: "(?:[Ll][Gg][Tt][Mm]|[Ll]ooks [Gg]ood [Tt]o [Mm]e)" regexes: '(?:[Ll][Gg][Tt][Mm]|[Ll]ooks [Gg]ood [Tt]o [Mm]e)'
skip-if: skip-if:
- skip all - skip all
- skip lgtm - skip lgtm
@@ -243,7 +243,7 @@ labels:
- name: License - name: License
content: License content: License
regexes: "(?:[Ll]icense|[Cc]opyright|[Mm][Ii][Tt]|[Aa]pache)" regexes: '(?:[Ll]icense|[Cc]opyright|[Mm][Ii][Tt]|[Aa]pache)'
skip-if: skip-if:
- skip all - skip all
- skip License - skip License

View File

@@ -1,4 +1,4 @@
name: "Issue Checker" name: 'Issue Checker'
on: on:
issues: issues:
@@ -19,7 +19,7 @@ jobs:
steps: steps:
- uses: MaaAssistantArknights/issue-checker@v1.14 - uses: MaaAssistantArknights/issue-checker@v1.14
with: with:
repo-token: "${{ secrets.GITHUB_TOKEN }}" repo-token: '${{ secrets.GITHUB_TOKEN }}'
configuration-path: .github/issue-checker.yml configuration-path: .github/issue-checker.yml
not-before: 2022-08-05T00:00:00Z not-before: 2022-08-05T00:00:00Z
include-title: 1 include-title: 1

View File

@@ -1,8 +1,8 @@
name: "Stale Issue Management" name: 'Stale Issue Management'
on: on:
schedule: schedule:
- cron: "0 0 * * *" - cron: '0 0 * * *'
workflow_dispatch: workflow_dispatch:
env: env:
@@ -24,18 +24,18 @@ jobs:
uses: actions/stale@v9 uses: actions/stale@v9
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "needs-more-info" only-labels: 'needs-more-info'
days-before-stale: ${{ env.daysBeforeStale }} days-before-stale: ${{ env.daysBeforeStale }}
days-before-close: 0 # Close immediately after stale days-before-close: 0 # Close immediately after stale
stale-issue-label: "inactive" stale-issue-label: 'inactive'
close-issue-label: "closed:no-response" close-issue-label: 'closed:no-response'
stale-issue-message: | stale-issue-message: |
This issue has been labeled as needing more information and has been inactive for ${{ env.daysBeforeStale }} days. This issue has been labeled as needing more information and has been inactive for ${{ env.daysBeforeStale }} days.
It will be closed now due to lack of additional information. It will be closed now due to lack of additional information.
该问题被标记为"需要更多信息"且已经 ${{ env.daysBeforeStale }} 天没有任何活动,将立即关闭。 该问题被标记为"需要更多信息"且已经 ${{ env.daysBeforeStale }} 天没有任何活动,将立即关闭。
operations-per-run: 50 operations-per-run: 50
exempt-issue-labels: "pending, Dev Team" exempt-issue-labels: 'pending, Dev Team'
days-before-pr-stale: -1 days-before-pr-stale: -1
days-before-pr-close: -1 days-before-pr-close: -1
@@ -45,11 +45,11 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: ${{ env.daysBeforeStale }} days-before-stale: ${{ env.daysBeforeStale }}
days-before-close: ${{ env.daysBeforeClose }} days-before-close: ${{ env.daysBeforeClose }}
stale-issue-label: "inactive" stale-issue-label: 'inactive'
stale-issue-message: | stale-issue-message: |
This issue has been inactive for a prolonged period and will be closed automatically in ${{ env.daysBeforeClose }} days. This issue has been inactive for a prolonged period and will be closed automatically in ${{ env.daysBeforeClose }} days.
该问题已长时间处于闲置状态,${{ env.daysBeforeClose }} 天后将自动关闭。 该问题已长时间处于闲置状态,${{ env.daysBeforeClose }} 天后将自动关闭。
exempt-issue-labels: "pending, Dev Team, kind/enhancement" exempt-issue-labels: 'pending, Dev Team, kind/enhancement'
days-before-pr-stale: -1 # Completely disable stalling for PRs days-before-pr-stale: -1 # Completely disable stalling for PRs
days-before-pr-close: -1 # Completely disable closing for PRs days-before-pr-close: -1 # Completely disable closing for PRs

View File

@@ -117,4 +117,4 @@ jobs:
makeLatest: false makeLatest: false
tag: ${{ steps.get-tag.outputs.tag }} tag: ${{ steps.get-tag.outputs.tag }}
artifacts: 'dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/*.blockmap' artifacts: 'dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/*.blockmap'
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@ export default defineConfig([
'simple-import-sort/exports': 'error', 'simple-import-sort/exports': 'error',
'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-imports': 'error',
'@eslint-react/no-prop-types': 'error', '@eslint-react/no-prop-types': 'error',
'prettier/prettier': ['error', { endOfLine: 'auto' }] 'prettier/prettier': ['error']
} }
}, },
// Configuration for ensuring compatibility with the original ESLint(8.x) rules // Configuration for ensuring compatibility with the original ESLint(8.x) rules

View File

@@ -1,48 +1,48 @@
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { ThemeMode } from '@types' import { ThemeMode } from '@types'
import { BrowserWindow, nativeTheme } from 'electron' import { BrowserWindow, nativeTheme } from 'electron'
import { titleBarOverlayDark, titleBarOverlayLight } from '../config' import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
import { configManager } from './ConfigManager' import { configManager } from './ConfigManager'
class ThemeService { class ThemeService {
private theme: ThemeMode = ThemeMode.system private theme: ThemeMode = ThemeMode.system
constructor() { constructor() {
this.theme = configManager.getTheme() this.theme = configManager.getTheme()
if (this.theme === ThemeMode.dark || this.theme === ThemeMode.light || this.theme === ThemeMode.system) { if (this.theme === ThemeMode.dark || this.theme === ThemeMode.light || this.theme === ThemeMode.system) {
nativeTheme.themeSource = this.theme nativeTheme.themeSource = this.theme
} else { } else {
// 兼容旧版本 // 兼容旧版本
configManager.setTheme(ThemeMode.system) configManager.setTheme(ThemeMode.system)
nativeTheme.themeSource = ThemeMode.system nativeTheme.themeSource = ThemeMode.system
} }
nativeTheme.on('updated', this.themeUpdatadHandler.bind(this)) nativeTheme.on('updated', this.themeUpdatadHandler.bind(this))
} }
themeUpdatadHandler() { themeUpdatadHandler() {
BrowserWindow.getAllWindows().forEach((win) => { BrowserWindow.getAllWindows().forEach((win) => {
if (win && !win.isDestroyed() && win.setTitleBarOverlay) { if (win && !win.isDestroyed() && win.setTitleBarOverlay) {
try { try {
win.setTitleBarOverlay(nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight) win.setTitleBarOverlay(nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight)
} catch (error) { } catch (error) {
// don't throw error if setTitleBarOverlay failed // don't throw error if setTitleBarOverlay failed
// Because it may be called with some windows have some title bar // Because it may be called with some windows have some title bar
} }
} }
win.webContents.send(IpcChannel.ThemeUpdated, nativeTheme.shouldUseDarkColors ? ThemeMode.dark : ThemeMode.light) win.webContents.send(IpcChannel.ThemeUpdated, nativeTheme.shouldUseDarkColors ? ThemeMode.dark : ThemeMode.light)
}) })
} }
setTheme(theme: ThemeMode) { setTheme(theme: ThemeMode) {
if (theme === this.theme) { if (theme === this.theme) {
return return
} }
this.theme = theme this.theme = theme
nativeTheme.themeSource = theme nativeTheme.themeSource = theme
configManager.setTheme(theme) configManager.setTheme(theme)
} }
} }
export const themeService = new ThemeService() export const themeService = new ThemeService()

View File

@@ -1,26 +1,26 @@
import { BrowserWindow } from 'electron' import { BrowserWindow } from 'electron'
import { configManager } from '../services/ConfigManager' import { configManager } from '../services/ConfigManager'
export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: boolean = false) { export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: boolean = false) {
if (reset) { if (reset) {
wins.forEach((win) => { wins.forEach((win) => {
win.webContents.setZoomFactor(1) win.webContents.setZoomFactor(1)
}) })
configManager.setZoomFactor(1) configManager.setZoomFactor(1)
return return
} }
if (delta === 0) { if (delta === 0) {
return return
} }
const currentZoom = configManager.getZoomFactor() const currentZoom = configManager.getZoomFactor()
const newZoom = Number((currentZoom + delta).toFixed(1)) const newZoom = Number((currentZoom + delta).toFixed(1))
if (newZoom >= 0.5 && newZoom <= 2.0) { if (newZoom >= 0.5 && newZoom <= 2.0) {
wins.forEach((win) => { wins.forEach((win) => {
win.webContents.setZoomFactor(newZoom) win.webContents.setZoomFactor(newZoom)
}) })
configManager.setZoomFactor(newZoom) configManager.setZoomFactor(newZoom)
} }
} }

View File

@@ -1,46 +1,45 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' 'unsafe-inline' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" />
<title>Cherry Studio</title>
<head> <style>
<meta charset="UTF-8" /> html,
<meta name="viewport" content="initial-scale=1, width=device-width" /> body {
<meta http-equiv="Content-Security-Policy" margin: 0;
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' 'unsafe-inline' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" /> }
<title>Cherry Studio</title>
<style> #spinner {
html, position: fixed;
body { width: 100vw;
margin: 0; height: 100vh;
} flex-direction: row;
justify-content: center;
align-items: center;
display: flex;
}
#spinner { #spinner img {
position: fixed; width: 100px;
width: 100vw; border-radius: 50px;
height: 100vh; }
flex-direction: row; </style>
justify-content: center; </head>
align-items: center;
display: flex;
}
#spinner img { <body>
width: 100px; <div id="root"></div>
border-radius: 50px; <div id="spinner">
} <img src="/src/assets/images/logo.png" />
</style> </div>
</head> <script>
console.time('init')
<body> </script>
<div id="root"></div> <script type="module" src="/src/init.ts"></script>
<div id="spinner"> <script type="module" src="/src/entryPoint.tsx"></script>
<img src="/src/assets/images/logo.png" /> </body>
</div> </html>
<script>
console.time('init')
</script>
<script type="module" src="/src/init.ts"></script>
<script type="module" src="/src/entryPoint.tsx"></script>
</body>
</html>

View File

@@ -1,24 +1,23 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" />
<title>Cherry Studio</title>
<head> <style>
<meta charset="UTF-8" /> html,
<meta name="viewport" content="initial-scale=1, width=device-width" /> body {
<meta http-equiv="Content-Security-Policy" margin: 0;
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" /> }
<title>Cherry Studio</title> </style>
</head>
<style> <body>
html, <div id="root"></div>
body { <script type="module" src="/src/windows/mini/entryPoint.tsx"></script>
margin: 0; </body>
} </html>
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/windows/mini/entryPoint.tsx"></script>
</body>
</html>

View File

@@ -1,41 +1,39 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head>
<head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, width=device-width" /> <meta name="viewport" content="initial-scale=1, width=device-width" />
<meta http-equiv="Content-Security-Policy" <meta
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" /> http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" />
<title>Cherry Studio Selection Assistant</title> <title>Cherry Studio Selection Assistant</title>
</head>
</head> <body>
<body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/windows/selection/action/entryPoint.tsx"></script> <script type="module" src="/src/windows/selection/action/entryPoint.tsx"></script>
<style> <style>
html { html {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
body { body {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} }
#root { #root {
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
</style> </style>
</body> </body>
</html>
</html>

View File

@@ -1,46 +1,43 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" />
<title>Cherry Studio Selection Toolbar</title>
</head>
<head> <body>
<meta charset="UTF-8" /> <div id="root"></div>
<meta name="viewport" content="initial-scale=1, width=device-width" /> <script type="module" src="/src/windows/selection/toolbar/entryPoint.tsx"></script>
<meta http-equiv="Content-Security-Policy" <style>
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" /> html {
<title>Cherry Studio Selection Toolbar</title> margin: 0 !important;
background-color: transparent !important;
background-image: none !important;
}
</head> body {
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
width: 100vw !important;
height: 100vh !important;
<body> -webkit-user-select: none;
<div id="root"></div> -moz-user-select: none;
<script type="module" src="/src/windows/selection/toolbar/entryPoint.tsx"></script> -ms-user-select: none;
<style> user-select: none;
html { }
margin: 0 !important;
background-color: transparent !important;
background-image: none !important;
} #root {
margin: 0 !important;
body { padding: 0 !important;
margin: 0 !important; width: max-content !important;
padding: 0 !important; height: fit-content !important;
overflow: hidden !important; }
width: 100vw !important; </style>
height: 100vh !important; </body>
</html>
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#root {
margin: 0 !important;
padding: 0 !important;
width: max-content !important;
height: fit-content !important;
}
</style>
</body>
</html>

View File

@@ -1,13 +1,13 @@
@font-face { @font-face {
font-family: 'Twemoji Country Flags'; font-family: 'Twemoji Country Flags';
unicode-range: unicode-range:
U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F; U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
/*https://github.com/beyondkmp/country-flag-emoji-polyfill/blob/master/font/TwemojiCountryFlags.woff2 */ /*https://github.com/beyondkmp/country-flag-emoji-polyfill/blob/master/font/TwemojiCountryFlags.woff2 */
src: url('TwemojiCountryFlags.woff2') format('woff2'); src: url('TwemojiCountryFlags.woff2') format('woff2');
font-display: swap; font-display: swap;
} }
/* 国旗字体样式类 */ /* 国旗字体样式类 */
.country-flag-font { .country-flag-font {
font-family: 'Twemoji Country Flags', 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif; font-family: 'Twemoji Country Flags', 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
} }

View File

@@ -1,20 +1,20 @@
:root { :root {
--font-family: --font-family:
Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, Oxygen, Cantarell, 'Open Sans', Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, Oxygen, Cantarell, 'Open Sans',
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'; 'Noto Color Emoji';
--font-family-serif: --font-family-serif:
serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Ubuntu, Roboto, Oxygen, Cantarell, 'Open Sans', serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Ubuntu, Roboto, Oxygen, Cantarell, 'Open Sans',
'Helvetica Neue', Arial, 'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 'Helvetica Neue', Arial, 'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--code-font-family: 'Cascadia Code', 'Fira Code', 'Consolas', Menlo, Courier, monospace; --code-font-family: 'Cascadia Code', 'Fira Code', 'Consolas', Menlo, Courier, monospace;
} }
// Windows系统专用字体配置 // Windows系统专用字体配置
body[os='windows'] { body[os='windows'] {
--font-family: --font-family:
'Twemoji Country Flags', Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, Oxygen, 'Twemoji Country Flags', Ubuntu, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, Oxygen,
Cantarell, 'Open Sans', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', Cantarell, 'Open Sans', 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji'; 'Segoe UI Symbol', 'Noto Color Emoji';
} }

View File

@@ -1,178 +1,178 @@
import type { MCPServer } from '@renderer/types' import type { MCPServer } from '@renderer/types'
import i18next from 'i18next' import i18next from 'i18next'
// Token storage constants and utilities // Token storage constants and utilities
const TOKEN_STORAGE_KEY = 'tokenLanyunToken' const TOKEN_STORAGE_KEY = 'tokenLanyunToken'
export const TOKENLANYUN_HOST = 'https://mcp.lanyun.net' export const TOKENLANYUN_HOST = 'https://mcp.lanyun.net'
export const LANYUN_MCP_HOST = TOKENLANYUN_HOST + '/mcp/manager/selectListByApiKey' export const LANYUN_MCP_HOST = TOKENLANYUN_HOST + '/mcp/manager/selectListByApiKey'
export const LANYUN_KEY_HOST = TOKENLANYUN_HOST + '/#/manage/apiKey' export const LANYUN_KEY_HOST = TOKENLANYUN_HOST + '/#/manage/apiKey'
export const saveTokenLanYunToken = (token: string): void => { export const saveTokenLanYunToken = (token: string): void => {
localStorage.setItem(TOKEN_STORAGE_KEY, token) localStorage.setItem(TOKEN_STORAGE_KEY, token)
} }
export const getTokenLanYunToken = (): string | null => { export const getTokenLanYunToken = (): string | null => {
return localStorage.getItem(TOKEN_STORAGE_KEY) return localStorage.getItem(TOKEN_STORAGE_KEY)
} }
export const clearTokenLanYunToken = (): void => { export const clearTokenLanYunToken = (): void => {
localStorage.removeItem(TOKEN_STORAGE_KEY) localStorage.removeItem(TOKEN_STORAGE_KEY)
} }
export const hasTokenLanYunToken = (): boolean => { export const hasTokenLanYunToken = (): boolean => {
return !!getTokenLanYunToken() return !!getTokenLanYunToken()
} }
interface TokenLanYunServer { interface TokenLanYunServer {
id: string id: string
/** /**
* locales 字段用于存储多语言信息。 * locales 字段用于存储多语言信息。
* 其中 keylang为语言代码如 'zh', 'en' * 其中 keylang为语言代码如 'zh', 'en'
* value 为该语言下的 name 和 description。 * value 为该语言下的 name 和 description。
* 例如: * 例如:
* { * {
* "zh": { name: "文档处理工具", description: "..." }, * "zh": { name: "文档处理工具", description: "..." },
* "en": { name: "Document Processor", description: "..." } * "en": { name: "Document Processor", description: "..." }
* } * }
*/ */
locales?: { locales?: {
[lang: string]: { [lang: string]: {
description?: string description?: string
name?: string name?: string
} }
} }
chineseName?: string chineseName?: string
description?: string description?: string
operationalUrls?: { url: string }[] operationalUrls?: { url: string }[]
tags?: string[] tags?: string[]
logoUrl?: string logoUrl?: string
} }
interface TokenLanYunSyncResult { interface TokenLanYunSyncResult {
success: boolean success: boolean
message: string message: string
addedServers: MCPServer[] addedServers: MCPServer[]
errorDetails?: string errorDetails?: string
} }
// Function to fetch and process TokenLanYun servers // Function to fetch and process TokenLanYun servers
export const syncTokenLanYunServers = async ( export const syncTokenLanYunServers = async (
token: string, token: string,
existingServers: MCPServer[] existingServers: MCPServer[]
): Promise<TokenLanYunSyncResult> => { ): Promise<TokenLanYunSyncResult> => {
const t = i18next.t const t = i18next.t
try { try {
const response = await fetch(LANYUN_MCP_HOST, { const response = await fetch(LANYUN_MCP_HOST, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${token}` Authorization: `Bearer ${token}`
} }
}) })
// Handle authentication errors // Handle authentication errors
if (response.status === 401 || response.status === 403) { if (response.status === 401 || response.status === 403) {
clearTokenLanYunToken() clearTokenLanYunToken()
return { return {
success: false, success: false,
message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'), message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'),
addedServers: [] addedServers: []
} }
} }
// Handle server errors // Handle server errors
if (response.status === 500 || !response.ok) { if (response.status === 500 || !response.ok) {
return { return {
success: false, success: false,
message: t('settings.mcp.sync.error'), message: t('settings.mcp.sync.error'),
addedServers: [], addedServers: [],
errorDetails: `Status: ${response.status}` errorDetails: `Status: ${response.status}`
} }
} }
// Process successful response // Process successful response
const data = await response.json() const data = await response.json()
if (data.code === 401) { if (data.code === 401) {
return { return {
success: false, success: false,
message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'), message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'),
addedServers: [], addedServers: [],
errorDetails: `Status: ${response.status}` errorDetails: `Status: ${response.status}`
} }
} }
if (data.code === 500) { if (data.code === 500) {
return { return {
success: false, success: false,
message: t('settings.mcp.sync.error'), message: t('settings.mcp.sync.error'),
addedServers: [], addedServers: [],
errorDetails: `Status: ${response.status}` errorDetails: `Status: ${response.status}`
} }
} }
const servers: TokenLanYunServer[] = data.data || [] const servers: TokenLanYunServer[] = data.data || []
if (servers.length === 0) { if (servers.length === 0) {
return { return {
success: true, success: true,
message: t('settings.mcp.sync.noServersAvailable', 'No MCP servers available'), message: t('settings.mcp.sync.noServersAvailable', 'No MCP servers available'),
addedServers: [] addedServers: []
} }
} }
// Transform Token servers to MCP servers format // Transform Token servers to MCP servers format
const addedServers: MCPServer[] = [] const addedServers: MCPServer[] = []
console.log('TokenLanYun servers:', servers) console.log('TokenLanYun servers:', servers)
for (const server of servers) { for (const server of servers) {
try { try {
if (!server.operationalUrls?.[0]?.url) continue if (!server.operationalUrls?.[0]?.url) continue
// If any existing server id contains '@lanyun', clear them before adding new ones // If any existing server id contains '@lanyun', clear them before adding new ones
// if (existingServers.some((s) => s.id.startsWith('@lanyun'))) { // if (existingServers.some((s) => s.id.startsWith('@lanyun'))) {
// for (let i = existingServers.length - 1; i >= 0; i--) { // for (let i = existingServers.length - 1; i >= 0; i--) {
// if (existingServers[i].id.startsWith('@lanyun')) { // if (existingServers[i].id.startsWith('@lanyun')) {
// existingServers.splice(i, 1) // existingServers.splice(i, 1)
// } // }
// } // }
// } // }
// Skip if server already exists after clearing // Skip if server already exists after clearing
if (existingServers.some((s) => s.id === `@lanyun/${server.id}`)) continue if (existingServers.some((s) => s.id === `@lanyun/${server.id}`)) continue
const mcpServer: MCPServer = { const mcpServer: MCPServer = {
id: `@lanyun/${server.id}`, id: `@lanyun/${server.id}`,
name: name:
server.chineseName || server.locales?.zh?.name || server.locales?.en?.name || `LanYun Server ${server.id}`, server.chineseName || server.locales?.zh?.name || server.locales?.en?.name || `LanYun Server ${server.id}`,
description: server.description || '', description: server.description || '',
type: 'sse', type: 'sse',
baseUrl: server.operationalUrls[0].url, baseUrl: server.operationalUrls[0].url,
command: '', command: '',
args: [], args: [],
env: {}, env: {},
isActive: true, isActive: true,
provider: '蓝耘科技', provider: '蓝耘科技',
providerUrl: server.operationalUrls[0].url, providerUrl: server.operationalUrls[0].url,
logoUrl: server.logoUrl || '', logoUrl: server.logoUrl || '',
tags: server.tags ?? (server.chineseName ? [server.chineseName] : []) tags: server.tags ?? (server.chineseName ? [server.chineseName] : [])
} }
addedServers.push(mcpServer) addedServers.push(mcpServer)
} catch (err) { } catch (err) {
console.error('Error processing LanYun server:', err) console.error('Error processing LanYun server:', err)
} }
} }
return { return {
success: true, success: true,
message: t('settings.mcp.sync.success', { count: addedServers.length }), message: t('settings.mcp.sync.success', { count: addedServers.length }),
addedServers addedServers
} }
} catch (error) { } catch (error) {
console.error('TokenLanyun sync error:', error) console.error('TokenLanyun sync error:', error)
return { return {
success: false, success: false,
message: t('settings.mcp.sync.error'), message: t('settings.mcp.sync.error'),
addedServers: [], addedServers: [],
errorDetails: String(error) errorDetails: String(error)
} }
} }
} }

View File

@@ -1,216 +1,216 @@
import Logger from '@renderer/config/logger' import Logger from '@renderer/config/logger'
import { FileMetadata } from '@renderer/types' import { FileMetadata } from '@renderer/types'
import { getFileExtension } from '@renderer/utils' import { getFileExtension } from '@renderer/utils'
// Track last focused component // Track last focused component
type ComponentType = 'inputbar' | 'messageEditor' | null type ComponentType = 'inputbar' | 'messageEditor' | null
let lastFocusedComponent: ComponentType = 'inputbar' // Default to inputbar let lastFocusedComponent: ComponentType = 'inputbar' // Default to inputbar
// 处理函数类型 // 处理函数类型
type PasteHandler = (event: ClipboardEvent) => Promise<boolean> type PasteHandler = (event: ClipboardEvent) => Promise<boolean>
// 处理函数存储 // 处理函数存储
const handlers: { const handlers: {
inputbar?: PasteHandler inputbar?: PasteHandler
messageEditor?: PasteHandler messageEditor?: PasteHandler
} = {} } = {}
// 初始化标志 // 初始化标志
let isInitialized = false let isInitialized = false
/** /**
* 处理粘贴事件的通用服务 * 处理粘贴事件的通用服务
* 处理各种粘贴场景,包括文本和文件 * 处理各种粘贴场景,包括文本和文件
*/ */
export const handlePaste = async ( export const handlePaste = async (
event: ClipboardEvent, event: ClipboardEvent,
isVisionModel: boolean, isVisionModel: boolean,
isGenerateImageModel: boolean, isGenerateImageModel: boolean,
supportExts: string[], supportExts: string[],
setFiles: (updater: (prevFiles: FileMetadata[]) => FileMetadata[]) => void, setFiles: (updater: (prevFiles: FileMetadata[]) => FileMetadata[]) => void,
setText?: (text: string) => void, setText?: (text: string) => void,
pasteLongTextAsFile?: boolean, pasteLongTextAsFile?: boolean,
pasteLongTextThreshold?: number, pasteLongTextThreshold?: number,
text?: string, text?: string,
resizeTextArea?: () => void, resizeTextArea?: () => void,
t?: (key: string) => string t?: (key: string) => string
): Promise<boolean> => { ): Promise<boolean> => {
try { try {
// 优先处理文本粘贴 // 优先处理文本粘贴
const clipboardText = event.clipboardData?.getData('text') const clipboardText = event.clipboardData?.getData('text')
if (clipboardText) { if (clipboardText) {
// 1. 文本粘贴 // 1. 文本粘贴
if (pasteLongTextAsFile && pasteLongTextThreshold && clipboardText.length > pasteLongTextThreshold) { if (pasteLongTextAsFile && pasteLongTextThreshold && clipboardText.length > pasteLongTextThreshold) {
// 长文本直接转文件,阻止默认粘贴 // 长文本直接转文件,阻止默认粘贴
event.preventDefault() event.preventDefault()
const tempFilePath = await window.api.file.createTempFile('pasted_text.txt') const tempFilePath = await window.api.file.createTempFile('pasted_text.txt')
await window.api.file.write(tempFilePath, clipboardText) await window.api.file.write(tempFilePath, clipboardText)
const selectedFile = await window.api.file.get(tempFilePath) const selectedFile = await window.api.file.get(tempFilePath)
if (selectedFile) { if (selectedFile) {
setFiles((prevFiles) => [...prevFiles, selectedFile]) setFiles((prevFiles) => [...prevFiles, selectedFile])
if (setText && text) setText(text) // 保持输入框内容不变 if (setText && text) setText(text) // 保持输入框内容不变
if (resizeTextArea) setTimeout(() => resizeTextArea(), 50) if (resizeTextArea) setTimeout(() => resizeTextArea(), 50)
} }
return true return true
} }
// 短文本走默认粘贴行为,直接返回 // 短文本走默认粘贴行为,直接返回
return false return false
} }
// 2. 文件/图片粘贴(仅在无文本时处理) // 2. 文件/图片粘贴(仅在无文本时处理)
if (event.clipboardData?.files && event.clipboardData.files.length > 0) { if (event.clipboardData?.files && event.clipboardData.files.length > 0) {
event.preventDefault() event.preventDefault()
try { try {
for (const file of event.clipboardData.files) { for (const file of event.clipboardData.files) {
// 使用新的API获取文件路径 // 使用新的API获取文件路径
const filePath = window.api.file.getPathForFile(file) const filePath = window.api.file.getPathForFile(file)
// 如果没有路径,可能是剪贴板中的图像数据 // 如果没有路径,可能是剪贴板中的图像数据
if (!filePath) { if (!filePath) {
// 图像生成也支持图像编辑 // 图像生成也支持图像编辑
if (file.type.startsWith('image/') && (isVisionModel || isGenerateImageModel)) { if (file.type.startsWith('image/') && (isVisionModel || isGenerateImageModel)) {
const tempFilePath = await window.api.file.createTempFile(file.name) const tempFilePath = await window.api.file.createTempFile(file.name)
const arrayBuffer = await file.arrayBuffer() const arrayBuffer = await file.arrayBuffer()
const uint8Array = new Uint8Array(arrayBuffer) const uint8Array = new Uint8Array(arrayBuffer)
await window.api.file.write(tempFilePath, uint8Array) await window.api.file.write(tempFilePath, uint8Array)
const selectedFile = await window.api.file.get(tempFilePath) const selectedFile = await window.api.file.get(tempFilePath)
if (selectedFile) { if (selectedFile) {
setFiles((prevFiles) => [...prevFiles, selectedFile]) setFiles((prevFiles) => [...prevFiles, selectedFile])
break break
} }
} else { } else {
if (t) { if (t) {
window.message.info({ window.message.info({
key: 'file_not_supported', key: 'file_not_supported',
content: t('chat.input.file_not_supported') content: t('chat.input.file_not_supported')
}) })
} }
} }
continue continue
} }
// 有路径的情况 // 有路径的情况
if (supportExts.includes(getFileExtension(filePath))) { if (supportExts.includes(getFileExtension(filePath))) {
const selectedFile = await window.api.file.get(filePath) const selectedFile = await window.api.file.get(filePath)
if (selectedFile) { if (selectedFile) {
setFiles((prevFiles) => [...prevFiles, selectedFile]) setFiles((prevFiles) => [...prevFiles, selectedFile])
} }
} else { } else {
if (t) { if (t) {
window.message.info({ window.message.info({
key: 'file_not_supported', key: 'file_not_supported',
content: t('chat.input.file_not_supported') content: t('chat.input.file_not_supported')
}) })
} }
} }
} }
} catch (error) { } catch (error) {
Logger.error('[PasteService] onPaste:', error) Logger.error('[PasteService] onPaste:', error)
if (t) { if (t) {
window.message.error(t('chat.input.file_error')) window.message.error(t('chat.input.file_error'))
} }
} }
return true return true
} }
// 其他情况默认粘贴 // 其他情况默认粘贴
return false return false
} catch (error) { } catch (error) {
Logger.error('[PasteService] handlePaste error:', error) Logger.error('[PasteService] handlePaste error:', error)
return false return false
} }
} }
/** /**
* 设置最后聚焦的组件 * 设置最后聚焦的组件
*/ */
export const setLastFocusedComponent = (component: ComponentType) => { export const setLastFocusedComponent = (component: ComponentType) => {
lastFocusedComponent = component lastFocusedComponent = component
} }
/** /**
* 获取最后聚焦的组件 * 获取最后聚焦的组件
*/ */
export const getLastFocusedComponent = (): ComponentType => { export const getLastFocusedComponent = (): ComponentType => {
return lastFocusedComponent return lastFocusedComponent
} }
/** /**
* 初始化全局粘贴事件监听 * 初始化全局粘贴事件监听
* 应用启动时只调用一次 * 应用启动时只调用一次
*/ */
export const init = () => { export const init = () => {
if (isInitialized) return if (isInitialized) return
// 添加全局粘贴事件监听 // 添加全局粘贴事件监听
document.addEventListener('paste', async (event) => { document.addEventListener('paste', async (event) => {
await handleGlobalPaste(event) await handleGlobalPaste(event)
}) })
isInitialized = true isInitialized = true
Logger.info('[PasteService] Global paste handler initialized') Logger.info('[PasteService] Global paste handler initialized')
} }
/** /**
* 注册组件的粘贴处理函数 * 注册组件的粘贴处理函数
*/ */
export const registerHandler = (component: ComponentType, handler: PasteHandler) => { export const registerHandler = (component: ComponentType, handler: PasteHandler) => {
if (!component) return if (!component) return
// Only log and update if the handler actually changes // Only log and update if the handler actually changes
if (!handlers[component] || handlers[component] !== handler) { if (!handlers[component] || handlers[component] !== handler) {
handlers[component] = handler handlers[component] = handler
} }
} }
/** /**
* 移除组件的粘贴处理函数 * 移除组件的粘贴处理函数
*/ */
export const unregisterHandler = (component: ComponentType) => { export const unregisterHandler = (component: ComponentType) => {
if (!component || !handlers[component]) return if (!component || !handlers[component]) return
delete handlers[component] delete handlers[component]
} }
/** /**
* 全局粘贴处理函数,根据最后聚焦的组件路由粘贴事件 * 全局粘贴处理函数,根据最后聚焦的组件路由粘贴事件
*/ */
const handleGlobalPaste = async (event: ClipboardEvent): Promise<boolean> => { const handleGlobalPaste = async (event: ClipboardEvent): Promise<boolean> => {
// 如果当前有活动元素且是输入区域,不执行全局处理 // 如果当前有活动元素且是输入区域,不执行全局处理
const activeElement = document.activeElement const activeElement = document.activeElement
if ( if (
activeElement && activeElement &&
(activeElement.tagName === 'INPUT' || (activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' || activeElement.tagName === 'TEXTAREA' ||
activeElement.getAttribute('contenteditable') === 'true') activeElement.getAttribute('contenteditable') === 'true')
) { ) {
return false return false
} }
// 根据最后聚焦的组件调用相应处理程序 // 根据最后聚焦的组件调用相应处理程序
if (lastFocusedComponent && handlers[lastFocusedComponent]) { if (lastFocusedComponent && handlers[lastFocusedComponent]) {
const handler = handlers[lastFocusedComponent] const handler = handlers[lastFocusedComponent]
if (handler) { if (handler) {
return await handler(event) return await handler(event)
} }
} }
// 如果没有匹配的处理程序默认使用inputbar处理 // 如果没有匹配的处理程序默认使用inputbar处理
if (handlers.inputbar) { if (handlers.inputbar) {
const handler = handlers.inputbar const handler = handlers.inputbar
if (handler) { if (handler) {
return await handler(event) return await handler(event)
} }
} }
return false return false
} }
export default { export default {
handlePaste, handlePaste,
setLastFocusedComponent, setLastFocusedComponent,
getLastFocusedComponent, getLastFocusedComponent,
init, init,
registerHandler, registerHandler,
unregisterHandler unregisterHandler
} }