mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 21:01:32 +08:00
Merge branch 'main' into feat/aisdk-package
This commit is contained in:
commit
d05ed94702
@ -78,7 +78,7 @@
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"officeparser": "^4.2.0",
|
||||
"os-proxy-config": "^1.1.2",
|
||||
"selection-hook": "^1.0.8",
|
||||
"selection-hook": "^1.0.9",
|
||||
"turndown": "7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -9,6 +9,7 @@ import { CancellationToken, UpdateInfo } from 'builder-util-runtime'
|
||||
import { app, BrowserWindow, dialog } from 'electron'
|
||||
import { AppUpdater as _AppUpdater, autoUpdater, Logger, NsisUpdater, UpdateCheckResult } from 'electron-updater'
|
||||
import path from 'path'
|
||||
import semver from 'semver'
|
||||
|
||||
import icon from '../../../build/icon.png?asset'
|
||||
import { configManager } from './ConfigManager'
|
||||
@ -44,12 +45,6 @@ export default class AppUpdater {
|
||||
|
||||
// 检测到不需要更新时
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
if (configManager.getTestPlan() && this.autoUpdater.channel !== UpgradeChannel.LATEST) {
|
||||
logger.info('test plan is enabled, but update is not available, do not send update not available event')
|
||||
// will not send update not available event, because will check for updates with latest channel
|
||||
return
|
||||
}
|
||||
|
||||
windowService.getMainWindow()?.webContents.send(IpcChannel.UpdateNotAvailable)
|
||||
})
|
||||
|
||||
@ -72,18 +67,24 @@ export default class AppUpdater {
|
||||
this.autoUpdater = autoUpdater
|
||||
}
|
||||
|
||||
private async _getPreReleaseVersionFromGithub(channel: UpgradeChannel) {
|
||||
private async _getReleaseVersionFromGithub(channel: UpgradeChannel) {
|
||||
const headers = {
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'Accept-Language': 'en-US,en;q=0.9'
|
||||
}
|
||||
try {
|
||||
logger.info(`get pre release version from github: ${channel}`)
|
||||
logger.info(`get release version from github: ${channel}`)
|
||||
const responses = await fetch('https://api.github.com/repos/CherryHQ/cherry-studio/releases?per_page=8', {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'Accept-Language': 'en-US,en;q=0.9'
|
||||
}
|
||||
headers
|
||||
})
|
||||
const data = (await responses.json()) as GithubReleaseInfo[]
|
||||
let mightHaveLatest = false
|
||||
const release: GithubReleaseInfo | undefined = data.find((item: GithubReleaseInfo) => {
|
||||
if (!item.draft && !item.prerelease) {
|
||||
mightHaveLatest = true
|
||||
}
|
||||
|
||||
return item.prerelease && item.tag_name.includes(`-${channel}.`)
|
||||
})
|
||||
|
||||
@ -91,8 +92,29 @@ export default class AppUpdater {
|
||||
return null
|
||||
}
|
||||
|
||||
logger.info(`prerelease url is ${release.tag_name}, set channel to ${channel}`)
|
||||
// if the release version is the same as the current version, return null
|
||||
if (release.tag_name === app.getVersion()) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (mightHaveLatest) {
|
||||
logger.info(`might have latest release, get latest release`)
|
||||
const latestReleaseResponse = await fetch(
|
||||
'https://api.github.com/repos/CherryHQ/cherry-studio/releases/latest',
|
||||
{
|
||||
headers
|
||||
}
|
||||
)
|
||||
const latestRelease = (await latestReleaseResponse.json()) as GithubReleaseInfo
|
||||
if (semver.gt(latestRelease.tag_name, release.tag_name)) {
|
||||
logger.info(
|
||||
`latest release version is ${latestRelease.tag_name}, prerelease version is ${release.tag_name}, return null`
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`release url is ${release.tag_name}, set channel to ${channel}`)
|
||||
return `https://github.com/CherryHQ/cherry-studio/releases/download/${release.tag_name}`
|
||||
} catch (error) {
|
||||
logger.error('Failed to get latest not draft version from github:', error as Error)
|
||||
@ -151,14 +173,14 @@ export default class AppUpdater {
|
||||
return
|
||||
}
|
||||
|
||||
const preReleaseUrl = await this._getPreReleaseVersionFromGithub(channel)
|
||||
if (preReleaseUrl) {
|
||||
logger.info(`prerelease url is ${preReleaseUrl}, set channel to ${channel}`)
|
||||
this._setChannel(channel, preReleaseUrl)
|
||||
const releaseUrl = await this._getReleaseVersionFromGithub(channel)
|
||||
if (releaseUrl) {
|
||||
logger.info(`release url is ${releaseUrl}, set channel to ${channel}`)
|
||||
this._setChannel(channel, releaseUrl)
|
||||
return
|
||||
}
|
||||
|
||||
// if no prerelease url, use github latest to avoid error
|
||||
// if no prerelease url, use github latest to get release
|
||||
this._setChannel(UpgradeChannel.LATEST, FeedUrl.GITHUB_LATEST)
|
||||
return
|
||||
}
|
||||
@ -195,17 +217,6 @@ export default class AppUpdater {
|
||||
`update check result: ${this.updateCheckResult?.isUpdateAvailable}, channel: ${this.autoUpdater.channel}, currentVersion: ${this.autoUpdater.currentVersion}`
|
||||
)
|
||||
|
||||
// if the update is not available, and the test plan is enabled, set the feed url to the github latest
|
||||
if (
|
||||
!this.updateCheckResult?.isUpdateAvailable &&
|
||||
configManager.getTestPlan() &&
|
||||
this.autoUpdater.channel !== UpgradeChannel.LATEST
|
||||
) {
|
||||
logger.info('test plan is enabled, but update is not available, set channel to latest')
|
||||
this._setChannel(UpgradeChannel.LATEST, FeedUrl.GITHUB_LATEST)
|
||||
this.updateCheckResult = await this.autoUpdater.checkForUpdates()
|
||||
}
|
||||
|
||||
if (this.updateCheckResult?.isUpdateAvailable && !this.autoUpdater.autoDownload) {
|
||||
// 如果 autoDownload 为 false,则需要再调用下面的函数触发下
|
||||
// do not use await, because it will block the return of this function
|
||||
|
||||
@ -21,6 +21,27 @@ class BackupManager {
|
||||
private tempDir = path.join(app.getPath('temp'), 'cherry-studio', 'backup', 'temp')
|
||||
private backupDir = path.join(app.getPath('temp'), 'cherry-studio', 'backup')
|
||||
|
||||
// 缓存实例,避免重复创建
|
||||
private s3Storage: S3Storage | null = null
|
||||
private webdavInstance: WebDav | null = null
|
||||
|
||||
// 缓存核心连接配置,用于检测连接配置是否变更
|
||||
private cachedS3ConnectionConfig: {
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
root?: string
|
||||
} | null = null
|
||||
|
||||
private cachedWebdavConnectionConfig: {
|
||||
webdavHost: string
|
||||
webdavUser?: string
|
||||
webdavPass?: string
|
||||
webdavPath?: string
|
||||
} | null = null
|
||||
|
||||
constructor() {
|
||||
this.checkConnection = this.checkConnection.bind(this)
|
||||
this.backup = this.backup.bind(this)
|
||||
@ -87,6 +108,88 @@ class BackupManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个配置对象是否相等,只比较影响客户端连接的核心字段,忽略 fileName 等易变字段
|
||||
*/
|
||||
private isS3ConfigEqual(cachedConfig: typeof this.cachedS3ConnectionConfig, config: S3Config): boolean {
|
||||
if (!cachedConfig) return false
|
||||
|
||||
return (
|
||||
cachedConfig.endpoint === config.endpoint &&
|
||||
cachedConfig.region === config.region &&
|
||||
cachedConfig.bucket === config.bucket &&
|
||||
cachedConfig.accessKeyId === config.accessKeyId &&
|
||||
cachedConfig.secretAccessKey === config.secretAccessKey &&
|
||||
cachedConfig.root === config.root
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度比较两个 WebDAV 配置对象是否相等,只比较影响客户端连接的核心字段,忽略 fileName 等易变字段
|
||||
*/
|
||||
private isWebDavConfigEqual(cachedConfig: typeof this.cachedWebdavConnectionConfig, config: WebDavConfig): boolean {
|
||||
if (!cachedConfig) return false
|
||||
|
||||
return (
|
||||
cachedConfig.webdavHost === config.webdavHost &&
|
||||
cachedConfig.webdavUser === config.webdavUser &&
|
||||
cachedConfig.webdavPass === config.webdavPass &&
|
||||
cachedConfig.webdavPath === config.webdavPath
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 S3Storage 实例,如果连接配置未变且实例已存在则复用,否则创建新实例
|
||||
* 注意:只有连接相关的配置变更才会重新创建实例,其他配置变更不影响实例复用
|
||||
*/
|
||||
private getS3Storage(config: S3Config): S3Storage {
|
||||
// 检查核心连接配置是否变更
|
||||
const configChanged = !this.isS3ConfigEqual(this.cachedS3ConnectionConfig, config)
|
||||
|
||||
if (configChanged || !this.s3Storage) {
|
||||
this.s3Storage = new S3Storage(config)
|
||||
// 只缓存连接相关的配置字段
|
||||
this.cachedS3ConnectionConfig = {
|
||||
endpoint: config.endpoint,
|
||||
region: config.region,
|
||||
bucket: config.bucket,
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
root: config.root
|
||||
}
|
||||
logger.debug('[BackupManager] Created new S3Storage instance')
|
||||
} else {
|
||||
logger.debug('[BackupManager] Reusing existing S3Storage instance')
|
||||
}
|
||||
|
||||
return this.s3Storage
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WebDav 实例,如果连接配置未变且实例已存在则复用,否则创建新实例
|
||||
* 注意:只有连接相关的配置变更才会重新创建实例,其他配置变更不影响实例复用
|
||||
*/
|
||||
private getWebDavInstance(config: WebDavConfig): WebDav {
|
||||
// 检查核心连接配置是否变更
|
||||
const configChanged = !this.isWebDavConfigEqual(this.cachedWebdavConnectionConfig, config)
|
||||
|
||||
if (configChanged || !this.webdavInstance) {
|
||||
this.webdavInstance = new WebDav(config)
|
||||
// 只缓存连接相关的配置字段
|
||||
this.cachedWebdavConnectionConfig = {
|
||||
webdavHost: config.webdavHost,
|
||||
webdavUser: config.webdavUser,
|
||||
webdavPass: config.webdavPass,
|
||||
webdavPath: config.webdavPath
|
||||
}
|
||||
logger.debug('[BackupManager] Created new WebDav instance')
|
||||
} else {
|
||||
logger.debug('[BackupManager] Reusing existing WebDav instance')
|
||||
}
|
||||
|
||||
return this.webdavInstance
|
||||
}
|
||||
|
||||
async backup(
|
||||
_: Electron.IpcMainInvokeEvent,
|
||||
fileName: string,
|
||||
@ -322,7 +425,7 @@ class BackupManager {
|
||||
async backupToWebdav(_: Electron.IpcMainInvokeEvent, data: string, webdavConfig: WebDavConfig) {
|
||||
const filename = webdavConfig.fileName || 'cherry-studio.backup.zip'
|
||||
const backupedFilePath = await this.backup(_, filename, data, undefined, webdavConfig.skipBackupFile)
|
||||
const webdavClient = new WebDav(webdavConfig)
|
||||
const webdavClient = this.getWebDavInstance(webdavConfig)
|
||||
try {
|
||||
let result
|
||||
if (webdavConfig.disableStream) {
|
||||
@ -349,7 +452,7 @@ class BackupManager {
|
||||
|
||||
async restoreFromWebdav(_: Electron.IpcMainInvokeEvent, webdavConfig: WebDavConfig) {
|
||||
const filename = webdavConfig.fileName || 'cherry-studio.backup.zip'
|
||||
const webdavClient = new WebDav(webdavConfig)
|
||||
const webdavClient = this.getWebDavInstance(webdavConfig)
|
||||
try {
|
||||
const retrievedFile = await webdavClient.getFileContents(filename)
|
||||
const backupedFilePath = path.join(this.backupDir, filename)
|
||||
@ -377,7 +480,7 @@ class BackupManager {
|
||||
|
||||
listWebdavFiles = async (_: Electron.IpcMainInvokeEvent, config: WebDavConfig) => {
|
||||
try {
|
||||
const client = new WebDav(config)
|
||||
const client = this.getWebDavInstance(config)
|
||||
const response = await client.getDirectoryContents()
|
||||
const files = Array.isArray(response) ? response : response.data
|
||||
|
||||
@ -467,7 +570,7 @@ class BackupManager {
|
||||
}
|
||||
|
||||
async checkConnection(_: Electron.IpcMainInvokeEvent, webdavConfig: WebDavConfig) {
|
||||
const webdavClient = new WebDav(webdavConfig)
|
||||
const webdavClient = this.getWebDavInstance(webdavConfig)
|
||||
return await webdavClient.checkConnection()
|
||||
}
|
||||
|
||||
@ -477,13 +580,13 @@ class BackupManager {
|
||||
path: string,
|
||||
options?: CreateDirectoryOptions
|
||||
) {
|
||||
const webdavClient = new WebDav(webdavConfig)
|
||||
const webdavClient = this.getWebDavInstance(webdavConfig)
|
||||
return await webdavClient.createDirectory(path, options)
|
||||
}
|
||||
|
||||
async deleteWebdavFile(_: Electron.IpcMainInvokeEvent, fileName: string, webdavConfig: WebDavConfig) {
|
||||
try {
|
||||
const webdavClient = new WebDav(webdavConfig)
|
||||
const webdavClient = this.getWebDavInstance(webdavConfig)
|
||||
return await webdavClient.deleteFile(fileName)
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to delete WebDAV file:', error)
|
||||
@ -525,7 +628,7 @@ class BackupManager {
|
||||
logger.debug(`Starting S3 backup to ${filename}`)
|
||||
|
||||
const backupedFilePath = await this.backup(_, filename, data, undefined, s3Config.skipBackupFile)
|
||||
const s3Client = new S3Storage(s3Config)
|
||||
const s3Client = this.getS3Storage(s3Config)
|
||||
try {
|
||||
const fileBuffer = await fs.promises.readFile(backupedFilePath)
|
||||
const result = await s3Client.putFileContents(filename, fileBuffer)
|
||||
@ -603,7 +706,7 @@ class BackupManager {
|
||||
|
||||
logger.debug(`Starting restore from S3: ${filename}`)
|
||||
|
||||
const s3Client = new S3Storage(s3Config)
|
||||
const s3Client = this.getS3Storage(s3Config)
|
||||
try {
|
||||
const retrievedFile = await s3Client.getFileContents(filename)
|
||||
const backupedFilePath = path.join(this.backupDir, filename)
|
||||
@ -628,7 +731,7 @@ class BackupManager {
|
||||
|
||||
listS3Files = async (_: Electron.IpcMainInvokeEvent, s3Config: S3Config) => {
|
||||
try {
|
||||
const s3Client = new S3Storage(s3Config)
|
||||
const s3Client = this.getS3Storage(s3Config)
|
||||
|
||||
const objects = await s3Client.listFiles()
|
||||
const files = objects
|
||||
@ -652,7 +755,7 @@ class BackupManager {
|
||||
|
||||
async deleteS3File(_: Electron.IpcMainInvokeEvent, fileName: string, s3Config: S3Config) {
|
||||
try {
|
||||
const s3Client = new S3Storage(s3Config)
|
||||
const s3Client = this.getS3Storage(s3Config)
|
||||
return await s3Client.deleteFile(fileName)
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to delete S3 file:', error)
|
||||
@ -661,7 +764,7 @@ class BackupManager {
|
||||
}
|
||||
|
||||
async checkS3Connection(_: Electron.IpcMainInvokeEvent, s3Config: S3Config) {
|
||||
const s3Client = new S3Storage(s3Config)
|
||||
const s3Client = this.getS3Storage(s3Config)
|
||||
return await s3Client.checkConnection()
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,3 +112,129 @@ export function MdiLightbulbOn(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function BingLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
<path d="M4.842.005a.966.966 0 01.604.142l2.62 1.813c.369.256.492.352.637.496.471.47.752 1.09.797 1.765l.008.847.003 1.441.004 13.002.144-.094 7.015-4.353.015.003.029.01c-.398-.17-.893-.339-1.655-.566l-.484-.146c-.584-.18-.71-.238-.921-.38a2.009 2.009 0 01-.37-.312 2.172 2.172 0 01-.41-.592L11.32 9.063c-.166-.444-.166-.49-.156-.63a.92.92 0 01.806-.864l.094-.01c.044-.005.22.023.29.044l.052.021c.06.026.16.075.313.154l3.63 1.908a6.626 6.626 0 013.292 4.531c.194.99.159 2.037-.102 3.012-.216.805-.639 1.694-1.054 2.213l-.08.099-.047.05c-.01.01-.013.01-.01.002l.043-.074-.072.114c-.011.031-.233.28-.38.425l-.17.161c-.22.202-.431.36-.832.62L13.544 23c-.941.6-1.86.912-2.913.992-.23.018-.854.008-1.074-.017a6.31 6.31 0 01-1.658-.412c-1.854-.738-3.223-2.288-3.705-4.195a8.077 8.077 0 01-.121-.57l-.046-.325a1.123 1.123 0 01-.014-.168l-.006-.029L4 11.617 4.01.866a.981.981 0 01.007-.111.943.943 0 01.825-.75z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function SearXNGLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 265 265" style={{ display: 'block' }} {...props}>
|
||||
<g transform="translate(-40.921 -17.417)">
|
||||
<circle
|
||||
cx="142.2"
|
||||
cy="122.9"
|
||||
r="85"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="28.3465"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="11.3386"
|
||||
/>
|
||||
<path
|
||||
d="M118.4 77.6c19.8-10.2 44-6.4 59.7 9.4s19.3 40 8.9 59.7"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="14.1732"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="11.3386"
|
||||
/>
|
||||
<path d="m184.2 202 37-38.6 81.8 78.3-37 38.6z" fill="currentColor" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function TavilyLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="m16.44.964 4.921 7.79c.79 1.252-.108 2.883-1.588 2.883H17.76V23.3h-2.91V.088c.61 0 1.22.292 1.59.876z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.342 8.755 13.263.964a1.864 1.864 0 0 1 1.59-.876V23.3a4.87 4.87 0 0 0-.252-.006c-.99 0-1.907.311-2.658.842V11.637H9.93c-1.48 0-2.38-1.631-1.589-2.882z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M30.278 31H18.031a4.596 4.596 0 0 0 1.219-2.91h22.577c0 .61-.292 1.22-.875 1.59L33.16 34.6c-1.251.791-2.883-.108-2.883-1.588V31z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="m33.16 21.581 7.79 4.921c.585.369.876.979.876 1.589H19.25a4.619 4.619 0 0 0-.858-2.91h11.887V23.17c0-1.48 1.631-2.38 2.882-1.589z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="m8.24 34.25-7.107 7.108a1.864 1.864 0 0 0 1.742.504l8.989-2.03c1.443-.325 1.961-2.114.915-3.16l-1.423-1.423 5.356-5.356a2.805 2.805 0 0 0 0-3.966l-.074-.075L8.24 34.25z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="m7.243 31.135 5.355-5.356a2.805 2.805 0 0 1 3.967 0l.074.074-8.397 8.397-7.108 7.108a1.864 1.864 0 0 1-.504-1.742l2.029-8.989c.325-1.444 2.115-1.961 3.161-.915l1.423 1.423z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ExaLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
<title>Exa</title>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 0h19v1.791L13.892 12 22 22.209V24H3V0zm9.62 10.348l6.589-8.557H6.03l6.59 8.557zM5.138 3.935v7.17h5.52l-5.52-7.17zm5.52 8.96h-5.52v7.17l5.52-7.17zM6.03 22.21l6.59-8.557 6.589 8.557H6.03z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function BochaLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg width="1em" height="1em" viewBox="0 0 135 116" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12.5754 13.8123C24.6109 7.94459 39.1223 12.9435 44.9955 24.9805L57.5355 50.6805C60.4695 56.6936 57.9756 63.9478 51.9652 66.8832C51.9627 66.8844 51.9602 66.8856 51.9577 66.8868C45.94 69.8206 38.6843 67.3212 35.7477 61.3027L12.5754 13.8123Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
opacity="0.64774"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M0 38.3013C9.46916 28.836 24.813 28.836 34.2822 38.3013L55.2526 59.2631C59.9819 63.9904 59.9852 71.6582 55.2601 76.3896C55.2576 76.3921 55.2551 76.3946 55.2526 76.397C50.5181 81.1297 42.8461 81.1297 38.1116 76.397L0 38.3013Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M86.8777 18.0444C113.939 18.0444 135.876 39.9725 135.876 67.0222C135.876 80.2286 129.086 93.6477 120.585 102.457L117.065 98.2367C111.026 90.9998 108.882 81.2777 111.314 72.1702C111.755 70.5198 111.976 69.0033 111.976 67.6209C111.976 53.6689 100.661 42.3586 86.7029 42.3586C72.7452 42.3586 61.4303 53.6689 61.4303 67.6209C61.4303 81.5728 72.7452 92.8831 86.7029 92.8831C89.3159 92.8831 91.8363 92.4867 94.2071 91.7508C101.312 89.5455 109.054 91.3768 114.419 96.5322L120.585 102.457C111.83 110.626 99.7992 116 86.8777 116C59.8168 116 37.8796 94.0719 37.8796 67.0222C37.8796 39.9725 59.8168 18.0444 86.8777 18.0444Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M37.8796 0C51.2677 0 62.1208 10.8581 62.1208 24.2522V41.7389C62.1208 55.133 51.2677 65.9911 37.8796 65.9911V0Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,7 +3,14 @@ import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
import SelectProviderModelPopup from '@renderer/pages/settings/ProviderSettings/SelectProviderModelPopup'
|
||||
import { checkApi } from '@renderer/services/ApiService'
|
||||
import WebSearchService from '@renderer/services/WebSearchService'
|
||||
import { Model, PreprocessProvider, Provider, WebSearchProvider } from '@renderer/types'
|
||||
import {
|
||||
isPreprocessProviderId,
|
||||
isWebSearchProviderId,
|
||||
Model,
|
||||
PreprocessProvider,
|
||||
Provider,
|
||||
WebSearchProvider
|
||||
} from '@renderer/types'
|
||||
import { ApiKeyConnectivity, ApiKeyWithStatus, HealthStatus } from '@renderer/types/healthCheck'
|
||||
import { formatApiKeys, splitApiKeyString } from '@renderer/utils/api'
|
||||
import { formatErrorMessage } from '@renderer/utils/error'
|
||||
@ -12,12 +19,11 @@ import { isEmpty } from 'lodash'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { ApiKeyValidity, ApiProviderKind, ApiProviderUnion } from './types'
|
||||
import { ApiKeyValidity, ApiProvider, UpdateApiProviderFunc } from './types'
|
||||
|
||||
interface UseApiKeysProps {
|
||||
provider: ApiProviderUnion
|
||||
updateProvider: (provider: Partial<ApiProviderUnion>) => void
|
||||
providerKind: ApiProviderKind
|
||||
provider: ApiProvider
|
||||
updateProvider: UpdateApiProviderFunc
|
||||
}
|
||||
|
||||
const logger = loggerService.withContext('ApiKeyListPopup')
|
||||
@ -25,7 +31,7 @@ const logger = loggerService.withContext('ApiKeyListPopup')
|
||||
/**
|
||||
* API Keys 管理 hook
|
||||
*/
|
||||
export function useApiKeys({ provider, updateProvider, providerKind }: UseApiKeysProps) {
|
||||
export function useApiKeys({ provider, updateProvider }: UseApiKeysProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 连通性检查的 UI 状态管理
|
||||
@ -199,11 +205,13 @@ export function useApiKeys({ provider, updateProvider, providerKind }: UseApiKey
|
||||
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
if (isLlmProvider(provider, providerKind) && model) {
|
||||
if (isLlmProvider(provider) && model) {
|
||||
await checkApi({ ...provider, apiKey: keyToCheck }, model)
|
||||
} else {
|
||||
} else if (isWebSearchProvider(provider)) {
|
||||
const result = await WebSearchService.checkSearch({ ...provider, apiKey: keyToCheck })
|
||||
if (!result.valid) throw new Error(result.error)
|
||||
} else {
|
||||
// 不处理预处理供应商
|
||||
}
|
||||
const latency = Date.now() - startTime
|
||||
|
||||
@ -228,7 +236,7 @@ export function useApiKeys({ provider, updateProvider, providerKind }: UseApiKey
|
||||
logger.error('failed to validate the connectivity of the api key', error)
|
||||
}
|
||||
},
|
||||
[keys, connectivityStates, updateConnectivityState, provider, providerKind]
|
||||
[keys, connectivityStates, updateConnectivityState, provider]
|
||||
)
|
||||
|
||||
// 检查单个 key 的连通性
|
||||
@ -240,23 +248,23 @@ export function useApiKeys({ provider, updateProvider, providerKind }: UseApiKey
|
||||
const currentState = connectivityStates.get(keyToCheck)
|
||||
if (currentState?.checking) return
|
||||
|
||||
const model = isLlmProvider(provider, providerKind) ? await getModelForCheck(provider, t) : undefined
|
||||
const model = isLlmProvider(provider) ? await getModelForCheck(provider, t) : undefined
|
||||
if (model === null) return
|
||||
|
||||
await runConnectivityCheck(index, model)
|
||||
},
|
||||
[provider, keys, connectivityStates, providerKind, t, runConnectivityCheck]
|
||||
[provider, keys, connectivityStates, t, runConnectivityCheck]
|
||||
)
|
||||
|
||||
// 检查所有 keys 的连通性
|
||||
const checkAllKeysConnectivity = useCallback(async () => {
|
||||
if (!provider || keys.length === 0) return
|
||||
|
||||
const model = isLlmProvider(provider, providerKind) ? await getModelForCheck(provider, t) : undefined
|
||||
const model = isLlmProvider(provider) ? await getModelForCheck(provider, t) : undefined
|
||||
if (model === null) return
|
||||
|
||||
await Promise.allSettled(keys.map((_, index) => runConnectivityCheck(index, model)))
|
||||
}, [provider, keys, providerKind, t, runConnectivityCheck])
|
||||
}, [provider, keys, t, runConnectivityCheck])
|
||||
|
||||
// 计算是否有 key 正在检查
|
||||
const isChecking = useMemo(() => {
|
||||
@ -275,16 +283,18 @@ export function useApiKeys({ provider, updateProvider, providerKind }: UseApiKey
|
||||
}
|
||||
}
|
||||
|
||||
export function isLlmProvider(obj: any, kind: ApiProviderKind): obj is Provider {
|
||||
return kind === 'llm' && 'type' in obj && 'models' in obj
|
||||
export function isLlmProvider(provider: ApiProvider): provider is Provider {
|
||||
return 'models' in provider
|
||||
}
|
||||
|
||||
export function isWebSearchProvider(obj: any, kind: ApiProviderKind): obj is WebSearchProvider {
|
||||
return kind === 'websearch' && ('url' in obj || 'engines' in obj)
|
||||
export function isWebSearchProvider(provider: ApiProvider): provider is WebSearchProvider {
|
||||
return isWebSearchProviderId(provider.id)
|
||||
}
|
||||
|
||||
export function isPreprocessProvider(obj: any, kind: ApiProviderKind): obj is PreprocessProvider {
|
||||
return kind === 'doc-preprocess' && ('quota' in obj || 'options' in obj)
|
||||
export function isPreprocessProvider(provider: ApiProvider): provider is PreprocessProvider {
|
||||
// NOTE: mistral 同时提供预处理和llm服务,所以其llm provier可能被误判为预处理provider
|
||||
// 后面需要使用更严格的判断方式
|
||||
return isPreprocessProviderId(provider.id) && !isLlmProvider(provider)
|
||||
}
|
||||
|
||||
// 获取模型用于检查
|
||||
|
||||
@ -6,6 +6,7 @@ import { useProvider } from '@renderer/hooks/useProvider'
|
||||
import { useWebSearchProvider } from '@renderer/hooks/useWebSearchProviders'
|
||||
import { SettingHelpText } from '@renderer/pages/settings'
|
||||
import { isProviderSupportAuth } from '@renderer/services/ProviderService'
|
||||
import { PreprocessProviderId, WebSearchProviderId } from '@renderer/types'
|
||||
import { ApiKeyWithStatus, HealthStatus } from '@renderer/types/healthCheck'
|
||||
import { Button, Card, Flex, List, Popconfirm, Space, Tooltip, Typography } from 'antd'
|
||||
import { Plus } from 'lucide-react'
|
||||
@ -15,19 +16,18 @@ import styled from 'styled-components'
|
||||
|
||||
import { isLlmProvider, useApiKeys } from './hook'
|
||||
import ApiKeyItem from './item'
|
||||
import { ApiProviderKind, ApiProviderUnion } from './types'
|
||||
import { ApiProvider, UpdateApiProviderFunc } from './types'
|
||||
|
||||
interface ApiKeyListProps {
|
||||
provider: ApiProviderUnion
|
||||
updateProvider: (provider: Partial<ApiProviderUnion>) => void
|
||||
providerKind: ApiProviderKind
|
||||
provider: ApiProvider
|
||||
updateProvider: UpdateApiProviderFunc
|
||||
showHealthCheck?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Api key 列表,管理 CRUD 操作、连接检查
|
||||
*/
|
||||
export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, providerKind, showHealthCheck = true }) => {
|
||||
export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, showHealthCheck = true }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 临时新项状态
|
||||
@ -42,7 +42,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
|
||||
checkKeyConnectivity,
|
||||
checkAllKeysConnectivity,
|
||||
isChecking
|
||||
} = useApiKeys({ provider, updateProvider, providerKind: providerKind })
|
||||
} = useApiKeys({ provider, updateProvider })
|
||||
|
||||
// 创建一个临时新项
|
||||
const handleAddNew = () => {
|
||||
@ -73,7 +73,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
|
||||
|
||||
const shouldAutoFocus = () => {
|
||||
if (provider.apiKey) return false
|
||||
return isLlmProvider(provider, providerKind) && provider.enabled && !isProviderSupportAuth(provider)
|
||||
return isLlmProvider(provider) && provider.enabled && !isProviderSupportAuth(provider)
|
||||
}
|
||||
|
||||
// 合并真实 keys 和临时新项
|
||||
@ -179,55 +179,33 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, prov
|
||||
|
||||
interface SpecificApiKeyListProps {
|
||||
providerId: string
|
||||
providerKind: ApiProviderKind
|
||||
showHealthCheck?: boolean
|
||||
}
|
||||
|
||||
export const LlmApiKeyList: FC<SpecificApiKeyListProps> = ({ providerId, providerKind, showHealthCheck = true }) => {
|
||||
type WebSearchApiKeyList = SpecificApiKeyListProps & {
|
||||
providerId: WebSearchProviderId
|
||||
}
|
||||
|
||||
type DocPreprocessApiKeyListProps = SpecificApiKeyListProps & {
|
||||
providerId: PreprocessProviderId
|
||||
}
|
||||
|
||||
export const LlmApiKeyList: FC<SpecificApiKeyListProps> = ({ providerId, showHealthCheck = true }) => {
|
||||
const { provider, updateProvider } = useProvider(providerId)
|
||||
|
||||
return (
|
||||
<ApiKeyList
|
||||
provider={provider}
|
||||
updateProvider={updateProvider}
|
||||
providerKind={providerKind}
|
||||
showHealthCheck={showHealthCheck}
|
||||
/>
|
||||
)
|
||||
return <ApiKeyList provider={provider} updateProvider={updateProvider} showHealthCheck={showHealthCheck} />
|
||||
}
|
||||
|
||||
export const WebSearchApiKeyList: FC<SpecificApiKeyListProps> = ({
|
||||
providerId,
|
||||
providerKind,
|
||||
showHealthCheck = true
|
||||
}) => {
|
||||
export const WebSearchApiKeyList: FC<WebSearchApiKeyList> = ({ providerId, showHealthCheck = true }) => {
|
||||
const { provider, updateProvider } = useWebSearchProvider(providerId)
|
||||
|
||||
return (
|
||||
<ApiKeyList
|
||||
provider={provider}
|
||||
updateProvider={updateProvider}
|
||||
providerKind={providerKind}
|
||||
showHealthCheck={showHealthCheck}
|
||||
/>
|
||||
)
|
||||
return <ApiKeyList provider={provider} updateProvider={updateProvider} showHealthCheck={showHealthCheck} />
|
||||
}
|
||||
|
||||
export const DocPreprocessApiKeyList: FC<SpecificApiKeyListProps> = ({
|
||||
providerId,
|
||||
providerKind,
|
||||
showHealthCheck = true
|
||||
}) => {
|
||||
export const DocPreprocessApiKeyList: FC<DocPreprocessApiKeyListProps> = ({ providerId, showHealthCheck = true }) => {
|
||||
const { provider, updateProvider } = usePreprocessProvider(providerId)
|
||||
|
||||
return (
|
||||
<ApiKeyList
|
||||
provider={provider}
|
||||
updateProvider={updateProvider}
|
||||
providerKind={providerKind}
|
||||
showHealthCheck={showHealthCheck}
|
||||
/>
|
||||
)
|
||||
return <ApiKeyList provider={provider} updateProvider={updateProvider} showHealthCheck={showHealthCheck} />
|
||||
}
|
||||
|
||||
const ListContainer = styled.div`
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { isPreprocessProviderId, isWebSearchProviderId } from '@renderer/types'
|
||||
import { Modal } from 'antd'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { DocPreprocessApiKeyList, LlmApiKeyList, WebSearchApiKeyList } from './list'
|
||||
import { ApiProviderKind } from './types'
|
||||
|
||||
interface ShowParams {
|
||||
providerId: string
|
||||
providerKind: ApiProviderKind
|
||||
title?: string
|
||||
showHealthCheck?: boolean
|
||||
}
|
||||
@ -20,7 +19,7 @@ interface Props extends ShowParams {
|
||||
/**
|
||||
* API Key 列表弹窗容器组件
|
||||
*/
|
||||
const PopupContainer: React.FC<Props> = ({ providerId, providerKind, title, resolve, showHealthCheck = true }) => {
|
||||
const PopupContainer: React.FC<Props> = ({ providerId, title, resolve, showHealthCheck = true }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -33,17 +32,14 @@ const PopupContainer: React.FC<Props> = ({ providerId, providerKind, title, reso
|
||||
}
|
||||
|
||||
const ListComponent = useMemo(() => {
|
||||
switch (providerKind) {
|
||||
case 'llm':
|
||||
return LlmApiKeyList
|
||||
case 'websearch':
|
||||
return WebSearchApiKeyList
|
||||
case 'doc-preprocess':
|
||||
return DocPreprocessApiKeyList
|
||||
default:
|
||||
return null
|
||||
if (isWebSearchProviderId(providerId)) {
|
||||
return <WebSearchApiKeyList providerId={providerId} showHealthCheck={showHealthCheck} />
|
||||
}
|
||||
}, [providerKind])
|
||||
if (isPreprocessProviderId(providerId)) {
|
||||
return <DocPreprocessApiKeyList providerId={providerId} showHealthCheck={showHealthCheck} />
|
||||
}
|
||||
return <LlmApiKeyList providerId={providerId} showHealthCheck={showHealthCheck} />
|
||||
}, [providerId, showHealthCheck])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -55,9 +51,7 @@ const PopupContainer: React.FC<Props> = ({ providerId, providerKind, title, reso
|
||||
centered
|
||||
width={600}
|
||||
footer={null}>
|
||||
{ListComponent && (
|
||||
<ListComponent providerId={providerId} providerKind={providerKind} showHealthCheck={showHealthCheck} />
|
||||
)}
|
||||
{ListComponent}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,6 +8,12 @@ export type ApiKeyValidity = {
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type ApiProviderUnion = Provider | WebSearchProvider | PreprocessProvider
|
||||
export type ApiProvider = Provider | WebSearchProvider | PreprocessProvider
|
||||
|
||||
export type ApiProviderKind = 'llm' | 'websearch' | 'doc-preprocess'
|
||||
export type UpdateProviderFunc = (p: Partial<Provider>) => void
|
||||
|
||||
export type UpdateWebSearchProviderFunc = (p: Partial<WebSearchProvider>) => void
|
||||
|
||||
export type UpdatePreprocessProviderFunc = (p: Partial<PreprocessProvider>) => void
|
||||
|
||||
export type UpdateApiProviderFunc = UpdateProviderFunc | UpdateWebSearchProviderFunc | UpdatePreprocessProviderFunc
|
||||
|
||||
@ -2724,7 +2724,7 @@ export function isSupportedThinkingTokenDoubaoModel(model?: Model): boolean {
|
||||
|
||||
const modelId = getLowerBaseModelName(model.id, '/')
|
||||
|
||||
return DOUBAO_THINKING_MODEL_REGEX.test(modelId) || DOUBAO_THINKING_MODEL_REGEX.test(modelId)
|
||||
return DOUBAO_THINKING_MODEL_REGEX.test(modelId) || DOUBAO_THINKING_MODEL_REGEX.test(model.name)
|
||||
}
|
||||
|
||||
export function isClaudeReasoningModel(model?: Model): boolean {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import Doc2xLogo from '@renderer/assets/images/ocr/doc2x.png'
|
||||
import MinerULogo from '@renderer/assets/images/ocr/mineru.jpg'
|
||||
import MistralLogo from '@renderer/assets/images/providers/mistral.png'
|
||||
import { PreprocessProviderId } from '@renderer/types'
|
||||
|
||||
export function getPreprocessProviderLogo(providerId: string) {
|
||||
export function getPreprocessProviderLogo(providerId: PreprocessProviderId) {
|
||||
switch (providerId) {
|
||||
case 'doc2x':
|
||||
return Doc2xLogo
|
||||
@ -15,7 +16,9 @@ export function getPreprocessProviderLogo(providerId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export const PREPROCESS_PROVIDER_CONFIG = {
|
||||
type PreprocessProviderConfig = { websites: { official: string; apiKey: string } }
|
||||
|
||||
export const PREPROCESS_PROVIDER_CONFIG: Record<PreprocessProviderId, PreprocessProviderConfig> = {
|
||||
doc2x: {
|
||||
websites: {
|
||||
official: 'https://doc2x.noedgeai.com',
|
||||
|
||||
@ -1,24 +1,13 @@
|
||||
import BochaLogo from '@renderer/assets/images/search/bocha.webp'
|
||||
import ExaLogo from '@renderer/assets/images/search/exa.png'
|
||||
import SearxngLogo from '@renderer/assets/images/search/searxng.svg'
|
||||
import TavilyLogo from '@renderer/assets/images/search/tavily.png'
|
||||
import { WebSearchProvider, WebSearchProviderId } from '@renderer/types'
|
||||
|
||||
export function getWebSearchProviderLogo(providerId: string) {
|
||||
switch (providerId) {
|
||||
case 'tavily':
|
||||
return TavilyLogo
|
||||
case 'searxng':
|
||||
return SearxngLogo
|
||||
case 'exa':
|
||||
return ExaLogo
|
||||
case 'bocha':
|
||||
return BochaLogo
|
||||
default:
|
||||
return undefined
|
||||
type WebSearchProviderConfig = {
|
||||
websites: {
|
||||
official: string
|
||||
apiKey?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const WEB_SEARCH_PROVIDER_CONFIG = {
|
||||
export const WEB_SEARCH_PROVIDER_CONFIG: Record<WebSearchProviderId, WebSearchProviderConfig> = {
|
||||
tavily: {
|
||||
websites: {
|
||||
official: 'https://tavily.com',
|
||||
@ -58,3 +47,46 @@ export const WEB_SEARCH_PROVIDER_CONFIG = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const WEB_SEARCH_PROVIDERS: WebSearchProvider[] = [
|
||||
{
|
||||
id: 'tavily',
|
||||
name: 'Tavily',
|
||||
apiHost: 'https://api.tavily.com',
|
||||
apiKey: ''
|
||||
},
|
||||
{
|
||||
id: 'searxng',
|
||||
name: 'Searxng',
|
||||
apiHost: '',
|
||||
basicAuthUsername: '',
|
||||
basicAuthPassword: ''
|
||||
},
|
||||
{
|
||||
id: 'exa',
|
||||
name: 'Exa',
|
||||
apiHost: 'https://api.exa.ai',
|
||||
apiKey: ''
|
||||
},
|
||||
{
|
||||
id: 'bocha',
|
||||
name: 'Bocha',
|
||||
apiHost: 'https://api.bochaai.com',
|
||||
apiKey: ''
|
||||
},
|
||||
{
|
||||
id: 'local-google',
|
||||
name: 'Google',
|
||||
url: 'https://www.google.com/search?q=%s'
|
||||
},
|
||||
{
|
||||
id: 'local-bing',
|
||||
name: 'Bing',
|
||||
url: 'https://cn.bing.com/search?q=%s&ensearch=1'
|
||||
},
|
||||
{
|
||||
id: 'local-baidu',
|
||||
name: 'Baidu',
|
||||
url: 'https://www.baidu.com/s?wd=%s'
|
||||
}
|
||||
] as const
|
||||
|
||||
@ -4,10 +4,10 @@ import {
|
||||
updatePreprocessProvider as _updatePreprocessProvider,
|
||||
updatePreprocessProviders as _updatePreprocessProviders
|
||||
} from '@renderer/store/preprocess'
|
||||
import { PreprocessProvider } from '@renderer/types'
|
||||
import { PreprocessProvider, PreprocessProviderId } from '@renderer/types'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
export const usePreprocessProvider = (id: string) => {
|
||||
export const usePreprocessProvider = (id: PreprocessProviderId) => {
|
||||
const dispatch = useDispatch()
|
||||
const preprocessProviders = useSelector((state: RootState) => state.preprocess.providers)
|
||||
const provider = preprocessProviders.find((provider) => provider.id === id)
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
updateWebSearchProvider,
|
||||
updateWebSearchProviders
|
||||
} from '@renderer/store/websearch'
|
||||
import { WebSearchProvider } from '@renderer/types'
|
||||
import { WebSearchProvider, WebSearchProviderId } from '@renderer/types'
|
||||
|
||||
export const useDefaultWebSearchProvider = () => {
|
||||
const defaultProvider = useAppSelector((state) => state.websearch.defaultProvider)
|
||||
@ -49,7 +49,7 @@ export const useWebSearchProviders = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useWebSearchProvider = (id: string) => {
|
||||
export const useWebSearchProvider = (id: WebSearchProviderId) => {
|
||||
const providers = useAppSelector((state) => state.websearch.providers)
|
||||
const provider = providers.find((provider) => provider.id === id)
|
||||
const dispatch = useAppDispatch()
|
||||
@ -60,7 +60,9 @@ export const useWebSearchProvider = (id: string) => {
|
||||
|
||||
return {
|
||||
provider,
|
||||
updateProvider: (updates: Partial<WebSearchProvider>) => dispatch(updateWebSearchProvider({ id, ...updates }))
|
||||
updateProvider: (updates: Partial<WebSearchProvider>) => {
|
||||
dispatch(updateWebSearchProvider({ id, ...updates }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons'
|
||||
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo } from '@renderer/components/Icons'
|
||||
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||
import { isWebSearchModel } from '@renderer/config/models'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useWebSearchProviders } from '@renderer/hooks/useWebSearchProviders'
|
||||
import WebSearchService from '@renderer/services/WebSearchService'
|
||||
import { Assistant, WebSearchProvider } from '@renderer/types'
|
||||
import { Assistant, WebSearchProvider, WebSearchProviderId } from '@renderer/types'
|
||||
import { hasObjectKey } from '@renderer/utils'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Globe } from 'lucide-react'
|
||||
@ -28,6 +30,33 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
|
||||
const enableWebSearch = assistant?.webSearchProviderId || assistant.enableWebSearch
|
||||
|
||||
const WebSearchIcon = useCallback(
|
||||
({ pid, size = 18 }: { pid?: WebSearchProviderId; size?: number }) => {
|
||||
const iconColor = enableWebSearch ? 'var(--color-primary)' : 'var(--color-icon)'
|
||||
|
||||
switch (pid) {
|
||||
case 'bocha':
|
||||
return <BochaLogo width={size} height={size} color={iconColor} />
|
||||
case 'exa':
|
||||
// size微调,视觉上和其他图标平衡一些
|
||||
return <ExaLogo width={size - 2} height={size} color={iconColor} />
|
||||
case 'tavily':
|
||||
return <TavilyLogo width={size} height={size} color={iconColor} />
|
||||
case 'searxng':
|
||||
return <SearXNGLogo width={size} height={size} color={iconColor} />
|
||||
case 'local-baidu':
|
||||
return <BaiduOutlined size={size} style={{ color: iconColor, fontSize: size }} />
|
||||
case 'local-bing':
|
||||
return <BingLogo width={size} height={size} color={iconColor} />
|
||||
case 'local-google':
|
||||
return <GoogleOutlined size={size} style={{ color: iconColor, fontSize: size }} />
|
||||
default:
|
||||
return <Globe size={size} style={{ color: iconColor, fontSize: size }} />
|
||||
}
|
||||
},
|
||||
[enableWebSearch]
|
||||
)
|
||||
|
||||
const updateSelectedWebSearchProvider = useCallback(
|
||||
async (providerId?: WebSearchProvider['id']) => {
|
||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||
@ -58,7 +87,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
? t('settings.tool.websearch.apikey')
|
||||
: t('settings.tool.websearch.free')
|
||||
: t('chat.input.web_search.enable_content'),
|
||||
icon: <Globe />,
|
||||
icon: <WebSearchIcon size={13} pid={p.id} />,
|
||||
isSelected: p.id === assistant?.webSearchProviderId,
|
||||
disabled: !WebSearchService.isWebSearchEnabled(p.id),
|
||||
action: () => updateSelectedWebSearchProvider(p.id)
|
||||
@ -80,6 +109,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
|
||||
return items
|
||||
}, [
|
||||
WebSearchIcon,
|
||||
assistant.enableWebSearch,
|
||||
assistant.model,
|
||||
assistant?.webSearchProviderId,
|
||||
@ -135,12 +165,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
||||
mouseLeaveDelay={0}
|
||||
arrow>
|
||||
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
|
||||
<Globe
|
||||
size={18}
|
||||
style={{
|
||||
color: enableWebSearch ? 'var(--color-primary)' : 'var(--color-icon)'
|
||||
}}
|
||||
/>
|
||||
<WebSearchIcon pid={assistant.webSearchProviderId} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
@ -2,14 +2,14 @@ import { loggerService } from '@logger'
|
||||
import { usePreprocessProvider } from '@renderer/hooks/usePreprocess'
|
||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||
import { getKnowledgeBaseParams } from '@renderer/services/KnowledgeService'
|
||||
import { KnowledgeBase } from '@renderer/types'
|
||||
import { KnowledgeBase, PreprocessProviderId } from '@renderer/types'
|
||||
import { Tag } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logger = loggerService.withContext('QuotaTag')
|
||||
|
||||
const QuotaTag: FC<{ base: KnowledgeBase; providerId: string; quota?: number }> = ({
|
||||
const QuotaTag: FC<{ base: KnowledgeBase; providerId: PreprocessProviderId; quota?: number }> = ({
|
||||
base,
|
||||
providerId,
|
||||
quota: _quota
|
||||
|
||||
@ -4,23 +4,14 @@ import { getPreprocessProviderLogo, PREPROCESS_PROVIDER_CONFIG } from '@renderer
|
||||
import { usePreprocessProvider } from '@renderer/hooks/usePreprocess'
|
||||
import { PreprocessProvider } from '@renderer/types'
|
||||
import { formatApiKeys, hasObjectKey } from '@renderer/utils'
|
||||
import { Avatar, Button, Divider, Flex, Input, InputNumber, Segmented, Tooltip } from 'antd'
|
||||
import { Avatar, Button, Divider, Flex, Input, Tooltip } from 'antd'
|
||||
import Link from 'antd/es/typography/Link'
|
||||
import { List } from 'lucide-react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import {
|
||||
SettingDivider,
|
||||
SettingHelpLink,
|
||||
SettingHelpText,
|
||||
SettingHelpTextRow,
|
||||
SettingRow,
|
||||
SettingRowTitle,
|
||||
SettingSubtitle,
|
||||
SettingTitle
|
||||
} from '..'
|
||||
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle, SettingTitle } from '..'
|
||||
|
||||
interface Props {
|
||||
provider: PreprocessProvider
|
||||
@ -31,7 +22,7 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
|
||||
const { t } = useTranslation()
|
||||
const [apiKey, setApiKey] = useState(preprocessProvider.apiKey || '')
|
||||
const [apiHost, setApiHost] = useState(preprocessProvider.apiHost || '')
|
||||
const [options, setOptions] = useState(preprocessProvider.options || {})
|
||||
// const [options, setOptions] = useState(preprocessProvider.options || {})
|
||||
|
||||
const preprocessProviderConfig = PREPROCESS_PROVIDER_CONFIG[preprocessProvider.id]
|
||||
const apiKeyWebsite = preprocessProviderConfig?.websites?.apiKey
|
||||
@ -40,7 +31,7 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
|
||||
useEffect(() => {
|
||||
setApiKey(preprocessProvider.apiKey ?? '')
|
||||
setApiHost(preprocessProvider.apiHost ?? '')
|
||||
setOptions(preprocessProvider.options ?? {})
|
||||
// setOptions(preprocessProvider.options ?? {})
|
||||
}, [preprocessProvider.apiKey, preprocessProvider.apiHost, preprocessProvider.options])
|
||||
|
||||
const onUpdateApiKey = () => {
|
||||
@ -52,7 +43,6 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
|
||||
const openApiKeyList = async () => {
|
||||
await ApiKeyListPopup.show({
|
||||
providerId: preprocessProvider.id,
|
||||
providerKind: 'doc-preprocess',
|
||||
title: `${preprocessProvider.name} ${t('settings.provider.api.key.list.title')}`,
|
||||
showHealthCheck: false // FIXME: 目前还没有检查功能
|
||||
})
|
||||
@ -70,11 +60,11 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const onUpdateOptions = (key: string, value: any) => {
|
||||
const newOptions = { ...options, [key]: value }
|
||||
setOptions(newOptions)
|
||||
updateProvider({ options: newOptions })
|
||||
}
|
||||
// const onUpdateOptions = (key: string, value: any) => {
|
||||
// const newOptions = { ...options, [key]: value }
|
||||
// setOptions(newOptions)
|
||||
// updateProvider({ options: newOptions })
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -145,7 +135,7 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
|
||||
)}
|
||||
|
||||
{/* 这部分看起来暂时用不上了 */}
|
||||
{hasObjectKey(preprocessProvider, 'options') && preprocessProvider.id === 'system' && (
|
||||
{/* {hasObjectKey(preprocessProvider, 'options') && preprocessProvider.id === 'system' && (
|
||||
<>
|
||||
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
|
||||
<SettingRow>
|
||||
@ -177,7 +167,7 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useDefaultPreprocessProvider, usePreprocessProviders } from '@renderer/hooks/usePreprocess'
|
||||
import { PreprocessProvider } from '@renderer/types'
|
||||
@ -40,8 +39,9 @@ const PreprocessSettings: FC = () => {
|
||||
placeholder={t('settings.tool.preprocess.provider_placeholder')}
|
||||
options={preprocessProviders.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
disabled: !isMac && p.id === 'system' // 在非 Mac 系统下禁用 system 选项
|
||||
label: p.name
|
||||
// 由于system字段实际未使用,先注释掉
|
||||
// disabled: !isMac && p.id === 'system' // 在非 Mac 系统下禁用 system 选项
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -128,7 +128,6 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
const openApiKeyList = async () => {
|
||||
await ApiKeyListPopup.show({
|
||||
providerId: provider.id,
|
||||
providerKind: 'llm',
|
||||
title: `${fancyProviderName} ${t('settings.provider.api.key.list.title')}`
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import { CheckOutlined, ExportOutlined, LoadingOutlined } from '@ant-design/icons'
|
||||
import { loggerService } from '@logger'
|
||||
import BochaLogo from '@renderer/assets/images/search/bocha.webp'
|
||||
import ExaLogo from '@renderer/assets/images/search/exa.png'
|
||||
import SearxngLogo from '@renderer/assets/images/search/searxng.svg'
|
||||
import TavilyLogo from '@renderer/assets/images/search/tavily.png'
|
||||
import ApiKeyListPopup from '@renderer/components/Popups/ApiKeyListPopup/popup'
|
||||
import { getWebSearchProviderLogo, WEB_SEARCH_PROVIDER_CONFIG } from '@renderer/config/webSearchProviders'
|
||||
import { WEB_SEARCH_PROVIDER_CONFIG } from '@renderer/config/webSearchProviders'
|
||||
import { useWebSearchProvider } from '@renderer/hooks/useWebSearchProviders'
|
||||
import WebSearchService from '@renderer/services/WebSearchService'
|
||||
import { WebSearchProviderId } from '@renderer/types'
|
||||
import { formatApiKeys, hasObjectKey } from '@renderer/utils'
|
||||
import { Button, Divider, Flex, Form, Input, Space, Tooltip } from 'antd'
|
||||
import Link from 'antd/es/typography/Link'
|
||||
@ -16,7 +21,7 @@ import { SettingDivider, SettingHelpLink, SettingHelpText, SettingHelpTextRow, S
|
||||
|
||||
const logger = loggerService.withContext('WebSearchProviderSetting')
|
||||
interface Props {
|
||||
providerId: string
|
||||
providerId: WebSearchProviderId
|
||||
}
|
||||
|
||||
const WebSearchProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
@ -74,7 +79,6 @@ const WebSearchProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
const openApiKeyList = async () => {
|
||||
await ApiKeyListPopup.show({
|
||||
providerId: provider.id,
|
||||
providerKind: 'websearch',
|
||||
title: `${provider.name} ${t('settings.provider.api.key.list.title')}`
|
||||
})
|
||||
}
|
||||
@ -132,6 +136,21 @@ const WebSearchProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
setBasicAuthPassword(provider.basicAuthPassword ?? '')
|
||||
}, [provider.apiKey, provider.apiHost, provider.basicAuthUsername, provider.basicAuthPassword])
|
||||
|
||||
const getWebSearchProviderLogo = (providerId: WebSearchProviderId) => {
|
||||
switch (providerId) {
|
||||
case 'tavily':
|
||||
return TavilyLogo
|
||||
case 'searxng':
|
||||
return SearxngLogo
|
||||
case 'exa':
|
||||
return ExaLogo
|
||||
case 'bocha':
|
||||
return BochaLogo
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingTitle>
|
||||
|
||||
@ -40,6 +40,7 @@ export const createCitationCallbacks = (deps: CitationCallbacksDependencies) =>
|
||||
status: MessageBlockStatus.SUCCESS
|
||||
}
|
||||
blockManager.smartBlockUpdate(citationBlockId, changes, MessageBlockType.CITATION, true)
|
||||
citationBlockId = null
|
||||
} else {
|
||||
logger.error('[onExternalToolComplete] citationBlockId is null. Cannot update.')
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { WEB_SEARCH_PROVIDERS } from '@renderer/config/webSearchProviders'
|
||||
import type { Model, WebSearchProvider } from '@renderer/types'
|
||||
export interface SubscribeSource {
|
||||
key: number
|
||||
@ -42,48 +43,7 @@ export interface WebSearchState {
|
||||
|
||||
export const initialState: WebSearchState = {
|
||||
defaultProvider: 'local-bing',
|
||||
providers: [
|
||||
{
|
||||
id: 'tavily',
|
||||
name: 'Tavily',
|
||||
apiHost: 'https://api.tavily.com',
|
||||
apiKey: ''
|
||||
},
|
||||
{
|
||||
id: 'searxng',
|
||||
name: 'Searxng',
|
||||
apiHost: '',
|
||||
basicAuthUsername: '',
|
||||
basicAuthPassword: ''
|
||||
},
|
||||
{
|
||||
id: 'exa',
|
||||
name: 'Exa',
|
||||
apiHost: 'https://api.exa.ai',
|
||||
apiKey: ''
|
||||
},
|
||||
{
|
||||
id: 'bocha',
|
||||
name: 'Bocha',
|
||||
apiHost: 'https://api.bochaai.com',
|
||||
apiKey: ''
|
||||
},
|
||||
{
|
||||
id: 'local-google',
|
||||
name: 'Google',
|
||||
url: 'https://www.google.com/search?q=%s'
|
||||
},
|
||||
{
|
||||
id: 'local-bing',
|
||||
name: 'Bing',
|
||||
url: 'https://cn.bing.com/search?q=%s&ensearch=1'
|
||||
},
|
||||
{
|
||||
id: 'local-baidu',
|
||||
name: 'Baidu',
|
||||
url: 'https://www.baidu.com/s?wd=%s'
|
||||
}
|
||||
],
|
||||
providers: WEB_SEARCH_PROVIDERS,
|
||||
searchWithTime: true,
|
||||
maxResults: 5,
|
||||
excludeDomains: [],
|
||||
@ -111,7 +71,7 @@ const websearchSlice = createSlice({
|
||||
updateWebSearchProviders: (state, action: PayloadAction<WebSearchProvider[]>) => {
|
||||
state.providers = action.payload
|
||||
},
|
||||
updateWebSearchProvider: (state, action: PayloadAction<Partial<WebSearchProvider> & { id: string }>) => {
|
||||
updateWebSearchProvider: (state, action: PayloadAction<Partial<WebSearchProvider>>) => {
|
||||
const index = state.providers.findIndex((provider) => provider.id === action.payload.id)
|
||||
if (index !== -1) {
|
||||
Object.assign(state.providers[index], action.payload)
|
||||
|
||||
@ -609,8 +609,20 @@ export type KnowledgeBaseParams = {
|
||||
}
|
||||
}
|
||||
|
||||
export const PreprocessProviderIds = {
|
||||
doc2x: 'doc2x',
|
||||
mistral: 'mistral',
|
||||
mineru: 'mineru'
|
||||
} as const
|
||||
|
||||
export type PreprocessProviderId = keyof typeof PreprocessProviderIds
|
||||
|
||||
export const isPreprocessProviderId = (id: string): id is PreprocessProviderId => {
|
||||
return Object.hasOwn(PreprocessProviderIds, id)
|
||||
}
|
||||
|
||||
export interface PreprocessProvider {
|
||||
id: string
|
||||
id: PreprocessProviderId
|
||||
name: string
|
||||
apiKey?: string
|
||||
apiHost?: string
|
||||
@ -675,8 +687,24 @@ export type ExternalToolResult = {
|
||||
memories?: MemoryItem[]
|
||||
}
|
||||
|
||||
export const WebSearchProviderIds = {
|
||||
tavily: 'tavily',
|
||||
searxng: 'searxng',
|
||||
exa: 'exa',
|
||||
bocha: 'bocha',
|
||||
'local-google': 'local-google',
|
||||
'local-bing': 'local-bing',
|
||||
'local-baidu': 'local-baidu'
|
||||
} as const
|
||||
|
||||
export type WebSearchProviderId = keyof typeof WebSearchProviderIds
|
||||
|
||||
export const isWebSearchProviderId = (id: string): id is WebSearchProviderId => {
|
||||
return Object.hasOwn(WebSearchProviderIds, id)
|
||||
}
|
||||
|
||||
export type WebSearchProvider = {
|
||||
id: string
|
||||
id: WebSearchProviderId
|
||||
name: string
|
||||
apiKey?: string
|
||||
apiHost?: string
|
||||
|
||||
148
yarn.lock
148
yarn.lock
@ -75,10 +75,10 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/amazon-bedrock@npm:^3.0.0":
|
||||
version: 3.0.7
|
||||
resolution: "@ai-sdk/amazon-bedrock@npm:3.0.7"
|
||||
version: 3.0.8
|
||||
resolution: "@ai-sdk/amazon-bedrock@npm:3.0.8"
|
||||
dependencies:
|
||||
"@ai-sdk/anthropic": "npm:2.0.3"
|
||||
"@ai-sdk/anthropic": "npm:2.0.4"
|
||||
"@ai-sdk/provider": "npm:2.0.0"
|
||||
"@ai-sdk/provider-utils": "npm:3.0.3"
|
||||
"@smithy/eventstream-codec": "npm:^4.0.1"
|
||||
@ -86,7 +86,7 @@ __metadata:
|
||||
aws4fetch: "npm:^1.0.20"
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
checksum: 10c0/96d118413db091fd325edc0a0900a7376ddeecb63de9d0bd00eca5f0f8ac86185eafe28740b19829a6cd873b304cc6ba6e3313bc435fdc6156abf9b705db1af4
|
||||
checksum: 10c0/d7b303b8581e9d28e9ac375b3718ef3f7fff3353d18185870f0b90fd542eb9398d029768502981e9e45a6b64137a7029f591993afd0b18e9ef74525f625524f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -102,15 +102,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/anthropic@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@ai-sdk/anthropic@npm:2.0.3"
|
||||
"@ai-sdk/anthropic@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@ai-sdk/anthropic@npm:2.0.4"
|
||||
dependencies:
|
||||
"@ai-sdk/provider": "npm:2.0.0"
|
||||
"@ai-sdk/provider-utils": "npm:3.0.3"
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
checksum: 10c0/733861197ae6c7fe93e9dc131a2176bd654eeee0b0575bb164f4259ecdb23d85039a0ef3ba4b1ad2de299c275b97d0fb1b0be0cff224713e81d0a31749ae1070
|
||||
checksum: 10c0/2e5a997b6e2d9a2964c4681418643fd2f347df78ac1f9677a0cc6a3a3454920d05c663e35521d8922f0a382ec77a25e4b92204b3760a1da05876bf00d41adc39
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -153,17 +153,17 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/google-vertex@npm:^3.0.0":
|
||||
version: 3.0.8
|
||||
resolution: "@ai-sdk/google-vertex@npm:3.0.8"
|
||||
version: 3.0.9
|
||||
resolution: "@ai-sdk/google-vertex@npm:3.0.9"
|
||||
dependencies:
|
||||
"@ai-sdk/anthropic": "npm:2.0.3"
|
||||
"@ai-sdk/anthropic": "npm:2.0.4"
|
||||
"@ai-sdk/google": "npm:2.0.6"
|
||||
"@ai-sdk/provider": "npm:2.0.0"
|
||||
"@ai-sdk/provider-utils": "npm:3.0.3"
|
||||
google-auth-library: "npm:^9.15.0"
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4
|
||||
checksum: 10c0/3d0b89f64fcd9eafa7a1af5f21cf317fa3c3d2ca1fae55bc5ab22e969ddc3f6db6cb918b9bb27ec5b3f32eca518e0529877db2826abfe8fce64426bda3e0775f
|
||||
checksum: 10c0/c6584b877f9e20a10dd7d92fc4cb1b4a9838510aa89734cf1ff2faa74ba820b976d3359d4eadcb6035c8911973300efb157931fa0d1105abc8db36f94544cc88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -1285,38 +1285,38 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@babel/core@npm:^7.27.7":
|
||||
version: 7.28.0
|
||||
resolution: "@babel/core@npm:7.28.0"
|
||||
version: 7.28.3
|
||||
resolution: "@babel/core@npm:7.28.3"
|
||||
dependencies:
|
||||
"@ampproject/remapping": "npm:^2.2.0"
|
||||
"@babel/code-frame": "npm:^7.27.1"
|
||||
"@babel/generator": "npm:^7.28.0"
|
||||
"@babel/generator": "npm:^7.28.3"
|
||||
"@babel/helper-compilation-targets": "npm:^7.27.2"
|
||||
"@babel/helper-module-transforms": "npm:^7.27.3"
|
||||
"@babel/helpers": "npm:^7.27.6"
|
||||
"@babel/parser": "npm:^7.28.0"
|
||||
"@babel/helper-module-transforms": "npm:^7.28.3"
|
||||
"@babel/helpers": "npm:^7.28.3"
|
||||
"@babel/parser": "npm:^7.28.3"
|
||||
"@babel/template": "npm:^7.27.2"
|
||||
"@babel/traverse": "npm:^7.28.0"
|
||||
"@babel/types": "npm:^7.28.0"
|
||||
"@babel/traverse": "npm:^7.28.3"
|
||||
"@babel/types": "npm:^7.28.2"
|
||||
convert-source-map: "npm:^2.0.0"
|
||||
debug: "npm:^4.1.0"
|
||||
gensync: "npm:^1.0.0-beta.2"
|
||||
json5: "npm:^2.2.3"
|
||||
semver: "npm:^6.3.1"
|
||||
checksum: 10c0/423302e7c721e73b1c096217880272e02020dfb697a55ccca60ad01bba90037015f84d0c20c6ce297cf33a19bb704bc5c2b3d3095f5284dfa592bd1de0b9e8c3
|
||||
checksum: 10c0/e6b3eb830c4b93f5a442b305776df1cd2bb4fafa4612355366f67c764f3e54a69d45b84def77fb2d4fd83439102667b0a92c3ea2838f678733245b748c602a7b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/generator@npm:^7.28.0":
|
||||
version: 7.28.0
|
||||
resolution: "@babel/generator@npm:7.28.0"
|
||||
"@babel/generator@npm:^7.28.0, @babel/generator@npm:^7.28.3":
|
||||
version: 7.28.3
|
||||
resolution: "@babel/generator@npm:7.28.3"
|
||||
dependencies:
|
||||
"@babel/parser": "npm:^7.28.0"
|
||||
"@babel/types": "npm:^7.28.0"
|
||||
"@babel/parser": "npm:^7.28.3"
|
||||
"@babel/types": "npm:^7.28.2"
|
||||
"@jridgewell/gen-mapping": "npm:^0.3.12"
|
||||
"@jridgewell/trace-mapping": "npm:^0.3.28"
|
||||
jsesc: "npm:^3.0.2"
|
||||
checksum: 10c0/1b3d122268ea3df50fde707ad864d9a55c72621357d5cebb972db3dd76859c45810c56e16ad23123f18f80cc2692f5a015d2858361300f0f224a05dc43d36a92
|
||||
checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -1350,16 +1350,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/helper-module-transforms@npm:^7.27.3":
|
||||
version: 7.27.3
|
||||
resolution: "@babel/helper-module-transforms@npm:7.27.3"
|
||||
"@babel/helper-module-transforms@npm:^7.28.3":
|
||||
version: 7.28.3
|
||||
resolution: "@babel/helper-module-transforms@npm:7.28.3"
|
||||
dependencies:
|
||||
"@babel/helper-module-imports": "npm:^7.27.1"
|
||||
"@babel/helper-validator-identifier": "npm:^7.27.1"
|
||||
"@babel/traverse": "npm:^7.27.3"
|
||||
"@babel/traverse": "npm:^7.28.3"
|
||||
peerDependencies:
|
||||
"@babel/core": ^7.0.0
|
||||
checksum: 10c0/fccb4f512a13b4c069af51e1b56b20f54024bcf1591e31e978a30f3502567f34f90a80da6a19a6148c249216292a8074a0121f9e52602510ef0f32dbce95ca01
|
||||
checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -1391,24 +1391,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/helpers@npm:^7.27.6":
|
||||
version: 7.28.2
|
||||
resolution: "@babel/helpers@npm:7.28.2"
|
||||
"@babel/helpers@npm:^7.28.3":
|
||||
version: 7.28.3
|
||||
resolution: "@babel/helpers@npm:7.28.3"
|
||||
dependencies:
|
||||
"@babel/template": "npm:^7.27.2"
|
||||
"@babel/types": "npm:^7.28.2"
|
||||
checksum: 10c0/f3e7b21517e2699c4ca193663ecfb1bf1b2ae2762d8ba4a9f1786feaca0d6984537fc60bf2206e92c43640a6dada6b438f523cc1ad78610d0151aeb061b37f63
|
||||
checksum: 10c0/03a8f94135415eec62d37be9c62c63908f2d5386c7b00e04545de4961996465775330e3eb57717ea7451e19b0e24615777ebfec408c2adb1df3b10b4df6bf1ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.0":
|
||||
version: 7.28.0
|
||||
resolution: "@babel/parser@npm:7.28.0"
|
||||
"@babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.0, @babel/parser@npm:^7.28.3":
|
||||
version: 7.28.3
|
||||
resolution: "@babel/parser@npm:7.28.3"
|
||||
dependencies:
|
||||
"@babel/types": "npm:^7.28.0"
|
||||
"@babel/types": "npm:^7.28.2"
|
||||
bin:
|
||||
parser: ./bin/babel-parser.js
|
||||
checksum: 10c0/c2ef81d598990fa949d1d388429df327420357cb5200271d0d0a2784f1e6d54afc8301eb8bdf96d8f6c77781e402da93c7dc07980fcc136ac5b9d5f1fce701b5
|
||||
checksum: 10c0/1f41eb82623b0ca0f94521b57f4790c6c457cd922b8e2597985b36bdec24114a9ccf54640286a760ceb60f11fe9102d192bf60477aee77f5d45f1029b9b72729
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -1424,9 +1424,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
|
||||
version: 7.28.2
|
||||
resolution: "@babel/runtime@npm:7.28.2"
|
||||
checksum: 10c0/c20afe253629d53a405a610b12a62ac74d341a2c1e0fb202bbef0c118f6b5c84f94bf16039f58fd0483dd256901259930a43976845bdeb180cab1f882c21b6e0
|
||||
version: 7.28.3
|
||||
resolution: "@babel/runtime@npm:7.28.3"
|
||||
checksum: 10c0/b360f82c2c5114f2a062d4d143d7b4ec690094764853937110585a9497977aed66c102166d0e404766c274e02a50ffb8f6d77fef7251ecf3f607f0e03e6397bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -1441,22 +1441,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.28.0":
|
||||
version: 7.28.0
|
||||
resolution: "@babel/traverse@npm:7.28.0"
|
||||
"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3":
|
||||
version: 7.28.3
|
||||
resolution: "@babel/traverse@npm:7.28.3"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:^7.27.1"
|
||||
"@babel/generator": "npm:^7.28.0"
|
||||
"@babel/generator": "npm:^7.28.3"
|
||||
"@babel/helper-globals": "npm:^7.28.0"
|
||||
"@babel/parser": "npm:^7.28.0"
|
||||
"@babel/parser": "npm:^7.28.3"
|
||||
"@babel/template": "npm:^7.27.2"
|
||||
"@babel/types": "npm:^7.28.0"
|
||||
"@babel/types": "npm:^7.28.2"
|
||||
debug: "npm:^4.3.1"
|
||||
checksum: 10c0/32794402457827ac558173bcebdcc0e3a18fa339b7c41ca35621f9f645f044534d91bb923ff385f5f960f2e495f56ce18d6c7b0d064d2f0ccb55b285fa6bc7b9
|
||||
checksum: 10c0/26e95b29a46925b7b41255e03185b7e65b2c4987e14bbee7bbf95867fb19c69181f301bbe1c7b201d4fe0cce6aa0cbea0282dad74b3a0fef3d9058f6c76fdcb3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.25.4, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.0, @babel/types@npm:^7.28.1, @babel/types@npm:^7.28.2":
|
||||
"@babel/types@npm:^7.25.4, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.1, @babel/types@npm:^7.28.2":
|
||||
version: 7.28.2
|
||||
resolution: "@babel/types@npm:7.28.2"
|
||||
dependencies:
|
||||
@ -3973,8 +3973,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@modelcontextprotocol/sdk@npm:^1.17.0":
|
||||
version: 1.17.2
|
||||
resolution: "@modelcontextprotocol/sdk@npm:1.17.2"
|
||||
version: 1.17.3
|
||||
resolution: "@modelcontextprotocol/sdk@npm:1.17.3"
|
||||
dependencies:
|
||||
ajv: "npm:^6.12.6"
|
||||
content-type: "npm:^1.0.5"
|
||||
@ -3988,7 +3988,7 @@ __metadata:
|
||||
raw-body: "npm:^3.0.0"
|
||||
zod: "npm:^3.23.8"
|
||||
zod-to-json-schema: "npm:^3.24.1"
|
||||
checksum: 10c0/9f0d00dc5f96a6bc78b775d2ce57d72270cf3b0fad08209f0061e30fff6e2005b7e6abbef98fbc030ba7e3aa5c78e5211f9b5c2a3d0d8f4455dfd47fef228720
|
||||
checksum: 10c0/23df0949724279eaa620f2780e3c731dcf746311f3175e3cba602643aacf9ee6dbcf99daeab3fa848296fe9ac971456fc031c79c1b55870dd019baf0263fd080
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -5927,21 +5927,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/query-core@npm:5.85.2":
|
||||
version: 5.85.2
|
||||
resolution: "@tanstack/query-core@npm:5.85.2"
|
||||
checksum: 10c0/398386c0e56b0ab0916951468b6b6a2b9f1f713fa95757405171f1c99c7a33cd3427eb20a82f252e5abc63ccd93e8e77c7cc5bc30043401d0dd5a074d7f5ff4e
|
||||
"@tanstack/query-core@npm:5.85.3":
|
||||
version: 5.85.3
|
||||
resolution: "@tanstack/query-core@npm:5.85.3"
|
||||
checksum: 10c0/7db9e78c3648a3d5bc295fff14e7afdf061e38ca55b0004e3f6e6f1f44596f564bf8b59c97b2d1f7ce851792c8eac7008608ccd4de0dfcd6349a4e0943b7d247
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/react-query@npm:^5.27.0":
|
||||
version: 5.85.2
|
||||
resolution: "@tanstack/react-query@npm:5.85.2"
|
||||
version: 5.85.3
|
||||
resolution: "@tanstack/react-query@npm:5.85.3"
|
||||
dependencies:
|
||||
"@tanstack/query-core": "npm:5.85.2"
|
||||
"@tanstack/query-core": "npm:5.85.3"
|
||||
peerDependencies:
|
||||
react: ^18 || ^19
|
||||
checksum: 10c0/8feb9a82d25589b7f3a9cc1e3f24ec2b29b35e86522ca94b3f94dc2649e4a4472da7782b2e1f0b7a48722d11e8c34cf77711b9fbad474b1b73478ae0f06c9ba0
|
||||
checksum: 10c0/3588a6997c5f5302d999f98230894ad3ed5d24b0b063b62c34300e2cd3a715afc7edd97eb8a99408f2f7f85e76812fa87d035dbbc340c95adecade80d1e44ae0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -7871,7 +7871,7 @@ __metadata:
|
||||
remove-markdown: "npm:^0.6.2"
|
||||
rollup-plugin-visualizer: "npm:^5.12.0"
|
||||
sass: "npm:^1.88.0"
|
||||
selection-hook: "npm:^1.0.8"
|
||||
selection-hook: "npm:^1.0.9"
|
||||
shiki: "npm:^3.9.1"
|
||||
strict-url-sanitise: "npm:^0.0.1"
|
||||
string-width: "npm:^7.2.0"
|
||||
@ -11001,9 +11001,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.5.199":
|
||||
version: 1.5.200
|
||||
resolution: "electron-to-chromium@npm:1.5.200"
|
||||
checksum: 10c0/3f323c097d41c31da09632df30fa26a887c4805fb3a12196eaededc4337cc3b62530d2c80d18ed352c7732c8edfadefbc604ccefe6d4e83c1156c2ed1525bdee
|
||||
version: 1.5.201
|
||||
resolution: "electron-to-chromium@npm:1.5.201"
|
||||
checksum: 10c0/83f415506e4f79ebe3bcf311526823fe73cd9d54938eff6d98504d222d7c1f7db21acbe98b30394349b1dc973ac85123f1afb5f2b495e166885e5ddd7cf086e6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -12111,14 +12111,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"fdir@npm:^6.4.4, fdir@npm:^6.4.6":
|
||||
version: 6.4.6
|
||||
resolution: "fdir@npm:6.4.6"
|
||||
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
|
||||
|
||||
@ -14398,8 +14398,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"langsmith@npm:>=0.2.8 <0.4.0, langsmith@npm:^0.3.33, langsmith@npm:^0.3.46":
|
||||
version: 0.3.59
|
||||
resolution: "langsmith@npm:0.3.59"
|
||||
version: 0.3.61
|
||||
resolution: "langsmith@npm:0.3.61"
|
||||
dependencies:
|
||||
"@types/uuid": "npm:^10.0.0"
|
||||
chalk: "npm:^4.1.2"
|
||||
@ -14422,7 +14422,7 @@ __metadata:
|
||||
optional: true
|
||||
openai:
|
||||
optional: true
|
||||
checksum: 10c0/e6c8c1f9f88a76116efa7fbb1af38be266759eb4e9a4c498e80e2d6cf5b456bbf0e607077cd14e0d864234685a0553b67f96bbc6b05f985b797cc41da8d6a682
|
||||
checksum: 10c0/2aaf611bf8f7b2e44d9266415a9933b5f94986e02a2fa89be560b9118b118f9c8722d1a3c677f1bbeac17d3330706e7bbfeeeaa814587de89d8a18fd97c0a45b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -19795,7 +19795,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"selection-hook@npm:^1.0.8":
|
||||
"selection-hook@npm:^1.0.9":
|
||||
version: 1.0.9
|
||||
resolution: "selection-hook@npm:1.0.9"
|
||||
dependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user