mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-30 15:59:09 +08:00
Merge branch 'main' of github.com:CherryHQ/cherry-studio into wip/data-refactor
This commit is contained in:
commit
7b633641d1
3
.gitignore
vendored
3
.gitignore
vendored
@ -60,6 +60,9 @@ coverage
|
||||
.vitest-cache
|
||||
vitest.config.*.timestamp-*
|
||||
|
||||
# TypeScript incremental build
|
||||
.tsbuildinfo
|
||||
|
||||
# playwright
|
||||
playwright-report
|
||||
test-results
|
||||
|
||||
30
.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch
vendored
Normal file
30
.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
diff --git a/index.js b/index.js
|
||||
index dc071739e79876dff88e1be06a9168e294222d13..b9df7525c62bdf777e89e732e1b0c81f84d872f2 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -380,7 +380,7 @@ if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
|
||||
}
|
||||
}
|
||||
|
||||
-if (!nativeBinding) {
|
||||
+if (!nativeBinding && process.platform !== 'linux') {
|
||||
if (loadErrors.length > 0) {
|
||||
throw new Error(
|
||||
`Cannot find native binding. ` +
|
||||
@@ -392,6 +392,13 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
-module.exports = nativeBinding
|
||||
-module.exports.OcrAccuracy = nativeBinding.OcrAccuracy
|
||||
-module.exports.recognize = nativeBinding.recognize
|
||||
+if (process.platform === 'linux') {
|
||||
+ module.exports = {OcrAccuracy: {
|
||||
+ Fast: 0,
|
||||
+ Accurate: 1
|
||||
+ }, recognize: () => Promise.resolve({text: '', confidence: 1.0})}
|
||||
+}else{
|
||||
+ module.exports = nativeBinding
|
||||
+ module.exports.OcrAccuracy = nativeBinding.OcrAccuracy
|
||||
+ module.exports.recognize = nativeBinding.recognize
|
||||
+}
|
||||
@ -4,6 +4,8 @@ import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import { resolve } from 'path'
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
|
||||
import pkg from './package.json' assert { type: 'json' }
|
||||
|
||||
const visualizerPlugin = (type: 'renderer' | 'main') => {
|
||||
return process.env[`VISUALIZER_${type.toUpperCase()}`] ? [visualizer({ open: true })] : []
|
||||
}
|
||||
@ -27,7 +29,7 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['@libsql/client', 'bufferutil', 'utf-8-validate'],
|
||||
external: ['bufferutil', 'utf-8-validate', 'electron', ...Object.keys(pkg.dependencies)],
|
||||
output: {
|
||||
manualChunks: undefined, // 彻底禁用代码分割 - 返回 null 强制单文件打包
|
||||
inlineDynamicImports: true // 内联所有动态导入,这是关键配置
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
||||
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
||||
"analyze:main": "VISUALIZER_MAIN=true yarn build",
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"typecheck": "concurrently -n \"node,web\" -c \"cyan,magenta\" \"npm run typecheck:node\" \"npm run typecheck:web\"",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"check:i18n": "tsx scripts/check-i18n.ts",
|
||||
@ -73,7 +73,7 @@
|
||||
"dependencies": {
|
||||
"@libsql/client": "0.14.0",
|
||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||
"@napi-rs/system-ocr": "^1.0.2",
|
||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"jsdom": "26.1.0",
|
||||
@ -200,6 +200,7 @@
|
||||
"cli-progress": "^3.12.0",
|
||||
"code-inspector-plugin": "^0.20.14",
|
||||
"color": "^5.0.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"country-flag-emoji-polyfill": "0.1.8",
|
||||
"dayjs": "^1.11.11",
|
||||
"dexie": "^4.0.8",
|
||||
|
||||
@ -2089,7 +2089,7 @@
|
||||
"Design",
|
||||
"Education"
|
||||
],
|
||||
"prompt": "I want you to act as a Graphviz DOT generator, an expert to create meaningful diagrams. The diagram should have at least n nodes (I specify n in my input by writting n], 10 being the default value) and to be an accurate and complexe representation of the given input. Each node is indexed by a number to reduce the size of the output, should not include any styling, and with layout=neato, overlap=false, node shape=rectangle] as parameters. The code should be valid, bugless and returned on a single line, without any explanation. Provide a clear and organized diagram, the relationships between the nodes have to make sense for an expert of that input. My first diagram is: \"The water cycle 8]\".\n\n",
|
||||
"prompt": "I want you to act as a Graphviz DOT generator, an expert to create meaningful diagrams. The diagram should have at least n nodes (I specify n in my input by writing n], 10 being the default value) and to be an accurate and complex representation of the given input. Each node is indexed by a number to reduce the size of the output, should not include any styling, and with layout=neato, overlap=false, node shape=rectangle] as parameters. The code should be valid, bugless and returned on a single line, without any explanation. Provide a clear and organized diagram, the relationships between the nodes have to make sense for an expert of that input. My first diagram is: \"The water cycle 8]\".\n\n",
|
||||
"description": "Generate meaningful charts."
|
||||
},
|
||||
{
|
||||
@ -2148,7 +2148,7 @@
|
||||
"Career",
|
||||
"Business"
|
||||
],
|
||||
"prompt": "Please acknowledge my following request. Please respond to me as a product manager. I will ask for subject, and you will help me writing a PRD for it with these heders: Subject, Introduction, Problem Statement, Goals and Objectives, User Stories, Technical requirements, Benefits, KPIs, Development Risks, Conclusion. Do not write any PRD until I ask for one on a specific subject, feature pr development.\n\n",
|
||||
"prompt": "Please acknowledge my following request. Please respond to me as a product manager. I will ask for subject, and you will help me writing a PRD for it with these headers: Subject, Introduction, Problem Statement, Goals and Objectives, User Stories, Technical requirements, Benefits, KPIs, Development Risks, Conclusion. Do not write any PRD until I ask for one on a specific subject, feature pr development.\n\n",
|
||||
"description": "Help draft the Product Requirements Document."
|
||||
},
|
||||
{
|
||||
@ -2159,7 +2159,7 @@
|
||||
"Entertainment",
|
||||
"General"
|
||||
],
|
||||
"prompt": "I want you to act as a drunk person. You will only answer like a very drunk person texting and nothing else. Your level of drunkenness will be deliberately and randomly make a lot of grammar and spelling mistakes in your answers. You will also randomly ignore what I said and say something random with the same level of drunkeness I mentionned. Do not write explanations on replies. My first sentence is \"how are you?",
|
||||
"prompt": "I want you to act as a drunk person. You will only answer like a very drunk person texting and nothing else. Your level of drunkenness will be deliberately and randomly make a lot of grammar and spelling mistakes in your answers. You will also randomly ignore what I said and say something random with the same level of drunkenness I mentioned. Do not write explanations on replies. My first sentence is \"how are you?",
|
||||
"description": "Mimic the speech pattern of a drunk person."
|
||||
},
|
||||
{
|
||||
@ -3517,7 +3517,7 @@
|
||||
"Tools",
|
||||
"Copywriting"
|
||||
],
|
||||
"prompt": "I want you to act as a scientific manuscript matcher. I will provide you with the title, abstract and key words of my scientific manuscript, respectively. Your task is analyzing my title, abstract and key words synthetically to find the most related, reputable journals for potential publication of my research based on an analysis of tens of millions of citation connections in database, such as Web of Science, Pubmed, Scopus, ScienceDirect and so on. You only need to provide me with the 15 most suitable journals. Your reply should include the name of journal, the cooresponding match score (The full score is ten). I want you to reply in text-based excel sheet and sort by matching scores in reverse order.\nMy title is \"XXX\" My abstract is \"XXX\" My key words are \"XXX\"\n\n",
|
||||
"prompt": "I want you to act as a scientific manuscript matcher. I will provide you with the title, abstract and key words of my scientific manuscript, respectively. Your task is analyzing my title, abstract and key words synthetically to find the most related, reputable journals for potential publication of my research based on an analysis of tens of millions of citation connections in database, such as Web of Science, Pubmed, Scopus, ScienceDirect and so on. You only need to provide me with the 15 most suitable journals. Your reply should include the name of journal, the corresponding match score (The full score is ten). I want you to reply in text-based excel sheet and sort by matching scores in reverse order.\nMy title is \"XXX\" My abstract is \"XXX\" My key words are \"XXX\"\n\n",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
|
||||
@ -7,15 +7,12 @@ const allArm64 = {
|
||||
'@img/sharp-darwin-arm64': '0.34.3',
|
||||
'@img/sharp-win32-arm64': '0.34.3',
|
||||
'@img/sharp-linux-arm64': '0.34.3',
|
||||
'@img/sharp-linuxmusl-arm64': '0.34.3',
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64': '1.2.0',
|
||||
'@img/sharp-libvips-linux-arm64': '1.2.0',
|
||||
'@img/sharp-libvips-linuxmusl-arm64': '1.2.0',
|
||||
|
||||
'@libsql/darwin-arm64': '0.4.7',
|
||||
'@libsql/linux-arm64-gnu': '0.4.7',
|
||||
'@libsql/linux-arm64-musl': '0.4.7',
|
||||
'@strongtz/win32-arm64-msvc': '0.4.7',
|
||||
|
||||
'@napi-rs/system-ocr-darwin-arm64': '1.0.2',
|
||||
@ -25,16 +22,13 @@ const allArm64 = {
|
||||
const allX64 = {
|
||||
'@img/sharp-darwin-x64': '0.34.3',
|
||||
'@img/sharp-linux-x64': '0.34.3',
|
||||
'@img/sharp-linuxmusl-x64': '0.34.3',
|
||||
'@img/sharp-win32-x64': '0.34.3',
|
||||
|
||||
'@img/sharp-libvips-darwin-x64': '1.2.0',
|
||||
'@img/sharp-libvips-linux-x64': '1.2.0',
|
||||
'@img/sharp-libvips-linuxmusl-x64': '1.2.0',
|
||||
|
||||
'@libsql/darwin-x64': '0.4.7',
|
||||
'@libsql/linux-x64-gnu': '0.4.7',
|
||||
'@libsql/linux-x64-musl': '0.4.7',
|
||||
'@libsql/win32-x64-msvc': '0.4.7',
|
||||
|
||||
'@napi-rs/system-ocr-darwin-x64': '1.0.2',
|
||||
|
||||
@ -32,7 +32,8 @@ class ObsidianVaultService {
|
||||
)
|
||||
} else {
|
||||
// Linux
|
||||
this.obsidianConfigPath = path.join(app.getPath('home'), '.config', 'obsidian', 'obsidian.json')
|
||||
this.obsidianConfigPath = this.resolveLinuxObsidianConfigPath()
|
||||
logger.debug(`Resolved Obsidian config path (linux): ${this.obsidianConfigPath}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,6 +165,57 @@ class ObsidianVaultService {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 Linux 下解析 Obsidian 配置文件路径,兼容多种安装方式。
|
||||
* 优先返回第一个存在的路径;若均不存在,则返回 XDG 默认路径。
|
||||
*/
|
||||
private resolveLinuxObsidianConfigPath(): string {
|
||||
const home = app.getPath('home')
|
||||
const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(home, '.config')
|
||||
|
||||
// 常见目录名与文件名大小写差异做兼容
|
||||
const configDirs = ['obsidian', 'Obsidian']
|
||||
const fileNames = ['obsidian.json', 'Obsidian.json']
|
||||
|
||||
const candidates: string[] = []
|
||||
|
||||
// 1) AppImage/DEB(XDG 标准路径)
|
||||
for (const dir of configDirs) {
|
||||
for (const file of fileNames) {
|
||||
candidates.push(path.join(xdgConfigHome, dir, file))
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Snap 安装:
|
||||
// - 常见:~/snap/obsidian/current/.config/obsidian/obsidian.json
|
||||
// - 兼容:~/snap/obsidian/common/.config/obsidian/obsidian.json
|
||||
for (const dir of configDirs) {
|
||||
for (const file of fileNames) {
|
||||
candidates.push(path.join(home, 'snap', 'obsidian', 'current', '.config', dir, file))
|
||||
candidates.push(path.join(home, 'snap', 'obsidian', 'common', '.config', dir, file))
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Flatpak 安装:~/.var/app/md.obsidian.Obsidian/config/obsidian/obsidian.json
|
||||
for (const dir of configDirs) {
|
||||
for (const file of fileNames) {
|
||||
candidates.push(path.join(home, '.var', 'app', 'md.obsidian.Obsidian', 'config', dir, file))
|
||||
}
|
||||
}
|
||||
|
||||
const existing = candidates.find((p) => {
|
||||
try {
|
||||
return fs.existsSync(p)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (existing) return existing
|
||||
|
||||
return path.join(xdgConfigHome, 'obsidian', 'obsidian.json')
|
||||
}
|
||||
}
|
||||
|
||||
export default ObsidianVaultService
|
||||
|
||||
@ -2,6 +2,7 @@ import { loggerService } from '@logger'
|
||||
import { isLinux } from '@main/constant'
|
||||
import { BuiltinOcrProviderIds, OcrHandler, OcrProvider, OcrResult, SupportedOcrFile } from '@types'
|
||||
|
||||
import { systemOcrService } from './builtin/SystemOcrService'
|
||||
import { tesseractService } from './builtin/TesseractService'
|
||||
|
||||
const logger = loggerService.withContext('OcrService')
|
||||
@ -34,7 +35,4 @@ export const ocrService = new OcrService()
|
||||
// Register built-in providers
|
||||
ocrService.register(BuiltinOcrProviderIds.tesseract, tesseractService.ocr.bind(tesseractService))
|
||||
|
||||
if (!isLinux) {
|
||||
const { systemOcrService } = require('./builtin/SystemOcrService')
|
||||
ocrService.register(BuiltinOcrProviderIds.system, systemOcrService.ocr.bind(systemOcrService))
|
||||
}
|
||||
!isLinux && ocrService.register(BuiltinOcrProviderIds.system, systemOcrService.ocr.bind(systemOcrService))
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { isLinux, isWin } from '@main/constant'
|
||||
import { loadOcrImage } from '@main/utils/ocr'
|
||||
import { OcrAccuracy, recognize } from '@napi-rs/system-ocr'
|
||||
import {
|
||||
ImageFileMetadata,
|
||||
isImageFileMetadata as isImageFileMetadata,
|
||||
@ -20,8 +21,6 @@ export class SystemOcrService extends OcrBaseService {
|
||||
if (isLinux) {
|
||||
return { text: '' }
|
||||
}
|
||||
|
||||
const { OcrAccuracy, recognize } = require('@napi-rs/system-ocr')
|
||||
const buffer = await loadOcrImage(file)
|
||||
const langs = isWin ? options?.langs : undefined
|
||||
const result = await recognize(buffer, OcrAccuracy.Accurate, langs)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ImageFileMetadata } from '@types'
|
||||
import { readFile } from 'fs/promises'
|
||||
import sharp from 'sharp'
|
||||
|
||||
const preprocessImage = async (buffer: Buffer): Promise<Buffer> => {
|
||||
const sharp = require('sharp')
|
||||
return sharp(buffer)
|
||||
.grayscale() // 转为灰度
|
||||
.normalize()
|
||||
|
||||
BIN
src/renderer/src/assets/images/banner.png
Normal file
BIN
src/renderer/src/assets/images/banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 299 KiB |
@ -262,12 +262,13 @@ export const CodeBlockView: React.FC<Props> = memo(({ children, language, onSave
|
||||
) : (
|
||||
<CodeViewer
|
||||
className="source-view"
|
||||
value={children}
|
||||
language={language}
|
||||
onHeightChange={handleHeightChange}
|
||||
expanded={shouldExpand}
|
||||
wrapped={shouldWrap}
|
||||
onHeightChange={handleHeightChange}>
|
||||
{children}
|
||||
</CodeViewer>
|
||||
maxHeight={`${MAX_COLLAPSED_CODE_HEIGHT}px`}
|
||||
/>
|
||||
),
|
||||
[children, codeEditorEnabled, handleHeightChange, language, onSave, shouldExpand, shouldWrap]
|
||||
)
|
||||
|
||||
@ -48,8 +48,6 @@ export interface CodeEditorProps {
|
||||
maxHeight?: string
|
||||
/** Minimum editor height. */
|
||||
minHeight?: string
|
||||
/** Font size that overrides the app setting. */
|
||||
fontSize?: string
|
||||
/** Editor options that extend BasicSetupOptions. */
|
||||
options?: {
|
||||
/**
|
||||
@ -70,6 +68,8 @@ export interface CodeEditorProps {
|
||||
} & BasicSetupOptions
|
||||
/** Additional extensions for CodeMirror. */
|
||||
extensions?: Extension[]
|
||||
/** Font size that overrides the app setting. */
|
||||
fontSize?: number
|
||||
/** Style overrides for the editor, passed directly to CodeMirror's style property. */
|
||||
style?: React.CSSProperties
|
||||
/** CSS class name appended to the default `code-editor` class. */
|
||||
@ -108,9 +108,9 @@ const CodeEditor = ({
|
||||
height,
|
||||
maxHeight,
|
||||
minHeight,
|
||||
fontSize,
|
||||
options,
|
||||
extensions,
|
||||
fontSize: customFontSize,
|
||||
style,
|
||||
className,
|
||||
editable = true,
|
||||
@ -131,7 +131,7 @@ const CodeEditor = ({
|
||||
const enableKeymap = useMemo(() => options?.keymap ?? codeEditor.keymap, [options?.keymap, codeEditor.keymap])
|
||||
|
||||
// 合并 codeEditor 和 options 的 basicSetup,options 优先
|
||||
const customBasicSetup = useMemo(() => {
|
||||
const basicSetup = useMemo(() => {
|
||||
return {
|
||||
lineNumbers: _lineNumbers,
|
||||
...(codeEditor as BasicSetupOptions),
|
||||
@ -139,7 +139,7 @@ const CodeEditor = ({
|
||||
}
|
||||
}, [codeEditor, _lineNumbers, options])
|
||||
|
||||
const customFontSize = useMemo(() => fontSize ?? `${_fontSize - 1}px`, [fontSize, _fontSize])
|
||||
const fontSize = useMemo(() => customFontSize ?? _fontSize - 1, [customFontSize, _fontSize])
|
||||
|
||||
const { activeCmTheme } = useCodeStyle()
|
||||
const initialContent = useRef(options?.stream ? (value ?? '').trimEnd() : (value ?? ''))
|
||||
@ -224,10 +224,10 @@ const CodeEditor = ({
|
||||
foldKeymap: enableKeymap,
|
||||
completionKeymap: enableKeymap,
|
||||
lintKeymap: enableKeymap,
|
||||
...customBasicSetup // override basicSetup
|
||||
...basicSetup // override basicSetup
|
||||
}}
|
||||
style={{
|
||||
fontSize: customFontSize,
|
||||
fontSize,
|
||||
marginTop: 0,
|
||||
borderRadius: 'inherit',
|
||||
...style
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { usePreference } from '@data/hooks/usePreference'
|
||||
import { MAX_COLLAPSED_CODE_HEIGHT } from '@renderer/config/constant'
|
||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||
import { useCodeHighlight } from '@renderer/hooks/useCodeHighlight'
|
||||
import { uuid } from '@renderer/utils'
|
||||
@ -11,13 +10,49 @@ import { ThemedToken } from 'shiki/core'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface CodeViewerProps {
|
||||
/** Code string value. */
|
||||
value: string
|
||||
/**
|
||||
* Code language string.
|
||||
* - Case-insensitive.
|
||||
* - Supports common names: javascript, json, python, etc.
|
||||
* - Supports shiki aliases: c#/csharp, objective-c++/obj-c++/objc++, etc.
|
||||
*/
|
||||
language: string
|
||||
children: string
|
||||
expanded?: boolean
|
||||
wrapped?: boolean
|
||||
/** Fired when the editor height changes. */
|
||||
onHeightChange?: (scrollHeight: number) => void
|
||||
className?: string
|
||||
/**
|
||||
* Height of the scroll container.
|
||||
* Only works when expanded is false.
|
||||
*/
|
||||
height?: string | number
|
||||
/**
|
||||
* Maximum height of the scroll container.
|
||||
* Only works when expanded is false.
|
||||
*/
|
||||
maxHeight?: string | number
|
||||
/** Viewer options. */
|
||||
options?: {
|
||||
/**
|
||||
* Whether to show line numbers.
|
||||
*/
|
||||
lineNumbers?: boolean
|
||||
}
|
||||
/** Font size that overrides the app setting. */
|
||||
fontSize?: number
|
||||
/** CSS class name appended to the default `code-viewer` class. */
|
||||
className?: string
|
||||
/**
|
||||
* Whether the editor is expanded.
|
||||
* If true, the height and maxHeight props are ignored.
|
||||
* @default true
|
||||
*/
|
||||
expanded?: boolean
|
||||
/**
|
||||
* Whether the code lines are wrapped.
|
||||
* @default true
|
||||
*/
|
||||
wrapped?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,20 +61,34 @@ interface CodeViewerProps {
|
||||
* - 使用虚拟滚动和按需高亮,改善页面内有大量长代码块时的响应
|
||||
* - 并发安全
|
||||
*/
|
||||
const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, className, height }: CodeViewerProps) => {
|
||||
const [codeShowLineNumbers] = usePreference('chat.code.show_line_numbers')
|
||||
const [fontSize] = usePreference('chat.message.font_size')
|
||||
const CodeViewer = ({
|
||||
value,
|
||||
language,
|
||||
height,
|
||||
maxHeight,
|
||||
onHeightChange,
|
||||
options,
|
||||
fontSize: customFontSize,
|
||||
className,
|
||||
expanded = true,
|
||||
wrapped = true
|
||||
}: CodeViewerProps) => {
|
||||
const [_lineNumbers] = usePreference('chat.code.show_line_numbers')
|
||||
const [_fontSize] = usePreference('chat.message.font_size')
|
||||
const { getShikiPreProperties, isShikiThemeDark } = useCodeStyle()
|
||||
const shikiThemeRef = useRef<HTMLDivElement>(null)
|
||||
const scrollerRef = useRef<HTMLDivElement>(null)
|
||||
const callerId = useRef(`${Date.now()}-${uuid()}`).current
|
||||
|
||||
const rawLines = useMemo(() => (typeof children === 'string' ? children.trimEnd().split('\n') : []), [children])
|
||||
const fontSize = useMemo(() => customFontSize ?? _fontSize - 1, [customFontSize, _fontSize])
|
||||
const lineNumbers = useMemo(() => options?.lineNumbers ?? _lineNumbers, [options?.lineNumbers, _lineNumbers])
|
||||
|
||||
const rawLines = useMemo(() => (typeof value === 'string' ? value.trimEnd().split('\n') : []), [value])
|
||||
|
||||
// 计算行号数字位数
|
||||
const gutterDigits = useMemo(
|
||||
() => (codeShowLineNumbers ? Math.max(rawLines.length.toString().length, 1) : 0),
|
||||
[codeShowLineNumbers, rawLines.length]
|
||||
() => (lineNumbers ? Math.max(rawLines.length.toString().length, 1) : 0),
|
||||
[lineNumbers, rawLines.length]
|
||||
)
|
||||
|
||||
// 设置 pre 标签属性
|
||||
@ -69,7 +118,7 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla
|
||||
const getScrollElement = useCallback(() => scrollerRef.current, [])
|
||||
const getItemKey = useCallback((index: number) => `${callerId}-${index}`, [callerId])
|
||||
// `line-height: 1.6` 为全局样式,但是为了避免测量误差在这里取整
|
||||
const estimateSize = useCallback(() => Math.round((fontSize - 1) * 1.6), [fontSize])
|
||||
const estimateSize = useCallback(() => Math.round(fontSize * 1.6), [fontSize])
|
||||
|
||||
// 创建 virtualizer 实例
|
||||
const virtualizer = useVirtualizer({
|
||||
@ -106,20 +155,19 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla
|
||||
}, [rawLines.length, onHeightChange])
|
||||
|
||||
return (
|
||||
<div ref={shikiThemeRef} style={height ? { height } : undefined}>
|
||||
<div ref={shikiThemeRef} style={expanded ? undefined : { height }}>
|
||||
<ScrollContainer
|
||||
ref={scrollerRef}
|
||||
className="shiki-scroller"
|
||||
$wrap={wrapped}
|
||||
$expanded={expanded}
|
||||
$expand={expanded}
|
||||
$lineHeight={estimateSize()}
|
||||
$height={height}
|
||||
style={
|
||||
{
|
||||
'--gutter-width': `${gutterDigits}ch`,
|
||||
fontSize: `${fontSize - 1}px`,
|
||||
maxHeight: expanded ? undefined : height ? undefined : MAX_COLLAPSED_CODE_HEIGHT,
|
||||
height: height,
|
||||
fontSize,
|
||||
height: expanded ? undefined : height,
|
||||
maxHeight: expanded ? undefined : maxHeight,
|
||||
overflowY: expanded ? 'hidden' : 'auto'
|
||||
} as React.CSSProperties
|
||||
}>
|
||||
@ -143,7 +191,7 @@ const CodeViewer = ({ children, language, expanded, wrapped, onHeightChange, cla
|
||||
<VirtualizedRow
|
||||
rawLine={rawLines[virtualItem.index]}
|
||||
tokenLine={tokenLines[virtualItem.index]}
|
||||
showLineNumbers={codeShowLineNumbers}
|
||||
showLineNumbers={lineNumbers}
|
||||
index={virtualItem.index}
|
||||
/>
|
||||
</div>
|
||||
@ -227,9 +275,8 @@ VirtualizedRow.displayName = 'VirtualizedRow'
|
||||
|
||||
const ScrollContainer = styled.div<{
|
||||
$wrap?: boolean
|
||||
$expanded?: boolean
|
||||
$expand?: boolean
|
||||
$lineHeight?: number
|
||||
$height?: string | number
|
||||
}>`
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
@ -245,7 +292,7 @@ const ScrollContainer = styled.div<{
|
||||
line-height: ${(props) => props.$lineHeight}px;
|
||||
/* contain 优化 wrap 时滚动性能,will-change 优化 unwrap 时滚动性能 */
|
||||
contain: ${(props) => (props.$wrap ? 'content' : 'none')};
|
||||
will-change: ${(props) => (!props.$wrap && !props.$expanded ? 'transform' : 'auto')};
|
||||
will-change: ${(props) => (!props.$wrap && !props.$expand ? 'transform' : 'auto')};
|
||||
|
||||
.line-number {
|
||||
width: var(--gutter-width, 1.2ch);
|
||||
|
||||
@ -71,8 +71,9 @@ describe('DraggableList', () => {
|
||||
})
|
||||
|
||||
it('should render nothing when list is empty', () => {
|
||||
const emptyList: Array<{ id: string; name: string }> = []
|
||||
render(
|
||||
<DraggableList list={[]} onUpdate={() => {}}>
|
||||
<DraggableList list={emptyList} onUpdate={() => {}}>
|
||||
{(item) => <div data-testid="item">{item.name}</div>}
|
||||
</DraggableList>
|
||||
)
|
||||
|
||||
@ -33,7 +33,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList: mockOriginalList, // 列表未过滤
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -61,7 +61,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -89,7 +89,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList: mockOriginalList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -110,7 +110,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList: mockOriginalList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
@ -136,7 +136,7 @@ describe('useDraggableReorder', () => {
|
||||
originalList: mockOriginalList,
|
||||
filteredList,
|
||||
onUpdate,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
ResponderProvided
|
||||
} from '@hello-pangea/dnd'
|
||||
import { droppableReorder } from '@renderer/utils'
|
||||
import { FC, HTMLAttributes } from 'react'
|
||||
import { HTMLAttributes, Key, useCallback } from 'react'
|
||||
|
||||
interface Props<T> {
|
||||
list: T[]
|
||||
@ -17,23 +17,25 @@ interface Props<T> {
|
||||
listStyle?: React.CSSProperties
|
||||
listProps?: HTMLAttributes<HTMLDivElement>
|
||||
children: (item: T, index: number) => React.ReactNode
|
||||
itemKey?: keyof T | ((item: T) => Key)
|
||||
onUpdate: (list: T[]) => void
|
||||
onDragStart?: OnDragStartResponder
|
||||
onDragEnd?: OnDragEndResponder
|
||||
droppableProps?: Partial<DroppableProps>
|
||||
}
|
||||
|
||||
const DraggableList: FC<Props<any>> = ({
|
||||
function DraggableList<T>({
|
||||
children,
|
||||
list,
|
||||
style,
|
||||
listStyle,
|
||||
listProps,
|
||||
itemKey,
|
||||
droppableProps,
|
||||
onDragStart,
|
||||
onUpdate,
|
||||
onDragEnd
|
||||
}) => {
|
||||
}: Props<T>) {
|
||||
const _onDragEnd = (result: DropResult, provided: ResponderProvided) => {
|
||||
onDragEnd?.(result, provided)
|
||||
if (result.destination) {
|
||||
@ -46,6 +48,17 @@ const DraggableList: FC<Props<any>> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const getId = useCallback(
|
||||
(item: T) => {
|
||||
if (typeof itemKey === 'function') return itemKey(item)
|
||||
if (itemKey) return item[itemKey] as Key
|
||||
if (typeof item === 'string') return item as Key
|
||||
if (item && typeof item === 'object' && 'id' in item) return item.id as Key
|
||||
return undefined
|
||||
},
|
||||
[itemKey]
|
||||
)
|
||||
|
||||
return (
|
||||
<DragDropContext onDragStart={onDragStart} onDragEnd={_onDragEnd}>
|
||||
<Droppable droppableId="droppable" {...droppableProps}>
|
||||
@ -53,9 +66,9 @@ const DraggableList: FC<Props<any>> = ({
|
||||
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
|
||||
<div {...listProps} className="draggable-list-container">
|
||||
{list.map((item, index) => {
|
||||
const id = item.id || item
|
||||
const draggableId = String(getId(item) ?? index)
|
||||
return (
|
||||
<Draggable key={`draggable_${id}_${index}`} draggableId={id} index={index}>
|
||||
<Draggable key={`draggable_${draggableId}`} draggableId={draggableId} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
|
||||
@ -9,7 +9,7 @@ interface UseDraggableReorderParams<T> {
|
||||
/** 用于更新原始列表状态的函数 */
|
||||
onUpdate: (newList: T[]) => void
|
||||
/** 用于从列表项中获取唯一ID的属性名或函数 */
|
||||
idKey: keyof T | ((item: T) => Key)
|
||||
itemKey: keyof T | ((item: T) => Key)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -19,8 +19,16 @@ interface UseDraggableReorderParams<T> {
|
||||
* @param params - { originalList, filteredList, onUpdate, idKey }
|
||||
* @returns 返回可以直接传递给 DraggableVirtualList 的 props: { onDragEnd, itemKey }
|
||||
*/
|
||||
export function useDraggableReorder<T>({ originalList, filteredList, onUpdate, idKey }: UseDraggableReorderParams<T>) {
|
||||
const getId = useCallback((item: T) => (typeof idKey === 'function' ? idKey(item) : (item[idKey] as Key)), [idKey])
|
||||
export function useDraggableReorder<T>({
|
||||
originalList,
|
||||
filteredList,
|
||||
onUpdate,
|
||||
itemKey
|
||||
}: UseDraggableReorderParams<T>) {
|
||||
const getId = useCallback(
|
||||
(item: T) => (typeof itemKey === 'function' ? itemKey(item) : (item[itemKey] as Key)),
|
||||
[itemKey]
|
||||
)
|
||||
|
||||
// 创建从 item ID 到其在 *原始列表* 中索引的映射
|
||||
const itemIndexMap = useMemo(() => {
|
||||
|
||||
@ -208,7 +208,7 @@ const VirtualRow = memo(
|
||||
const draggableId = String(virtualItem.key)
|
||||
return (
|
||||
<Draggable
|
||||
key={`draggable_${draggableId}_${virtualItem.index}`}
|
||||
key={`draggable_${draggableId}`}
|
||||
draggableId={draggableId}
|
||||
isDragDisabled={disabled}
|
||||
index={virtualItem.index}>
|
||||
|
||||
145
src/renderer/src/components/OGCard.tsx
Normal file
145
src/renderer/src/components/OGCard.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import CherryLogo from '@renderer/assets/images/banner.png'
|
||||
import Favicon from '@renderer/components/Icons/FallbackFavicon'
|
||||
import { useMetaDataParser } from '@renderer/hooks/useMetaDataParser'
|
||||
import { Skeleton, Typography } from 'antd'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
const { Title, Paragraph } = Typography
|
||||
|
||||
type Props = {
|
||||
link: string
|
||||
show: boolean
|
||||
}
|
||||
|
||||
export const OGCard = ({ link, show }: Props) => {
|
||||
const openGraph = ['og:title', 'og:description', 'og:image', 'og:imageAlt'] as const
|
||||
const { metadata, isLoading, parseMetadata } = useMetaDataParser(link, openGraph)
|
||||
|
||||
const hasImage = !!metadata['og:image']
|
||||
|
||||
const hostname = useMemo(() => {
|
||||
try {
|
||||
return new URL(link).hostname
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [link])
|
||||
|
||||
useEffect(() => {
|
||||
// use show to lazy loading
|
||||
if (show && isLoading) {
|
||||
parseMetadata()
|
||||
}
|
||||
}, [parseMetadata, isLoading, show])
|
||||
|
||||
if (isLoading) {
|
||||
return <CardSkeleton />
|
||||
}
|
||||
|
||||
return (
|
||||
<PreviewContainer hasImage={hasImage}>
|
||||
{hasImage && (
|
||||
<PreviewImageContainer>
|
||||
<PreviewImage src={metadata['og:image']} alt={metadata['og:imageAlt'] || link} />
|
||||
</PreviewImageContainer>
|
||||
)}
|
||||
{!hasImage && (
|
||||
<PreviewImageContainer>
|
||||
<PreviewImage src={CherryLogo} alt={'no image'} />
|
||||
</PreviewImageContainer>
|
||||
)}
|
||||
|
||||
<PreviewContent>
|
||||
<StyledHyperLink>
|
||||
{hostname && <Favicon hostname={hostname} alt={link} />}
|
||||
<Title
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.2',
|
||||
color: 'var(--color-text)'
|
||||
}}>
|
||||
{metadata['og:title'] || hostname}
|
||||
</Title>
|
||||
</StyledHyperLink>
|
||||
<Paragraph
|
||||
title={metadata['og:description'] || link}
|
||||
ellipsis={{ rows: 2 }}
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
lineHeight: '1.2',
|
||||
color: 'var(--color-text-secondary)'
|
||||
}}>
|
||||
{metadata['og:description'] || link}
|
||||
</Paragraph>
|
||||
</PreviewContent>
|
||||
</PreviewContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const CardSkeleton = () => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Skeleton.Image style={{ width: '100%', height: 140 }} active />
|
||||
<Skeleton
|
||||
paragraph={{
|
||||
rows: 1,
|
||||
style: {
|
||||
margin: '8px 0'
|
||||
}
|
||||
}}
|
||||
active
|
||||
/>
|
||||
</SkeletonContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledHyperLink = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const PreviewContainer = styled.div<{ hasImage?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-background);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
width: 380px;
|
||||
height: 220px;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const PreviewImageContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
min-height: 140px;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const PreviewContent = styled.div`
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const PreviewImage = styled.img`
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
object-fit: cover;
|
||||
`
|
||||
|
||||
const SkeletonContainer = styled.div`
|
||||
width: 380px;
|
||||
height: 220px;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-background);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
gap: 16px;
|
||||
`
|
||||
@ -56,6 +56,7 @@ const MermaidPreview = ({
|
||||
document.body.removeChild(measureEl)
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[diagramId, mermaid, forceRenderKey]
|
||||
)
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ interface UseDndReorderParams<T> {
|
||||
/** 用于更新原始列表状态的函数 */
|
||||
onUpdate: (newList: T[]) => void
|
||||
/** 用于从列表项中获取唯一ID的属性名或函数 */
|
||||
idKey: keyof T | ((item: T) => Key)
|
||||
itemKey: keyof T | ((item: T) => Key)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,8 +18,11 @@ interface UseDndReorderParams<T> {
|
||||
* @param params - { originalList, filteredList, onUpdate, idKey }
|
||||
* @returns 返回可以直接传递给 Sortable 的 onSortEnd 回调
|
||||
*/
|
||||
export function useDndReorder<T>({ originalList, filteredList, onUpdate, idKey }: UseDndReorderParams<T>) {
|
||||
const getId = useCallback((item: T) => (typeof idKey === 'function' ? idKey(item) : (item[idKey] as Key)), [idKey])
|
||||
export function useDndReorder<T>({ originalList, filteredList, onUpdate, itemKey }: UseDndReorderParams<T>) {
|
||||
const getId = useCallback(
|
||||
(item: T) => (typeof itemKey === 'function' ? itemKey(item) : (item[itemKey] as Key)),
|
||||
[itemKey]
|
||||
)
|
||||
|
||||
// 创建从 item ID 到其在 *原始列表* 中索引的映射
|
||||
const itemIndexMap = useMemo(() => {
|
||||
|
||||
@ -739,6 +739,12 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
||||
provider: 'gemini',
|
||||
name: 'Gemini 2.0 Flash',
|
||||
group: 'Gemini 2.0'
|
||||
},
|
||||
{
|
||||
id: 'gemini-2.5-flash-image-preview',
|
||||
provider: 'gemini',
|
||||
name: 'Gemini 2.5 Flash Image',
|
||||
group: 'Gemini 2.5'
|
||||
}
|
||||
],
|
||||
anthropic: [
|
||||
@ -1564,6 +1570,12 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
||||
}
|
||||
],
|
||||
openrouter: [
|
||||
{
|
||||
id: 'google/gemini-2.5-flash-image-preview',
|
||||
provider: 'openrouter',
|
||||
name: 'Google: Gemini 2.5 Flash Image',
|
||||
group: 'google'
|
||||
},
|
||||
{
|
||||
id: 'google/gemini-2.5-flash-preview',
|
||||
provider: 'openrouter',
|
||||
@ -2320,6 +2332,9 @@ export const DEDICATED_IMAGE_MODELS = [
|
||||
'gpt-image-1'
|
||||
]
|
||||
|
||||
// Models that should auto-enable image generation button when selected
|
||||
export const AUTO_ENABLE_IMAGE_MODELS = ['gemini-2.5-flash-image-preview', ...DEDICATED_IMAGE_MODELS]
|
||||
|
||||
export const OPENAI_IMAGE_GENERATION_MODELS = [
|
||||
'o3',
|
||||
'gpt-4o',
|
||||
@ -2340,8 +2355,17 @@ export const GENERATE_IMAGE_MODELS = [
|
||||
]
|
||||
|
||||
export const isDedicatedImageGenerationModel = (model: Model): boolean => {
|
||||
if (!model) return false
|
||||
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
return DEDICATED_IMAGE_MODELS.filter((m) => modelId.includes(m)).length > 0
|
||||
return DEDICATED_IMAGE_MODELS.some((m) => modelId.includes(m))
|
||||
}
|
||||
|
||||
export const isAutoEnableImageGenerationModel = (model: Model): boolean => {
|
||||
if (!model) return false
|
||||
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
return AUTO_ENABLE_IMAGE_MODELS.some((m) => modelId.includes(m))
|
||||
}
|
||||
|
||||
export function isGenerateImageModel(model: Model): boolean {
|
||||
|
||||
78
src/renderer/src/hooks/useMetaDataParser.ts
Normal file
78
src/renderer/src/hooks/useMetaDataParser.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import axios from 'axios'
|
||||
import * as htmlparser2 from 'htmlparser2'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
export function useMetaDataParser<T extends string>(
|
||||
link: string,
|
||||
properties: readonly T[],
|
||||
options?: {
|
||||
timeout?: number
|
||||
}
|
||||
) {
|
||||
const { timeout = 5000 } = options || {}
|
||||
|
||||
const [metadata, setMetadata] = useState<Record<T, string>>({} as Record<T, string>)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
const abortControllerRef = useRef<AbortController | null>(null)
|
||||
|
||||
const parseMetadata = useCallback(async () => {
|
||||
if (!link || !isLoading) return
|
||||
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort()
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
abortControllerRef.current = controller
|
||||
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const response = await axios.get(link, { timeout, signal: controller.signal })
|
||||
|
||||
const htmlContent = response.data
|
||||
const parsedMetadata = {} as Record<T, string>
|
||||
|
||||
const parser = new htmlparser2.Parser({
|
||||
onopentag(tagName, attributes) {
|
||||
if (tagName === 'meta') {
|
||||
const { name: metaName, property: metaProperty, content } = attributes
|
||||
const metaKey = metaName || metaProperty
|
||||
if (!metaKey || !properties.includes(metaKey as T)) return
|
||||
parsedMetadata[metaKey as T] = content
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
parser.parseComplete(htmlContent)
|
||||
|
||||
setMetadata(parsedMetadata)
|
||||
} catch (err) {
|
||||
// Don't set error if request was aborted
|
||||
if (axios.isCancel(err) || (err instanceof Error && err.name === 'AbortError')) {
|
||||
return
|
||||
}
|
||||
setError(err instanceof Error ? err : new Error('Failed to fetch HTML'))
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [isLoading, link, properties, timeout])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort()
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
metadata,
|
||||
isLoading,
|
||||
error,
|
||||
parseMetadata
|
||||
}
|
||||
}
|
||||
@ -1552,6 +1552,7 @@
|
||||
"selected": "Selected tags"
|
||||
},
|
||||
"function_calling": "Function Calling",
|
||||
"invalid_model": "Invalid Model",
|
||||
"no_matches": "No models available",
|
||||
"parameter_name": "Parameter Name",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1552,6 +1552,7 @@
|
||||
"selected": "選択済みのタグ"
|
||||
},
|
||||
"function_calling": "関数呼び出し",
|
||||
"invalid_model": "無効なモデル",
|
||||
"no_matches": "利用可能なモデルがありません",
|
||||
"parameter_name": "パラメータ名",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1552,6 +1552,7 @@
|
||||
"selected": "Выбранные теги"
|
||||
},
|
||||
"function_calling": "Вызов функции",
|
||||
"invalid_model": "Недействительная модель",
|
||||
"no_matches": "Нет доступных моделей",
|
||||
"parameter_name": "Имя параметра",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1552,6 +1552,7 @@
|
||||
"selected": "已选标签"
|
||||
},
|
||||
"function_calling": "函数调用",
|
||||
"invalid_model": "无效模型",
|
||||
"no_matches": "无可用模型",
|
||||
"parameter_name": "参数名称",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1552,6 +1552,7 @@
|
||||
"selected": "已選標籤"
|
||||
},
|
||||
"function_calling": "函數調用",
|
||||
"invalid_model": "無效模型",
|
||||
"no_matches": "無可用模型",
|
||||
"parameter_name": "參數名稱",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1550,6 +1550,7 @@
|
||||
"selected": "Επιλεγμένη ετικέτα"
|
||||
},
|
||||
"function_calling": "Ξεχωριστική Κλήση Συναρτήσεων",
|
||||
"invalid_model": "Μη έγκυρο μοντέλο",
|
||||
"no_matches": "Δεν υπάρχουν διαθέσιμα μοντέλα",
|
||||
"parameter_name": "Όνομα παραμέτρου",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1550,6 +1550,7 @@
|
||||
"selected": "Etiquetas seleccionadas"
|
||||
},
|
||||
"function_calling": "Llamada a función",
|
||||
"invalid_model": "Modelo inválido",
|
||||
"no_matches": "No hay modelos disponibles",
|
||||
"parameter_name": "Nombre del parámetro",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1550,6 +1550,7 @@
|
||||
"selected": "Étiquette sélectionnée"
|
||||
},
|
||||
"function_calling": "Appel de fonction",
|
||||
"invalid_model": "Modèle invalide",
|
||||
"no_matches": "Aucun modèle disponible",
|
||||
"parameter_name": "Nom du paramètre",
|
||||
"parameter_type": {
|
||||
|
||||
@ -1550,6 +1550,7 @@
|
||||
"selected": "Etiqueta selecionada"
|
||||
},
|
||||
"function_calling": "Chamada de função",
|
||||
"invalid_model": "Modelo inválido",
|
||||
"no_matches": "Nenhum modelo disponível",
|
||||
"parameter_name": "Nome do parâmetro",
|
||||
"parameter_type": {
|
||||
|
||||
@ -4,7 +4,7 @@ import { loggerService } from '@logger'
|
||||
import { QuickPanelView, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import {
|
||||
isDedicatedImageGenerationModel,
|
||||
isAutoEnableImageGenerationModel,
|
||||
isGenerateImageModel,
|
||||
isGenerateImageModels,
|
||||
isMandatoryWebSearchModel,
|
||||
@ -783,7 +783,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
if (!isGenerateImageModel(model) && assistant.enableGenerateImage) {
|
||||
updateAssistant({ ...assistant, enableGenerateImage: false })
|
||||
}
|
||||
if (isDedicatedImageGenerationModel(model) && !assistant.enableGenerateImage) {
|
||||
if (isAutoEnableImageGenerationModel(model) && !assistant.enableGenerateImage) {
|
||||
updateAssistant({ ...assistant, enableGenerateImage: true })
|
||||
}
|
||||
}, [assistant, model, updateAssistant])
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import Favicon from '@renderer/components/Icons/FallbackFavicon'
|
||||
import { OGCard } from '@renderer/components/OGCard'
|
||||
import { Popover } from 'antd'
|
||||
import React, { memo, useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import React, { memo, useMemo, useState } from 'react'
|
||||
|
||||
interface HyperLinkProps {
|
||||
children: React.ReactNode
|
||||
href: string
|
||||
}
|
||||
|
||||
const Hyperlink: React.FC<HyperLinkProps> = ({ children, href }) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const link = useMemo(() => {
|
||||
try {
|
||||
return decodeURIComponent(href)
|
||||
@ -16,32 +18,20 @@ const Hyperlink: React.FC<HyperLinkProps> = ({ children, href }) => {
|
||||
}
|
||||
}, [href])
|
||||
|
||||
const hostname = useMemo(() => {
|
||||
try {
|
||||
return new URL(link).hostname
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [link])
|
||||
|
||||
if (!href) return children
|
||||
|
||||
return (
|
||||
<Popover
|
||||
arrow={false}
|
||||
content={
|
||||
<StyledHyperLink>
|
||||
{hostname && <Favicon hostname={hostname} alt={link} />}
|
||||
<span>{link}</span>
|
||||
</StyledHyperLink>
|
||||
}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
content={<OGCard link={link} show={open} />}
|
||||
placement="top"
|
||||
color="var(--color-background)"
|
||||
styles={{
|
||||
body: {
|
||||
border: '1px solid var(--color-border)',
|
||||
padding: '12px',
|
||||
borderRadius: '8px'
|
||||
padding: 0,
|
||||
borderRadius: '8px',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
}}>
|
||||
{children}
|
||||
@ -49,17 +39,4 @@ const Hyperlink: React.FC<HyperLinkProps> = ({ children, href }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const StyledHyperLink = styled.div`
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
span {
|
||||
max-width: min(400px, 70vw);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`
|
||||
|
||||
export default memo(Hyperlink)
|
||||
|
||||
@ -141,7 +141,7 @@ const Markdown: FC<Props> = ({ block, postProcess }) => {
|
||||
} as Partial<Components>
|
||||
}, [block.id])
|
||||
|
||||
if (messageContent.includes('<style>')) {
|
||||
if (/<style\b[^>]*>/i.test(messageContent)) {
|
||||
components.style = MarkdownShadowDOMRenderer as any
|
||||
}
|
||||
|
||||
|
||||
@ -18,17 +18,55 @@ const mocks = vi.hoisted(() => ({
|
||||
),
|
||||
Favicon: ({ hostname, alt }: { hostname: string; alt: string }) => (
|
||||
<img data-testid="favicon" data-hostname={hostname} alt={alt} />
|
||||
)
|
||||
),
|
||||
Typography: {
|
||||
Title: ({ children }: { children: React.ReactNode }) => <div data-testid="title">{children}</div>,
|
||||
Text: ({ children }: { children: React.ReactNode }) => <div data-testid="text">{children}</div>
|
||||
},
|
||||
Skeleton: () => <div data-testid="skeleton">Loading...</div>,
|
||||
useMetaDataParser: vi.fn(() => ({
|
||||
metadata: {},
|
||||
isLoading: false,
|
||||
isLoaded: true,
|
||||
parseMetadata: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('antd', () => ({
|
||||
Popover: mocks.Popover
|
||||
Popover: mocks.Popover,
|
||||
Typography: mocks.Typography,
|
||||
Skeleton: mocks.Skeleton
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/components/Icons/FallbackFavicon', () => ({
|
||||
__esModule: true,
|
||||
default: mocks.Favicon
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/hooks/useMetaDataParser', () => ({
|
||||
useMetaDataParser: mocks.useMetaDataParser
|
||||
}))
|
||||
|
||||
// Mock the OGCard component
|
||||
vi.mock('@renderer/components/OGCard', () => ({
|
||||
OGCard: ({ link }: { link: string; show: boolean }) => {
|
||||
let hostname = ''
|
||||
try {
|
||||
hostname = new URL(link).hostname
|
||||
} catch (e) {
|
||||
// Ignore invalid URLs
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="og-card">
|
||||
{hostname && <mocks.Favicon hostname={hostname} alt={link} />}
|
||||
<mocks.Typography.Title>{hostname}</mocks.Typography.Title>
|
||||
<mocks.Typography.Text>{link}</mocks.Typography.Text>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}))
|
||||
|
||||
describe('Hyperlink', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -69,7 +107,9 @@ describe('Hyperlink', () => {
|
||||
// Content includes decoded url text and favicon with hostname
|
||||
expect(screen.getByTestId('favicon')).toHaveAttribute('data-hostname', 'domain.com')
|
||||
expect(screen.getByTestId('favicon')).toHaveAttribute('alt', 'https://domain.com/a b')
|
||||
expect(screen.getByTestId('popover-content')).toHaveTextContent('https://domain.com/a b')
|
||||
// The title should show hostname and text should show the full URL
|
||||
expect(screen.getByTestId('title')).toHaveTextContent('domain.com')
|
||||
expect(screen.getByTestId('text')).toHaveTextContent('https://domain.com/a b')
|
||||
})
|
||||
|
||||
it('should not render favicon when URL parsing fails (invalid url)', () => {
|
||||
@ -81,7 +121,9 @@ describe('Hyperlink', () => {
|
||||
|
||||
// decodeURIComponent succeeds => "not/url" is displayed
|
||||
expect(screen.queryByTestId('favicon')).toBeNull()
|
||||
expect(screen.getByTestId('popover-content')).toHaveTextContent('not/url')
|
||||
// Since there's no hostname and no og:title, title shows empty, but text shows the URL
|
||||
expect(screen.getByTestId('title')).toBeEmptyDOMElement()
|
||||
expect(screen.getByTestId('text')).toHaveTextContent('not/url')
|
||||
})
|
||||
|
||||
it('should not render favicon for non-http(s) scheme without hostname (mailto:)', () => {
|
||||
@ -93,6 +135,8 @@ describe('Hyperlink', () => {
|
||||
|
||||
// Decoded to mailto:test@example.com, hostname is empty => no favicon
|
||||
expect(screen.queryByTestId('favicon')).toBeNull()
|
||||
expect(screen.getByTestId('popover-content')).toHaveTextContent('mailto:test@example.com')
|
||||
// Since there's no hostname and no og:title, title shows empty, but text shows the decoded URL
|
||||
expect(screen.getByTestId('title')).toBeEmptyDOMElement()
|
||||
expect(screen.getByTestId('text')).toHaveTextContent('mailto:test@example.com')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,42 +1,34 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Hyperlink > should match snapshot for normal url 1`] = `
|
||||
.c0 {
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c0 span {
|
||||
max-width: min(400px,70vw);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
data-arrow="false"
|
||||
data-color="var(--color-background)"
|
||||
data-placement="top"
|
||||
data-styles="{"body":{"border":"1px solid var(--color-border)","padding":"12px","borderRadius":"8px"}}"
|
||||
data-styles="{"body":{"padding":0,"borderRadius":"8px","overflow":"hidden"}}"
|
||||
data-testid="popover"
|
||||
>
|
||||
<div
|
||||
data-testid="popover-content"
|
||||
>
|
||||
<div
|
||||
class="c0"
|
||||
data-testid="og-card"
|
||||
>
|
||||
<img
|
||||
alt="https://example.com/path with space"
|
||||
data-hostname="example.com"
|
||||
data-testid="favicon"
|
||||
/>
|
||||
<span>
|
||||
<div
|
||||
data-testid="title"
|
||||
>
|
||||
example.com
|
||||
</div>
|
||||
<div
|
||||
data-testid="text"
|
||||
>
|
||||
https://example.com/path with space
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@ -156,9 +156,12 @@ const Container = styled.div`
|
||||
flex-direction: column;
|
||||
max-width: var(--assistants-width);
|
||||
min-width: var(--assistants-width);
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
|
||||
&.right {
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
}
|
||||
|
||||
[navbar-position='left'] & {
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
@ -4,8 +4,9 @@ import { isLocalAi } from '@renderer/config/env'
|
||||
import { isEmbeddingModel, isRerankModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { getProviderName } from '@renderer/services/ProviderService'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { Assistant, Model } from '@renderer/types'
|
||||
import { Button } from 'antd'
|
||||
import { Button, Tag } from 'antd'
|
||||
import { ChevronsUpDown } from 'lucide-react'
|
||||
import { FC, useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -19,6 +20,7 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
const { model, updateAssistant } = useAssistant(assistant.id)
|
||||
const { t } = useTranslation()
|
||||
const timerRef = useRef<NodeJS.Timeout>(undefined)
|
||||
const provider = useAppSelector((state) => state.llm.providers.find((p) => p.id === model?.provider))
|
||||
|
||||
const modelFilter = (model: Model) => !isEmbeddingModel(model) && !isRerankModel(model)
|
||||
|
||||
@ -60,6 +62,7 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
</ModelName>
|
||||
</ButtonContent>
|
||||
<ChevronsUpDown size={14} color="var(--color-icon)" />
|
||||
{!provider && <Tag color="error">{t('models.invalid_model')}</Tag>}
|
||||
</DropdownButton>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { loggerService } from '@logger'
|
||||
import Ellipsis from '@renderer/components/Ellipsis'
|
||||
import { useFiles } from '@renderer/hooks/useFiles'
|
||||
import { useKnowledge } from '@renderer/hooks/useKnowledge'
|
||||
import FileItem from '@renderer/pages/files/FileItem'
|
||||
import StatusIcon from '@renderer/pages/knowledge/components/StatusIcon'
|
||||
@ -48,6 +49,7 @@ const getDisplayTime = (item: KnowledgeItem) => {
|
||||
const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap, preprocessMap }) => {
|
||||
const { t } = useTranslation()
|
||||
const [windowHeight, setWindowHeight] = useState(window.innerHeight)
|
||||
const { onSelectFile, selecting } = useFiles({ extensions: fileTypes })
|
||||
|
||||
const { base, fileItems, addFiles, refreshItem, removeItem, getProcessingStatus } = useKnowledge(
|
||||
selectedBase.id || ''
|
||||
@ -71,19 +73,12 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
|
||||
return null
|
||||
}
|
||||
|
||||
const handleAddFile = () => {
|
||||
if (disabled) {
|
||||
const handleAddFile = async () => {
|
||||
if (disabled || selecting) {
|
||||
return
|
||||
}
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.multiple = true
|
||||
input.accept = fileTypes.join(',')
|
||||
input.onchange = (e) => {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
files && handleDrop(Array.from(files))
|
||||
}
|
||||
input.click()
|
||||
const selectedFiles = await onSelectFile({ multipleSelections: true })
|
||||
processFiles(selectedFiles)
|
||||
}
|
||||
|
||||
const handleDrop = async (files: File[]) => {
|
||||
@ -118,8 +113,14 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
|
||||
}
|
||||
})
|
||||
.filter(({ ext }) => fileTypes.includes(ext))
|
||||
const uploadedFiles = await FileManager.uploadFiles(_files)
|
||||
logger.debug('uploadedFiles', uploadedFiles)
|
||||
processFiles(_files)
|
||||
}
|
||||
}
|
||||
|
||||
const processFiles = async (files: FileMetadata[]) => {
|
||||
logger.debug('processFiles', files)
|
||||
if (files.length > 0) {
|
||||
const uploadedFiles = await FileManager.uploadFiles(files)
|
||||
addFiles(uploadedFiles)
|
||||
}
|
||||
}
|
||||
@ -150,16 +151,23 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
|
||||
</ItemHeader>
|
||||
|
||||
<ItemFlexColumn>
|
||||
<Dragger
|
||||
showUploadList={false}
|
||||
customRequest={({ file }) => handleDrop([file as File])}
|
||||
multiple={true}
|
||||
accept={fileTypes.join(',')}>
|
||||
<p className="ant-upload-text">{t('knowledge.drag_file')}</p>
|
||||
<p className="ant-upload-hint">
|
||||
{t('knowledge.file_hint', { file_types: 'TXT, MD, HTML, PDF, DOCX, PPTX, XLSX, EPUB...' })}
|
||||
</p>
|
||||
</Dragger>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleAddFile()
|
||||
}}>
|
||||
<Dragger
|
||||
showUploadList={false}
|
||||
customRequest={({ file }) => handleDrop([file as File])}
|
||||
multiple={true}
|
||||
accept={fileTypes.join(',')}
|
||||
openFileDialogOnClick={false}>
|
||||
<p className="ant-upload-text">{t('knowledge.drag_file')}</p>
|
||||
<p className="ant-upload-hint">
|
||||
{t('knowledge.file_hint', { file_types: 'TXT, MD, HTML, PDF, DOCX, PPTX, XLSX, EPUB...' })}
|
||||
</p>
|
||||
</Dragger>
|
||||
</div>
|
||||
{fileItems.length === 0 ? (
|
||||
<KnowledgeEmptyView />
|
||||
) : (
|
||||
|
||||
@ -2,7 +2,6 @@ import 'emoji-picker-element'
|
||||
|
||||
import { CloseCircleFilled } from '@ant-design/icons'
|
||||
import CodeEditor from '@renderer/components/CodeEditor'
|
||||
import CodeViewer from '@renderer/components/CodeViewer'
|
||||
import EmojiPicker from '@renderer/components/EmojiPicker'
|
||||
import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout'
|
||||
import { RichEditorRef } from '@renderer/components/RichEditor/types'
|
||||
@ -14,6 +13,7 @@ import { Button, Input, Popover } from 'antd'
|
||||
import { Edit, HelpCircle, Save } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SettingDivider } from '..'
|
||||
@ -122,7 +122,9 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
|
||||
<TextAreaContainer>
|
||||
<RichEditorContainer>
|
||||
{showPreview ? (
|
||||
<CodeViewer children={processedPrompt} language="markdown" expanded={true} height="100%" />
|
||||
<MarkdownContainer>
|
||||
<ReactMarkdown>{processedPrompt || prompt}</ReactMarkdown>
|
||||
</MarkdownContainer>
|
||||
) : (
|
||||
<CodeEditor
|
||||
value={prompt}
|
||||
@ -214,4 +216,10 @@ const RichEditorContainer = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const MarkdownContainer = styled.div.attrs({ className: 'markdown' })`
|
||||
height: 100%;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
`
|
||||
|
||||
export default AssistantPromptSettings
|
||||
|
||||
@ -47,12 +47,14 @@ const OcrImageSettings = ({ setProvider }: Props) => {
|
||||
}))
|
||||
}, [getOcrProviderName, imageProviders, platformSupport])
|
||||
|
||||
const isSystem = imageProvider.id === BuiltinOcrProviderIds.system
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.tool.ocr.image_provider')}</SettingRowTitle>
|
||||
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
||||
{!platformSupport && <ErrorTag message={t('settings.tool.ocr.error.not_system')} />}
|
||||
{!platformSupport && isSystem && <ErrorTag message={t('settings.tool.ocr.error.not_system')} />}
|
||||
<Select
|
||||
value={imageProvider.id}
|
||||
style={{ width: '200px' }}
|
||||
|
||||
@ -55,7 +55,7 @@ const McpServersList: FC = () => {
|
||||
originalList: mcpServers,
|
||||
filteredList: filteredMcpServers,
|
||||
onUpdate: updateMcpServers,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
@ -321,7 +321,7 @@ const ProviderList: FC = () => {
|
||||
originalList: providers,
|
||||
filteredList: filteredProviders,
|
||||
onUpdate: updateProviders,
|
||||
idKey: 'id'
|
||||
itemKey: 'id'
|
||||
})
|
||||
|
||||
const handleDragStart = useCallback(() => {
|
||||
|
||||
@ -136,8 +136,15 @@ export function getAssistantProvider(assistant: Assistant): Provider {
|
||||
|
||||
export function getProviderByModel(model?: Model): Provider {
|
||||
const providers = store.getState().llm.providers
|
||||
const providerId = model ? model.provider : getDefaultProvider().id
|
||||
return providers.find((p) => p.id === providerId) as Provider
|
||||
const provider = providers.find((p) => p.id === model?.provider)
|
||||
|
||||
if (!provider) {
|
||||
const defaultProvider = providers.find((p) => p.id === getDefaultModel()?.provider)
|
||||
const cherryinProvider = providers.find((p) => p.id === 'cherryin')
|
||||
return defaultProvider || cherryinProvider || providers[0]
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
export function getProviderByModelId(modelId?: string) {
|
||||
|
||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 144,
|
||||
version: 145,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs']
|
||||
// migrate
|
||||
},
|
||||
|
||||
@ -2335,8 +2335,21 @@
|
||||
// logger.error('migrate 144 error', error as Error)
|
||||
// return state
|
||||
// }
|
||||
// },
|
||||
// '145': (state: RootState) => {
|
||||
// try {
|
||||
// if (state.settings) {
|
||||
// if (state.settings.showMessageOutline === undefined || state.settings.showMessageOutline === null) {
|
||||
// state.settings.showMessageOutline = false
|
||||
// }
|
||||
// }
|
||||
// return state
|
||||
// } catch (error) {
|
||||
// logger.error('migrate 145 error', error as Error)
|
||||
// return state
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 注意:添加新迁移时,记得同时更新 persistReducer
|
||||
// // file://./index.ts
|
||||
|
||||
@ -220,7 +220,7 @@ export interface SettingsState {
|
||||
navbarPosition: 'left' | 'top'
|
||||
// API Server
|
||||
apiServer: ApiServerConfig
|
||||
showMessageOutline?: boolean
|
||||
showMessageOutline: boolean
|
||||
// Notes Related
|
||||
showWorkspace: boolean
|
||||
}
|
||||
|
||||
@ -53,8 +53,8 @@ export const Text: React.FC<React.HTMLAttributes<HTMLSpanElement>> = ({ style, c
|
||||
)
|
||||
|
||||
// VStack 组件
|
||||
export const VStack: React.FC<{ grap?: number; align?: string; children: React.ReactNode }> = ({
|
||||
grap = 5,
|
||||
export const VStack: React.FC<{ gap?: number; align?: string; children: React.ReactNode }> = ({
|
||||
gap = 5,
|
||||
align = 'stretch',
|
||||
children,
|
||||
...props
|
||||
@ -64,7 +64,7 @@ export const VStack: React.FC<{ grap?: number; align?: string; children: React.R
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: align,
|
||||
gap: `${grap}px`
|
||||
gap: `${gap}px`
|
||||
}}
|
||||
{...props}>
|
||||
{children}
|
||||
@ -88,8 +88,8 @@ export const GridItem: React.FC<
|
||||
)
|
||||
|
||||
// HStack 组件
|
||||
export const HStack: React.FC<{ grap?: number; children: React.ReactNode; style?: React.CSSProperties }> = ({
|
||||
grap,
|
||||
export const HStack: React.FC<{ gap?: number; children: React.ReactNode; style?: React.CSSProperties }> = ({
|
||||
gap,
|
||||
children,
|
||||
style,
|
||||
...props
|
||||
@ -99,7 +99,7 @@ export const HStack: React.FC<{ grap?: number; children: React.ReactNode; style?
|
||||
display: 'inline-flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: grap ? `${grap}px` : '5px',
|
||||
gap: gap ? `${gap}px` : '5px',
|
||||
...style
|
||||
}}
|
||||
{...props}>
|
||||
|
||||
@ -59,7 +59,7 @@ const TreeNode: React.FC<TreeNodeProps> = ({ node, handleClick, treeData, paddin
|
||||
handleClick(node.id)
|
||||
}}>
|
||||
<GridItem colSpan={8} style={{ paddingLeft: `${paddingLeft}px`, textAlign: 'left' }}>
|
||||
<HStack grap={2}>
|
||||
<HStack gap={2}>
|
||||
<IconButton
|
||||
aria-label="Toggle"
|
||||
aria-expanded={isOpen ? true : false}
|
||||
|
||||
@ -156,7 +156,7 @@ export const TracePage: React.FC<TracePageProp> = ({ topicId, traceId, modelName
|
||||
<SimpleGrid columns={1} templateColumns="1fr">
|
||||
<Box padding={0} className="scroll-container">
|
||||
{showList ? (
|
||||
<VStack grap={1} align="start">
|
||||
<VStack gap={1} align="start">
|
||||
{spans.length === 0 ? (
|
||||
<Text>没有找到Trace信息</Text>
|
||||
) : (
|
||||
|
||||
@ -103,7 +103,7 @@ export const isBuiltinOcrProvider = (p: OcrProvider): p is BuiltinOcrProvider =>
|
||||
return isBuiltinOcrProviderId(p.id)
|
||||
}
|
||||
|
||||
// Not sure compatiable api endpoint exists. May not support custom ocr provider
|
||||
// Not sure compatible api endpoint exists. May not support custom ocr provider
|
||||
export type CustomOcrProvider = OcrProvider & {
|
||||
id: Exclude<string, BuiltinOcrProviderId>
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ describe('markdownConverter', () => {
|
||||
|
||||
it('should convert task list HTML back to Markdown with label', () => {
|
||||
const html =
|
||||
'<ul data-type="taskList" class="task-list"><li data-type="taskItem" class="task-list-item" data-checked="false"><label><input type="checkbox"> abcd</label></li><li data-type="taskItem" class="task-list-item" data-checked="true"><label><input type="checkbox" checked> efgh</lable></li></ul>'
|
||||
'<ul data-type="taskList" class="task-list"><li data-type="taskItem" class="task-list-item" data-checked="false"><label><input type="checkbox"> abcd</label></li><li data-type="taskItem" class="task-list-item" data-checked="true"><label><input type="checkbox" checked> efgh</label></li></ul>'
|
||||
const result = htmlToMarkdown(html)
|
||||
expect(result).toBe('- [ ] abcd\n\n- [x] efgh')
|
||||
})
|
||||
@ -361,7 +361,7 @@ describe('markdownConverter', () => {
|
||||
})
|
||||
|
||||
describe('markdown image', () => {
|
||||
it('should convert markdown iamge to HTML img tag', () => {
|
||||
it('should convert markdown image to HTML img tag', () => {
|
||||
const markdown = ''
|
||||
const result = markdownToHtml(markdown)
|
||||
expect(result).toBe('<p><img src="train.jpg" alt="foo" /></p>\n')
|
||||
|
||||
@ -182,59 +182,200 @@ export async function captureScrollableIframe(
|
||||
iframeRef: React.RefObject<HTMLIFrameElement | null>
|
||||
): Promise<HTMLCanvasElement | undefined> {
|
||||
const iframe = iframeRef.current
|
||||
if (!iframe) return Promise.resolve(undefined)
|
||||
if (!iframe?.contentDocument?.defaultView) return undefined
|
||||
|
||||
const doc = iframe.contentDocument
|
||||
const win = doc?.defaultView
|
||||
if (!doc || !win) return Promise.resolve(undefined)
|
||||
const win = iframe.contentWindow!
|
||||
|
||||
// 等待两帧渲染稳定
|
||||
await new Promise<void>((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())))
|
||||
// 禁用动画以确保捕获静态状态
|
||||
const disableAnimations = () => {
|
||||
const style = doc.createElement('style')
|
||||
style.textContent = `*, *::before, *::after {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
// transform: none !important;
|
||||
}`
|
||||
doc.head.appendChild(style)
|
||||
return style
|
||||
}
|
||||
|
||||
// 触发懒加载资源尽快加载
|
||||
doc.querySelectorAll('img[loading="lazy"]').forEach((img) => img.setAttribute('loading', 'eager'))
|
||||
await new Promise((r) => setTimeout(r, 200))
|
||||
// 内联字体以避免跨域问题
|
||||
const inlineFonts = async () => {
|
||||
const fontFaceRegex = /@font-face[\s\S]*?\}/g
|
||||
const fontUrlRegex = /url\((['"]?)([^)"']+)\1\)/g
|
||||
const fontExtRegex = /\.(woff2?|ttf|otf)(\?|#|$)/i
|
||||
|
||||
const de = doc.documentElement
|
||||
const b = doc.body
|
||||
const fetchAsDataUrl = async (url: string): Promise<string> => {
|
||||
try {
|
||||
const res = await fetch(url, { mode: 'cors', credentials: 'omit' })
|
||||
if (!res.ok) return url
|
||||
const blob = await res.blob()
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => resolve(reader.result as string)
|
||||
reader.onerror = () => resolve(url)
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
// 计算完整尺寸
|
||||
const totalWidth = Math.max(b.scrollWidth, de.scrollWidth, b.clientWidth, de.clientWidth)
|
||||
const totalHeight = Math.max(b.scrollHeight, de.scrollHeight, b.clientHeight, de.clientHeight)
|
||||
const processCss = async (cssText: string, baseUrl: string): Promise<string[]> => {
|
||||
const fontBlocks: string[] = []
|
||||
let match: RegExpExecArray | null
|
||||
|
||||
logger.verbose('The iframe to be captured has size:', { totalWidth, totalHeight })
|
||||
while ((match = fontFaceRegex.exec(cssText)) !== null) {
|
||||
let block = match[0]
|
||||
const fontUrls: Array<[string, string]> = []
|
||||
|
||||
// 按比例缩放以不超过上限
|
||||
const MAX = 32767
|
||||
const maxSide = Math.max(totalWidth, totalHeight)
|
||||
const scale = maxSide > MAX ? MAX / maxSide : 1
|
||||
const pixelRatio = (win.devicePixelRatio || 1) * scale
|
||||
let urlMatch: RegExpExecArray | null
|
||||
fontUrlRegex.lastIndex = 0
|
||||
while ((urlMatch = fontUrlRegex.exec(block)) !== null) {
|
||||
const url = urlMatch[2]
|
||||
if (!url.startsWith('data:') && fontExtRegex.test(url)) {
|
||||
try {
|
||||
const absoluteUrl = new URL(url, baseUrl).href
|
||||
fontUrls.push([urlMatch[0], absoluteUrl])
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bg = win.getComputedStyle(b).backgroundColor || '#ffffff'
|
||||
const fg = win.getComputedStyle(b).color || '#000000'
|
||||
// 并行处理所有字体URL
|
||||
const dataUrls = await Promise.all(
|
||||
fontUrls.map(async ([original, url]) => {
|
||||
const dataUrl = await fetchAsDataUrl(url)
|
||||
return [original, `url(${dataUrl})`] as const
|
||||
})
|
||||
)
|
||||
|
||||
dataUrls.forEach(([original, replacement]) => {
|
||||
block = block.replace(original, replacement)
|
||||
})
|
||||
|
||||
fontBlocks.push(block)
|
||||
}
|
||||
|
||||
return fontBlocks
|
||||
}
|
||||
|
||||
const allFontBlocks: string[] = []
|
||||
|
||||
// 处理外部样式表
|
||||
const externalSheets = doc.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')
|
||||
await Promise.all(
|
||||
Array.from(externalSheets).map(async (link) => {
|
||||
if (!link.href) return
|
||||
try {
|
||||
const res = await fetch(link.href, { mode: 'cors', credentials: 'omit' })
|
||||
if (res.ok) {
|
||||
const cssText = await res.text()
|
||||
const blocks = await processCss(cssText, link.href)
|
||||
allFontBlocks.push(...blocks)
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 处理内联样式
|
||||
const inlineStyles = doc.querySelectorAll('style')
|
||||
await Promise.all(
|
||||
Array.from(inlineStyles).map(async (style) => {
|
||||
const cssText = style.textContent || ''
|
||||
const blocks = await processCss(cssText, doc.baseURI)
|
||||
allFontBlocks.push(...blocks)
|
||||
})
|
||||
)
|
||||
|
||||
return allFontBlocks.join('\n')
|
||||
}
|
||||
|
||||
const animationStyle = disableAnimations()
|
||||
let injectedFontStyle: HTMLStyleElement | null = null
|
||||
|
||||
const ensureFontStyle = (css: string): HTMLStyleElement => {
|
||||
const EXISTING = doc.head.querySelector('style[data-cs-inline-fonts="true"]') as HTMLStyleElement | null
|
||||
if (EXISTING) {
|
||||
if (css && css.trim()) {
|
||||
EXISTING.textContent = `${EXISTING.textContent || ''}\n${css}`
|
||||
}
|
||||
return EXISTING
|
||||
}
|
||||
const style = doc.createElement('style')
|
||||
style.setAttribute('data-cs-inline-fonts', 'true')
|
||||
style.textContent = css
|
||||
doc.head.appendChild(style)
|
||||
return style
|
||||
}
|
||||
|
||||
try {
|
||||
const canvas = await htmlToImage.toCanvas(de, {
|
||||
backgroundColor: bg,
|
||||
// 等待渲染稳定
|
||||
await new Promise((r) => win.requestAnimationFrame(() => win.requestAnimationFrame(() => r(null))))
|
||||
|
||||
// 强制加载懒加载图片
|
||||
doc.querySelectorAll('img[loading="lazy"]').forEach((img) => img.setAttribute('loading', 'eager'))
|
||||
|
||||
// 获取字体CSS
|
||||
const fontEmbedCSS = await inlineFonts()
|
||||
|
||||
// 将字体 CSS 注入到 iframe 文档中,确保注册到 FontFaceSet
|
||||
if (fontEmbedCSS && fontEmbedCSS.trim().length > 0) {
|
||||
injectedFontStyle = ensureFontStyle(fontEmbedCSS)
|
||||
// 访问一次以避免被标记为未使用
|
||||
if (injectedFontStyle.parentNode == null) {
|
||||
doc.head.appendChild(injectedFontStyle)
|
||||
}
|
||||
}
|
||||
|
||||
// 等待字体就绪,避免序列化时回退到系统字体
|
||||
await Promise.race([
|
||||
(doc as any).fonts?.ready ?? Promise.resolve(),
|
||||
new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
])
|
||||
|
||||
// 计算尺寸
|
||||
const { documentElement: de, body: b } = doc
|
||||
const totalWidth = Math.max(b.scrollWidth, de.scrollWidth, b.clientWidth, de.clientWidth)
|
||||
const totalHeight = Math.max(b.scrollHeight, de.scrollHeight, b.clientHeight, de.clientHeight)
|
||||
|
||||
logger.verbose('Capturing iframe:', { totalWidth, totalHeight })
|
||||
|
||||
// 限制最大尺寸,按比例缩放
|
||||
const MAX_SIZE = 32767
|
||||
const scale = Math.min(1, MAX_SIZE / Math.max(totalWidth, totalHeight))
|
||||
const pixelRatio = (win.devicePixelRatio || 1) * scale
|
||||
|
||||
const styles = win.getComputedStyle(b)
|
||||
const backgroundColor = styles.backgroundColor || '#ffffff'
|
||||
const color = styles.color || '#000000'
|
||||
|
||||
return await htmlToImage.toCanvas(de, {
|
||||
fontEmbedCSS,
|
||||
backgroundColor,
|
||||
cacheBust: true,
|
||||
pixelRatio,
|
||||
skipAutoScale: true,
|
||||
width: Math.floor(totalWidth),
|
||||
height: Math.floor(totalHeight),
|
||||
style: {
|
||||
backgroundColor: bg,
|
||||
color: fg,
|
||||
backgroundColor,
|
||||
color,
|
||||
width: `${totalWidth}px`,
|
||||
height: `${totalHeight}px`,
|
||||
overflow: 'visible',
|
||||
display: 'block'
|
||||
}
|
||||
})
|
||||
|
||||
return canvas
|
||||
} catch (error) {
|
||||
logger.error('Error capturing iframe full snapshot:', error as Error)
|
||||
return Promise.resolve(undefined)
|
||||
logger.error('Error capturing iframe:', error as Error)
|
||||
return undefined
|
||||
} finally {
|
||||
// 恢复动画
|
||||
animationStyle.remove()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
"src/renderer/src/services/traceApi.ts" ],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": ".tsbuildinfo/tsconfig.node.tsbuildinfo",
|
||||
"types": [
|
||||
"electron-vite/node",
|
||||
"vitest/globals"
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": ".tsbuildinfo/tsconfig.web.tsbuildinfo",
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
291
yarn.lock
291
yarn.lock
@ -4942,7 +4942,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@napi-rs/system-ocr@npm:^1.0.2":
|
||||
"@napi-rs/system-ocr@npm:1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "@napi-rs/system-ocr@npm:1.0.2"
|
||||
dependencies:
|
||||
@ -4963,6 +4963,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@napi-rs/system-ocr@patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch":
|
||||
version: 1.0.2
|
||||
resolution: "@napi-rs/system-ocr@patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch::version=1.0.2&hash=407396"
|
||||
dependencies:
|
||||
"@napi-rs/system-ocr-darwin-arm64": "npm:1.0.2"
|
||||
"@napi-rs/system-ocr-darwin-x64": "npm:1.0.2"
|
||||
"@napi-rs/system-ocr-win32-arm64-msvc": "npm:1.0.2"
|
||||
"@napi-rs/system-ocr-win32-x64-msvc": "npm:1.0.2"
|
||||
dependenciesMeta:
|
||||
"@napi-rs/system-ocr-darwin-arm64":
|
||||
optional: true
|
||||
"@napi-rs/system-ocr-darwin-x64":
|
||||
optional: true
|
||||
"@napi-rs/system-ocr-win32-arm64-msvc":
|
||||
optional: true
|
||||
"@napi-rs/system-ocr-win32-x64-msvc":
|
||||
optional: true
|
||||
checksum: 10c0/c1e336b3d506dd771c72b1bddc94e4a9ddeae5292222a7485d66dd0c11eed5d2a37cc7ea338f229e7d4abad276966658bdb4a2b322c9d6a0349dad6e65fcea51
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@napi-rs/wasm-runtime@npm:^0.2.4":
|
||||
version: 0.2.12
|
||||
resolution: "@napi-rs/wasm-runtime@npm:0.2.12"
|
||||
@ -4974,14 +4995,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@napi-rs/wasm-runtime@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@napi-rs/wasm-runtime@npm:1.0.1"
|
||||
"@napi-rs/wasm-runtime@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "@napi-rs/wasm-runtime@npm:1.0.3"
|
||||
dependencies:
|
||||
"@emnapi/core": "npm:^1.4.5"
|
||||
"@emnapi/runtime": "npm:^1.4.5"
|
||||
"@tybys/wasm-util": "npm:^0.10.0"
|
||||
checksum: 10c0/3244105b75637d8d39e76782921fe46e48105bcd390db01a10dc7b596ee99af0f06b7f2b841d7632e756bd3220a5d595b9d426a5453da1ccc895900b894d098f
|
||||
checksum: 10c0/7918d82477e75931b6e35bb003464382eb93e526362f81a98bf8610407a67b10f4d041931015ad48072c89db547deb7e471dfb91f4ab11ac63a24d8580297f75
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -5286,10 +5307,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@oxc-project/runtime@npm:=0.77.3":
|
||||
version: 0.77.3
|
||||
resolution: "@oxc-project/runtime@npm:0.77.3"
|
||||
checksum: 10c0/e2e9d64c9af481c4cad78240f8d5bf252567b026cf857c93bbc43a296b15f2b71cdf99e8890184cc60e26ec9178de4b209ba2729dbe99dab8dc09f8cfa592820
|
||||
"@oxc-project/runtime@npm:=0.82.3":
|
||||
version: 0.82.3
|
||||
resolution: "@oxc-project/runtime@npm:0.82.3"
|
||||
checksum: 10c0/48fd0577a9bd146da7eefea8e61a7c855f8947ef6233fe7db2921e5c1f07d73459d8fb4d2d9e45f4d522d5bb31af8157c96020860154fdf7223a9cb0957e36c0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -5300,10 +5321,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@oxc-project/types@npm:=0.77.3":
|
||||
version: 0.77.3
|
||||
resolution: "@oxc-project/types@npm:0.77.3"
|
||||
checksum: 10c0/aaccfccd59605a46b605b9c2dd966dc470f593ccb66c2a89c189ccbe90fc768e9bf9abfa82f4302addf9881d372ea9c4e634597ad078cf4f76219ce4d9886119
|
||||
"@oxc-project/types@npm:=0.82.3":
|
||||
version: 0.82.3
|
||||
resolution: "@oxc-project/types@npm:0.82.3"
|
||||
checksum: 10c0/17dffc91dc3b726be67b7333d251e811bf4badce8ae77269d1626a107cd7cb673674a3fd6e0f127e40951d630281b9a164fee787a1a0cad12e7372a14b89d7cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -5800,16 +5821,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-android-arm64@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-android-arm64@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-android-arm64@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-android-arm64@npm:1.0.0-beta.34"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.34"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5821,9 +5842,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-darwin-x64@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-darwin-x64@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.34"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5835,9 +5856,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-freebsd-x64@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-freebsd-x64@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.34"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5849,9 +5870,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.34"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5863,9 +5884,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.34"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5877,9 +5898,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.34"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5891,16 +5912,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-linux-arm64-ohos@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-linux-arm64-ohos@npm:1.0.0-beta.29"
|
||||
conditions: os=openharmony & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.34"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5912,9 +5926,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.34"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5926,11 +5940,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.34"
|
||||
conditions: os=openharmony & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.34"
|
||||
dependencies:
|
||||
"@napi-rs/wasm-runtime": "npm:^1.0.1"
|
||||
"@napi-rs/wasm-runtime": "npm:^1.0.3"
|
||||
conditions: cpu=wasm32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5944,9 +5965,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.34"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5958,9 +5979,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.34"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5972,9 +5993,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.34"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@ -5986,10 +6007,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rolldown/pluginutils@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.29"
|
||||
checksum: 10c0/6b53011bb93c83be617a5511197656991b06a2ffa8eb869af211cbb0aed8cc9a6cf48f0a6d0ec92c0daadb912fd74808a635a6a6477f97ca9effaf5606c77deb
|
||||
"@rolldown/pluginutils@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.34"
|
||||
checksum: 10c0/96565287991825ecd90b60607dae908ebfdde233661fc589c98547a75c1fd0282b2e2a7849c3eb0c9941e2fba34667a8d5cdb8d597370815c19c2f29b4c157b4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -9587,7 +9608,7 @@ __metadata:
|
||||
"@mistralai/mistralai": "npm:^1.7.5"
|
||||
"@modelcontextprotocol/sdk": "npm:^1.17.0"
|
||||
"@mozilla/readability": "npm:^0.6.0"
|
||||
"@napi-rs/system-ocr": "npm:^1.0.2"
|
||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch"
|
||||
"@notionhq/client": "npm:^2.2.15"
|
||||
"@opentelemetry/api": "npm:^1.9.0"
|
||||
"@opentelemetry/core": "npm:2.0.0"
|
||||
@ -9660,6 +9681,7 @@ __metadata:
|
||||
cli-progress: "npm:^3.12.0"
|
||||
code-inspector-plugin: "npm:^0.20.14"
|
||||
color: "npm:^5.0.0"
|
||||
concurrently: "npm:^9.2.1"
|
||||
country-flag-emoji-polyfill: "npm:0.1.8"
|
||||
dayjs: "npm:^1.11.11"
|
||||
dexie: "npm:^4.0.8"
|
||||
@ -10908,6 +10930,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "chalk@npm:4.1.2"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^4.1.0"
|
||||
supports-color: "npm:^7.1.0"
|
||||
checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "chalk@npm:3.0.0"
|
||||
@ -10918,16 +10950,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "chalk@npm:4.1.2"
|
||||
dependencies:
|
||||
ansi-styles: "npm:^4.1.0"
|
||||
supports-color: "npm:^7.1.0"
|
||||
checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^5.4.1":
|
||||
version: 5.4.1
|
||||
resolution: "chalk@npm:5.4.1"
|
||||
@ -11449,6 +11471,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"concurrently@npm:^9.2.1":
|
||||
version: 9.2.1
|
||||
resolution: "concurrently@npm:9.2.1"
|
||||
dependencies:
|
||||
chalk: "npm:4.1.2"
|
||||
rxjs: "npm:7.8.2"
|
||||
shell-quote: "npm:1.8.3"
|
||||
supports-color: "npm:8.1.1"
|
||||
tree-kill: "npm:1.2.2"
|
||||
yargs: "npm:17.7.2"
|
||||
bin:
|
||||
conc: dist/bin/concurrently.js
|
||||
concurrently: dist/bin/concurrently.js
|
||||
checksum: 10c0/da37f239f82eb7ac24f5ddb56259861e5f1d6da2ade7602b6ea7ad3101b13b5ccec02a77b7001402d1028ff2fdc38eed55644b32853ad5abf30e057002a963aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"conf@npm:^10.2.0":
|
||||
version: 10.2.0
|
||||
resolution: "conf@npm:10.2.0"
|
||||
@ -14149,15 +14188,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fdir@npm:^6.4.6":
|
||||
version: 6.4.6
|
||||
resolution: "fdir@npm:6.4.6"
|
||||
"fdir@npm:^6.5.0":
|
||||
version: 6.5.0
|
||||
resolution: "fdir@npm:6.5.0"
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9
|
||||
checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -19480,6 +19519,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"picomatch@npm:^4.0.3":
|
||||
version: 4.0.3
|
||||
resolution: "picomatch@npm:4.0.3"
|
||||
checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pidtree@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "pidtree@npm:0.6.0"
|
||||
@ -21515,27 +21561,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rolldown@npm:1.0.0-beta.29":
|
||||
version: 1.0.0-beta.29
|
||||
resolution: "rolldown@npm:1.0.0-beta.29"
|
||||
"rolldown@npm:1.0.0-beta.34":
|
||||
version: 1.0.0-beta.34
|
||||
resolution: "rolldown@npm:1.0.0-beta.34"
|
||||
dependencies:
|
||||
"@oxc-project/runtime": "npm:=0.77.3"
|
||||
"@oxc-project/types": "npm:=0.77.3"
|
||||
"@rolldown/binding-android-arm64": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-darwin-arm64": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-darwin-x64": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-freebsd-x64": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-arm64-musl": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-arm64-ohos": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-x64-gnu": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-linux-x64-musl": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-wasm32-wasi": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-win32-ia32-msvc": "npm:1.0.0-beta.29"
|
||||
"@rolldown/binding-win32-x64-msvc": "npm:1.0.0-beta.29"
|
||||
"@rolldown/pluginutils": "npm:1.0.0-beta.29"
|
||||
"@oxc-project/runtime": "npm:=0.82.3"
|
||||
"@oxc-project/types": "npm:=0.82.3"
|
||||
"@rolldown/binding-android-arm64": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-darwin-arm64": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-darwin-x64": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-freebsd-x64": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-linux-arm64-musl": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-linux-x64-gnu": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-linux-x64-musl": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-openharmony-arm64": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-wasm32-wasi": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-win32-ia32-msvc": "npm:1.0.0-beta.34"
|
||||
"@rolldown/binding-win32-x64-msvc": "npm:1.0.0-beta.34"
|
||||
"@rolldown/pluginutils": "npm:1.0.0-beta.34"
|
||||
ansis: "npm:^4.0.0"
|
||||
dependenciesMeta:
|
||||
"@rolldown/binding-android-arm64":
|
||||
@ -21552,12 +21598,12 @@ __metadata:
|
||||
optional: true
|
||||
"@rolldown/binding-linux-arm64-musl":
|
||||
optional: true
|
||||
"@rolldown/binding-linux-arm64-ohos":
|
||||
optional: true
|
||||
"@rolldown/binding-linux-x64-gnu":
|
||||
optional: true
|
||||
"@rolldown/binding-linux-x64-musl":
|
||||
optional: true
|
||||
"@rolldown/binding-openharmony-arm64":
|
||||
optional: true
|
||||
"@rolldown/binding-wasm32-wasi":
|
||||
optional: true
|
||||
"@rolldown/binding-win32-arm64-msvc":
|
||||
@ -21568,7 +21614,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
rolldown: bin/cli.mjs
|
||||
checksum: 10c0/7660c1bc353d6e0be2b046f18110ed4bd66ed64e6d3bde214c5060b22922e9356f5b8c368d7491976b0a2e02202a157d12b005c5aeddb8b4ce25c2f9c7c19e67
|
||||
checksum: 10c0/3fdaa36b3bfcdd6913973ef8d785a7e7eeb8c181626ac0d0b8a75aecca2ba3d536ff29a3f5c003f692d7c422e022d0357d7d564ab4aa67cf128230ca137473e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -21700,6 +21746,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rxjs@npm:7.8.2":
|
||||
version: 7.8.2
|
||||
resolution: "rxjs@npm:7.8.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.1.0"
|
||||
checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0":
|
||||
version: 5.2.1
|
||||
resolution: "safe-buffer@npm:5.2.1"
|
||||
@ -22032,6 +22087,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shell-quote@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "shell-quote@npm:1.8.3"
|
||||
checksum: 10c0/bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shiki@npm:3.12.0, shiki@npm:^3.12.0":
|
||||
version: 3.12.0
|
||||
resolution: "shiki@npm:3.12.0"
|
||||
@ -22696,6 +22758,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supports-color@npm:8.1.1":
|
||||
version: 8.1.1
|
||||
resolution: "supports-color@npm:8.1.1"
|
||||
dependencies:
|
||||
has-flag: "npm:^4.0.0"
|
||||
checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supports-color@npm:^7.1.0":
|
||||
version: 7.2.0
|
||||
resolution: "supports-color@npm:7.2.0"
|
||||
@ -23131,7 +23202,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tree-kill@npm:^1.2.2":
|
||||
"tree-kill@npm:1.2.2, tree-kill@npm:^1.2.2":
|
||||
version: 1.2.2
|
||||
resolution: "tree-kill@npm:1.2.2"
|
||||
bin:
|
||||
@ -23902,15 +23973,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:rolldown-vite@latest":
|
||||
version: 7.0.10
|
||||
resolution: "rolldown-vite@npm:7.0.10"
|
||||
version: 7.1.5
|
||||
resolution: "rolldown-vite@npm:7.1.5"
|
||||
dependencies:
|
||||
fdir: "npm:^6.4.6"
|
||||
fdir: "npm:^6.5.0"
|
||||
fsevents: "npm:~2.3.3"
|
||||
lightningcss: "npm:^1.30.1"
|
||||
picomatch: "npm:^4.0.2"
|
||||
picomatch: "npm:^4.0.3"
|
||||
postcss: "npm:^8.5.6"
|
||||
rolldown: "npm:1.0.0-beta.29"
|
||||
rolldown: "npm:1.0.0-beta.34"
|
||||
tinyglobby: "npm:^0.2.14"
|
||||
peerDependencies:
|
||||
"@types/node": ^20.19.0 || >=22.12.0
|
||||
@ -23952,7 +24023,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
vite: bin/vite.js
|
||||
checksum: 10c0/9094a52664c475822deee5597d161ab8846062de01040b6cae18d81e5e894c20f201b93229efd51bfcd021b2d171a08852a17acc67dd53a1ac896c800a950eea
|
||||
checksum: 10c0/55f6648a8700345700382adac4877208eedcfff5757debba74851227dbc50eae3cc7ccea86bcfda689a9855fbbd2c7e7dd020ffc0c01bfb815dbc6bf65991cbd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24557,7 +24628,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs@npm:^17.0.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2":
|
||||
"yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2":
|
||||
version: 17.7.2
|
||||
resolution: "yargs@npm:17.7.2"
|
||||
dependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user