diff --git a/package.json b/package.json index 5c888b0e9..83edefd40 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "pdf-to-img": "^4.4.0", "pdfjs-dist": "4.2.67", "proxy-agent": "^6.5.0", - "selection-hook": "^0.9.20", + "selection-hook": "^0.9.21", "tar": "^7.4.3", "turndown": "^7.2.0", "webdav": "^5.8.0", diff --git a/src/main/configs/SelectionConfig.ts b/src/main/configs/SelectionConfig.ts index f96487c38..1d0a3850c 100644 --- a/src/main/configs/SelectionConfig.ts +++ b/src/main/configs/SelectionConfig.ts @@ -1,8 +1,13 @@ -interface IBlacklist { +interface IFilterList { WINDOWS: string[] MAC?: string[] } +interface IFinetunedList { + EXCLUDE_CLIPBOARD_CURSOR_DETECT: IFilterList + INCLUDE_CLIPBOARD_DELAY_READ: IFilterList +} + /************************************************************************* * 注意:请不要修改此配置,除非你非常清楚其含义、影响和行为的目的 * Note: Do not modify this configuration unless you fully understand its meaning, implications, and intended behavior. @@ -13,7 +18,7 @@ interface IBlacklist { * * Specification: must be all lowercase, need to accurately find the actual running program name *************************************************************************/ -export const SELECTION_PREDEFINED_BLACKLIST: IBlacklist = { +export const SELECTION_PREDEFINED_BLACKLIST: IFilterList = { WINDOWS: [ // Screenshot 'snipaste.exe', @@ -36,6 +41,17 @@ export const SELECTION_PREDEFINED_BLACKLIST: IBlacklist = { 'maya.exe', // CAD 'acad.exe', - 'sldworks.exe' + 'sldworks.exe', + // Remote Desktop + 'mstsc.exe' ] } + +export const SELECTION_FINETUNED_LIST: IFinetunedList = { + EXCLUDE_CLIPBOARD_CURSOR_DETECT: { + WINDOWS: ['acrobat.exe', 'wps.exe', 'cajviewer.exe'] + }, + INCLUDE_CLIPBOARD_DELAY_READ: { + WINDOWS: ['acrobat.exe', 'wps.exe', 'cajviewer.exe', 'foxitphantom.exe'] + } +} diff --git a/src/main/services/SelectionService.ts b/src/main/services/SelectionService.ts index ab4fad7ec..e4f33c2cf 100644 --- a/src/main/services/SelectionService.ts +++ b/src/main/services/SelectionService.ts @@ -1,4 +1,4 @@ -import { SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig' +import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig' import { isDev, isWin } from '@main/constant' import { IpcChannel } from '@shared/IpcChannel' import { BrowserWindow, ipcMain, screen } from 'electron' @@ -157,6 +157,7 @@ export class SelectionService { this.filterList = configManager.getSelectionAssistantFilterList() this.setHookGlobalFilterMode(this.filterMode, this.filterList) + this.setHookFineTunedList() configManager.subscribe(ConfigKeys.SelectionAssistantTriggerMode, (triggerMode: TriggerMode) => { const oldTriggerMode = this.triggerMode @@ -205,9 +206,9 @@ export class SelectionService { if (!this.selectionHook) return const modeMap = { - default: 0, - whitelist: 1, - blacklist: 2 + default: SelectionHook!.FilterMode.DEFAULT, + whitelist: SelectionHook!.FilterMode.INCLUDE_LIST, + blacklist: SelectionHook!.FilterMode.EXCLUDE_LIST } let combinedList: string[] = list @@ -237,6 +238,20 @@ export class SelectionService { } } + private setHookFineTunedList() { + if (!this.selectionHook) return + + this.selectionHook.setFineTunedList( + SelectionHook!.FineTunedListType.EXCLUDE_CLIPBOARD_CURSOR_DETECT, + SELECTION_FINETUNED_LIST.EXCLUDE_CLIPBOARD_CURSOR_DETECT.WINDOWS + ) + + this.selectionHook.setFineTunedList( + SelectionHook!.FineTunedListType.INCLUDE_CLIPBOARD_DELAY_READ, + SELECTION_FINETUNED_LIST.INCLUDE_CLIPBOARD_DELAY_READ.WINDOWS + ) + } + /** * Start the selection service and initialize required windows * @returns {boolean} Success status of service start diff --git a/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx b/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx index cf00802f6..0928df8d6 100644 --- a/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx +++ b/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx @@ -1,8 +1,10 @@ import { nanoid } from '@reduxjs/toolkit' import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' +import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring' import { useMermaid } from '@renderer/hooks/useMermaid' -import { Flex } from 'antd' -import React, { memo, startTransition, useCallback, useEffect, useRef, useState } from 'react' +import { Flex, Spin } from 'antd' +import { debounce } from 'lodash' +import React, { memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react' import styled from 'styled-components' interface Props { @@ -10,12 +12,16 @@ interface Props { setTools?: (value: React.SetStateAction) => void } +/** 预览 Mermaid 图表 + * 通过防抖渲染提供比较统一的体验,减少闪烁。 + * FIXME: 等将来容易判断代码块结束位置时再重构。 + */ const MermaidPreview: React.FC = ({ children, setTools }) => { - const { mermaid, isLoading, error: mermaidError } = useMermaid() + const { mermaid, isLoading: isLoadingMermaid, error: mermaidError } = useMermaid() const mermaidRef = useRef(null) - const [error, setError] = useState(null) const diagramId = useRef(`mermaid-${nanoid(6)}`).current - const errorTimeoutRef = useRef(null) + const [error, setError] = useState(null) + const [isRendering, setIsRendering] = useState(false) // 使用通用图像工具 const { handleZoom, handleCopyImage, handleDownload } = usePreviewToolHandlers(mermaidRef, { @@ -32,55 +38,69 @@ const MermaidPreview: React.FC = ({ children, setTools }) => { handleDownload }) - const render = useCallback(async () => { - try { - if (!children) return + // 实际的渲染函数 + const renderMermaid = useCallback( + async (content: string) => { + if (!content || !mermaidRef.current) return - // 验证语法,提前抛出异常 - await mermaid.parse(children) + try { + setIsRendering(true) - if (!mermaidRef.current) return - const { svg } = await mermaid.render(diagramId, children, mermaidRef.current) + // 验证语法,提前抛出异常 + await mermaid.parse(content) - // 避免不可见时产生 undefined 和 NaN - const fixedSvg = svg.replace(/translate\(undefined,\s*NaN\)/g, 'translate(0, 0)') - mermaidRef.current.innerHTML = fixedSvg + const { svg } = await mermaid.render(diagramId, content, mermaidRef.current) - // 没有语法错误时清除错误记录和定时器 - setError(null) - if (errorTimeoutRef.current) { - clearTimeout(errorTimeoutRef.current) - errorTimeoutRef.current = null - } - } catch (error) { - // 延迟显示错误 - if (errorTimeoutRef.current) clearTimeout(errorTimeoutRef.current) - errorTimeoutRef.current = setTimeout(() => { + // 避免不可见时产生 undefined 和 NaN + const fixedSvg = svg.replace(/translate\(undefined,\s*NaN\)/g, 'translate(0, 0)') + mermaidRef.current.innerHTML = fixedSvg + + // 渲染成功,清除错误记录 + setError(null) + } catch (error) { setError((error as Error).message) - }, 500) - } - }, [children, diagramId, mermaid]) - - // 渲染Mermaid图表 - useEffect(() => { - if (isLoading) return - - startTransition(render) - - // 清理定时器 - return () => { - if (errorTimeoutRef.current) { - clearTimeout(errorTimeoutRef.current) - errorTimeoutRef.current = null + } finally { + setIsRendering(false) } + }, + [diagramId, mermaid] + ) + + // debounce 渲染 + const debouncedRender = useMemo( + () => + debounce((content: string) => { + startTransition(() => renderMermaid(content)) + }, 300), + [renderMermaid] + ) + + // 触发渲染 + useEffect(() => { + if (isLoadingMermaid) return + + if (children) { + setIsRendering(true) + debouncedRender(children) + } else { + debouncedRender.cancel() + setIsRendering(false) } - }, [isLoading, render]) + + return () => { + debouncedRender.cancel() + } + }, [children, isLoadingMermaid, debouncedRender]) + + const isLoading = isLoadingMermaid || isRendering return ( - - {(mermaidError || error) && {mermaidError || error}} - - + }> + + {(mermaidError || error) && {mermaidError || error}} + + + ) } diff --git a/src/renderer/src/providers/AiProvider/GeminiProvider.ts b/src/renderer/src/providers/AiProvider/GeminiProvider.ts index c5c4c2b76..a4e8778f9 100644 --- a/src/renderer/src/providers/AiProvider/GeminiProvider.ts +++ b/src/renderer/src/providers/AiProvider/GeminiProvider.ts @@ -305,7 +305,11 @@ export default class GeminiProvider extends BaseProvider { const effortRatio = EFFORT_RATIO[reasoningEffort] if (effortRatio > 1) { - return {} + return { + thinkingConfig: { + includeThoughts: true + } + } } const { max } = findTokenLimit(model.id) || { max: 0 } diff --git a/yarn.lock b/yarn.lock index 8b1d1266e..6a7fa33b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5694,7 +5694,7 @@ __metadata: remark-math: "npm:^6.0.0" rollup-plugin-visualizer: "npm:^5.12.0" sass: "npm:^1.88.0" - selection-hook: "npm:^0.9.20" + selection-hook: "npm:^0.9.21" shiki: "npm:^3.4.2" string-width: "npm:^7.2.0" styled-components: "npm:^6.1.11" @@ -16543,13 +16543,13 @@ __metadata: languageName: node linkType: hard -"selection-hook@npm:^0.9.20": - version: 0.9.20 - resolution: "selection-hook@npm:0.9.20" +"selection-hook@npm:^0.9.21": + version: 0.9.21 + resolution: "selection-hook@npm:0.9.21" dependencies: node-gyp: "npm:latest" node-gyp-build: "npm:^4.8.4" - checksum: 10c0/2508fc5093a735467bd7082ae84432e2b7d8675d194f9bcf74dab755a63f72ffcbbeee2784297402fbb295faddd590b0333ca290b37047d8af25ed584cd04e60 + checksum: 10c0/e458f8d37c049899e0f9450ca29f882ee01e11b15490f7ee2ddd088843e3e9a201fd5410083faebfbf11e1259a2a74b0b736ff8f1789b626a21ea48ef0ca8705 languageName: node linkType: hard