Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06ce720d6e | |||
| 75fcf8fbb5 | |||
| 35aa9d7355 | |||
| b08aecb22b | |||
| 45fc6c2afd | |||
| d6e7ce330e | |||
| 4f7d8731ea | |||
| 2b5ac5ab51 |
@@ -107,6 +107,8 @@
|
||||
"@agentic/searxng": "^7.3.3",
|
||||
"@agentic/tavily": "^7.3.3",
|
||||
"@ai-sdk/amazon-bedrock": "^3.0.53",
|
||||
"@ai-sdk/cerebras": "^1.0.31",
|
||||
"@ai-sdk/gateway": "^2.0.9",
|
||||
"@ai-sdk/google-vertex": "^3.0.62",
|
||||
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch",
|
||||
"@ai-sdk/mistral": "^2.0.23",
|
||||
|
||||
@@ -485,6 +485,12 @@ class FileStorage {
|
||||
): Promise<string> => {
|
||||
const filePath = path.join(this.storageDir, id)
|
||||
|
||||
// 先检查文件是否存在,避免 ENOENT 错误
|
||||
if (!fs.existsSync(filePath)) {
|
||||
logger.debug(`File not found: ${filePath}`)
|
||||
throw new Error(`ENOENT: no such file or directory, open '${id}'`)
|
||||
}
|
||||
|
||||
const fileExtension = path.extname(filePath)
|
||||
|
||||
if (documentExts.includes(fileExtension)) {
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
* 2. 暂时保持接口兼容性
|
||||
*/
|
||||
|
||||
import type { GatewayLanguageModelEntry } from '@ai-sdk/gateway'
|
||||
import { createExecutor } from '@cherrystudio/ai-core'
|
||||
import { loggerService } from '@logger'
|
||||
import { getEnableDeveloperMode } from '@renderer/hooks/useSettings'
|
||||
import { addSpan, endSpan } from '@renderer/services/SpanManagerService'
|
||||
import type { StartSpanParams } from '@renderer/trace/types/ModelSpanEntity'
|
||||
import type { Assistant, GenerateImageParams, Model, Provider } from '@renderer/types'
|
||||
import { type Assistant, type GenerateImageParams, type Model, type Provider, SystemProviderIds } from '@renderer/types'
|
||||
import type { AiSdkModel, StreamTextParams } from '@renderer/types/aiCoreTypes'
|
||||
import { SUPPORTED_IMAGE_ENDPOINT_LIST } from '@renderer/utils'
|
||||
import { buildClaudeCodeSystemModelMessage } from '@shared/anthropic'
|
||||
import { type ImageModel, type LanguageModel, type Provider as AiSdkProvider, wrapLanguageModel } from 'ai'
|
||||
import { gateway, type ImageModel, type LanguageModel, type Provider as AiSdkProvider, wrapLanguageModel } from 'ai'
|
||||
|
||||
import AiSdkToChunkAdapter from './chunk/AiSdkToChunkAdapter'
|
||||
import LegacyAiProvider from './legacy/index'
|
||||
@@ -439,6 +440,18 @@ export default class ModernAiProvider {
|
||||
|
||||
// 代理其他方法到原有实现
|
||||
public async models() {
|
||||
if (this.actualProvider.id === SystemProviderIds['ai-gateway']) {
|
||||
const formatModel = function (models: GatewayLanguageModelEntry[]): Model[] {
|
||||
return models.map((m) => ({
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
provider: 'gateway',
|
||||
group: m.id.split('/')[0],
|
||||
description: m.description ?? undefined
|
||||
}))
|
||||
}
|
||||
return formatModel((await gateway.getAvailableModels()).models)
|
||||
}
|
||||
return this.legacyProvider.models()
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,21 @@ export const NEW_PROVIDER_CONFIGS: ProviderConfig[] = [
|
||||
creatorFunctionName: 'createHuggingFace',
|
||||
supportsImageGeneration: true,
|
||||
aliases: ['hf', 'hugging-face']
|
||||
},
|
||||
{
|
||||
id: 'ai-gateway',
|
||||
name: 'AI Gateway',
|
||||
import: () => import('@ai-sdk/gateway'),
|
||||
creatorFunctionName: 'createGateway',
|
||||
supportsImageGeneration: true,
|
||||
aliases: ['gateway']
|
||||
},
|
||||
{
|
||||
id: 'cerebras',
|
||||
name: 'Cerebras',
|
||||
import: () => import('@ai-sdk/cerebras'),
|
||||
creatorFunctionName: 'createCerebras',
|
||||
supportsImageGeneration: false
|
||||
}
|
||||
] as const
|
||||
|
||||
|
||||
@@ -151,11 +151,12 @@ export function buildProviderOptions(
|
||||
...providerSpecificOptions,
|
||||
...getCustomParameters(assistant)
|
||||
}
|
||||
// vertex需要映射到google或anthropic
|
||||
|
||||
const rawProviderKey =
|
||||
{
|
||||
'google-vertex': 'google',
|
||||
'google-vertex-anthropic': 'anthropic'
|
||||
'google-vertex-anthropic': 'anthropic',
|
||||
'ai-gateway': 'gateway'
|
||||
}[rawProviderId] || rawProviderId
|
||||
|
||||
// 返回 AI Core SDK 要求的格式:{ 'providerId': providerOptions }
|
||||
|
||||
@@ -109,6 +109,11 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
||||
|
||||
// use thinking, doubao, zhipu, etc.
|
||||
if (isSupportedThinkingTokenDoubaoModel(model) || isSupportedThinkingTokenZhipuModel(model)) {
|
||||
if (provider.id === SystemProviderIds.cerebras) {
|
||||
return {
|
||||
disable_reasoning: true
|
||||
}
|
||||
}
|
||||
return { thinking: { type: 'disabled' } }
|
||||
}
|
||||
|
||||
@@ -306,6 +311,9 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
||||
return {}
|
||||
}
|
||||
if (isSupportedThinkingTokenZhipuModel(model)) {
|
||||
if (provider.id === SystemProviderIds.cerebras) {
|
||||
return {}
|
||||
}
|
||||
return { thinking: { type: 'enabled' } }
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Vercel</title><path d="M12 0l12 20.785H0L12 0z"></path></svg>
|
||||
|
After Width: | Height: | Size: 225 B |
@@ -1,5 +1,6 @@
|
||||
import { ActionIconButton } from '@renderer/components/Buttons'
|
||||
import NarrowLayout from '@renderer/pages/home/Messages/NarrowLayout'
|
||||
import { scrollElementIntoView } from '@renderer/utils'
|
||||
import { Tooltip } from 'antd'
|
||||
import { debounce } from 'lodash'
|
||||
import { CaseSensitive, ChevronDown, ChevronUp, User, WholeWord, X } from 'lucide-react'
|
||||
@@ -181,17 +182,14 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
|
||||
// 3. 将当前项滚动到视图中
|
||||
// 获取第一个文本节点的父元素来进行滚动
|
||||
const parentElement = currentMatchRange.startContainer.parentElement
|
||||
if (shouldScroll) {
|
||||
parentElement?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
})
|
||||
if (shouldScroll && parentElement) {
|
||||
// 优先在指定的滚动容器内滚动,避免滚动整个页面导致索引错乱/看起来"跳到第一条"
|
||||
scrollElementIntoView(parentElement, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[allRanges, currentIndex]
|
||||
[allRanges, currentIndex, target]
|
||||
)
|
||||
|
||||
const search = useCallback(
|
||||
|
||||
@@ -87,8 +87,27 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
|
||||
danger: true,
|
||||
onClick: async () => {
|
||||
try {
|
||||
const content = await window.api.file.read('custom-minapps.json')
|
||||
const customApps = JSON.parse(content)
|
||||
let content: string
|
||||
let customApps: MinAppType[]
|
||||
|
||||
try {
|
||||
content = await window.api.file.read('custom-minapps.json')
|
||||
customApps = JSON.parse(content)
|
||||
// 确保解析结果是数组
|
||||
if (!Array.isArray(customApps)) {
|
||||
customApps = []
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 如果文件不存在或解析失败,使用空数组
|
||||
if (error.message?.includes('ENOENT') || error.message?.includes('no such file')) {
|
||||
customApps = []
|
||||
} else {
|
||||
// 其他解析错误,使用空数组
|
||||
logger.warn('Failed to read custom-minapps.json, using empty array:', error as Error)
|
||||
customApps = []
|
||||
}
|
||||
}
|
||||
|
||||
const updatedApps = customApps.filter((customApp: MinAppType) => customApp.id !== app.id)
|
||||
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(updatedApps, null, 2))
|
||||
window.toast.success(t('settings.miniapps.custom.remove_success'))
|
||||
|
||||
@@ -67,13 +67,42 @@ const loadCustomMiniApp = async (): Promise<MinAppType[]> => {
|
||||
let content: string
|
||||
try {
|
||||
content = await window.api.file.read('custom-minapps.json')
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
// 如果文件不存在,创建一个空的 JSON 数组
|
||||
content = '[]'
|
||||
await window.api.file.writeWithId('custom-minapps.json', content)
|
||||
// 检查是否是文件不存在的错误
|
||||
if (error.message?.includes('ENOENT') || error.message?.includes('no such file')) {
|
||||
content = '[]'
|
||||
try {
|
||||
await window.api.file.writeWithId('custom-minapps.json', content)
|
||||
} catch (writeError) {
|
||||
logger.warn('Failed to create custom-minapps.json file:', writeError as Error)
|
||||
// 如果创建文件失败,仍然返回空数组
|
||||
}
|
||||
} else {
|
||||
// 其他类型的错误,记录并返回空数组
|
||||
logger.error('Failed to read custom-minapps.json:', error as Error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 确保内容有效
|
||||
if (!content || content.trim() === '') {
|
||||
content = '[]'
|
||||
}
|
||||
|
||||
let customApps: any[]
|
||||
try {
|
||||
customApps = JSON.parse(content)
|
||||
// 确保解析结果是数组
|
||||
if (!Array.isArray(customApps)) {
|
||||
logger.warn('custom-minapps.json content is not an array, using empty array')
|
||||
customApps = []
|
||||
}
|
||||
} catch (parseError) {
|
||||
logger.error('Failed to parse custom-minapps.json content:', parseError as Error)
|
||||
customApps = []
|
||||
}
|
||||
|
||||
const customApps = JSON.parse(content)
|
||||
const now = new Date().toISOString()
|
||||
|
||||
return customApps.map((app: any) => ({
|
||||
|
||||
@@ -1003,6 +1003,18 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
||||
provider: 'minimax',
|
||||
name: 'minimax-01',
|
||||
group: 'minimax-01'
|
||||
},
|
||||
{
|
||||
id: 'MiniMax-M2',
|
||||
provider: 'minimax',
|
||||
name: 'MiniMax M2',
|
||||
group: 'minimax-m2'
|
||||
},
|
||||
{
|
||||
id: 'MiniMax-M2-Stable',
|
||||
provider: 'minimax',
|
||||
name: 'MiniMax M2 Stable',
|
||||
group: 'minimax-m2'
|
||||
}
|
||||
],
|
||||
hyperbolic: [
|
||||
@@ -1840,5 +1852,26 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
||||
group: 'LongCat'
|
||||
}
|
||||
],
|
||||
huggingface: []
|
||||
huggingface: [],
|
||||
'ai-gateway': [],
|
||||
cerebras: [
|
||||
{
|
||||
id: 'gpt-oss-120b',
|
||||
name: 'GPT oss 120B',
|
||||
provider: 'cerebras',
|
||||
group: 'openai'
|
||||
},
|
||||
{
|
||||
id: 'zai-glm-4.6',
|
||||
name: 'GLM 4.6',
|
||||
provider: 'cerebras',
|
||||
group: 'zai'
|
||||
},
|
||||
{
|
||||
id: 'qwen-3-235b-a22b-instruct-2507',
|
||||
name: 'Qwen 3 235B A22B Instruct',
|
||||
provider: 'cerebras',
|
||||
group: 'qwen'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import BaiduCloudProviderLogo from '@renderer/assets/images/providers/baidu-clou
|
||||
import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png'
|
||||
import BurnCloudProviderLogo from '@renderer/assets/images/providers/burncloud.png'
|
||||
import CephalonProviderLogo from '@renderer/assets/images/providers/cephalon.jpeg'
|
||||
import CerebrasProviderLogo from '@renderer/assets/images/providers/cerebras.webp'
|
||||
import CherryInProviderLogo from '@renderer/assets/images/providers/cherryin.png'
|
||||
import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png'
|
||||
import DmxapiProviderLogo from '@renderer/assets/images/providers/DMXAPI.png'
|
||||
@@ -51,6 +52,7 @@ import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
||||
import TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png'
|
||||
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
||||
import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png'
|
||||
import AIGatewayProviderLogo from '@renderer/assets/images/providers/vercel.svg'
|
||||
import VertexAIProviderLogo from '@renderer/assets/images/providers/vertexai.svg'
|
||||
import BytedanceProviderLogo from '@renderer/assets/images/providers/volcengine.png'
|
||||
import VoyageAIProviderLogo from '@renderer/assets/images/providers/voyageai.png'
|
||||
@@ -470,7 +472,8 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
||||
name: 'MiniMax',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.minimax.chat/v1/',
|
||||
apiHost: 'https://api.minimaxi.com/v1',
|
||||
anthropicApiHost: 'https://api.minimaxi.com/anthropic',
|
||||
models: SYSTEM_MODELS.minimax,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
@@ -675,6 +678,26 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
||||
models: [],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
'ai-gateway': {
|
||||
id: 'ai-gateway',
|
||||
name: 'AI Gateway',
|
||||
type: 'ai-gateway',
|
||||
apiKey: '',
|
||||
apiHost: 'https://ai-gateway.vercel.sh/v1',
|
||||
models: [],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
},
|
||||
cerebras: {
|
||||
id: 'cerebras',
|
||||
name: 'Cerebras AI',
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.cerebras.ai/v1',
|
||||
models: SYSTEM_MODELS.cerebras,
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
}
|
||||
} as const
|
||||
|
||||
@@ -741,7 +764,9 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
|
||||
aionly: AiOnlyProviderLogo,
|
||||
longcat: LongCatProviderLogo,
|
||||
huggingface: HuggingfaceProviderLogo,
|
||||
sophnet: SophnetProviderLogo
|
||||
sophnet: SophnetProviderLogo,
|
||||
'ai-gateway': AIGatewayProviderLogo,
|
||||
cerebras: CerebrasProviderLogo
|
||||
} as const
|
||||
|
||||
export function getProviderLogo(providerId: string) {
|
||||
@@ -1048,7 +1073,7 @@ export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
|
||||
},
|
||||
minimax: {
|
||||
api: {
|
||||
url: 'https://api.minimax.chat/v1/'
|
||||
url: 'https://api.minimaxi.com/v1/'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://platform.minimaxi.com/',
|
||||
@@ -1390,6 +1415,28 @@ export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
|
||||
docs: 'https://huggingface.co/docs',
|
||||
models: 'https://huggingface.co/models'
|
||||
}
|
||||
},
|
||||
'ai-gateway': {
|
||||
api: {
|
||||
url: 'https://ai-gateway.vercel.sh/v1/ai'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://vercel.com/ai-gateway',
|
||||
apiKey: 'https://vercel.com/',
|
||||
docs: 'https://vercel.com/docs/ai-gateway',
|
||||
models: 'https://vercel.com/ai-gateway/models'
|
||||
}
|
||||
},
|
||||
cerebras: {
|
||||
api: {
|
||||
url: 'https://api.cerebras.ai/v1'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://www.cerebras.ai',
|
||||
apiKey: 'https://cloud.cerebras.ai',
|
||||
docs: 'https://inference-docs.cerebras.ai/introduction',
|
||||
models: 'https://inference-docs.cerebras.ai/models/overview'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1452,7 +1499,7 @@ export const isSupportEnableThinkingProvider = (provider: Provider) => {
|
||||
)
|
||||
}
|
||||
|
||||
const NOT_SUPPORT_SERVICE_TIER_PROVIDERS = ['github', 'copilot'] as const satisfies SystemProviderId[]
|
||||
const NOT_SUPPORT_SERVICE_TIER_PROVIDERS = ['github', 'copilot', 'cerebras'] as const satisfies SystemProviderId[]
|
||||
|
||||
/**
|
||||
* 判断提供商是否支持 service_tier 设置。 Only for OpenAI API.
|
||||
@@ -1519,6 +1566,10 @@ export function isGeminiProvider(provider: Provider): boolean {
|
||||
return provider.type === 'gemini'
|
||||
}
|
||||
|
||||
export function isAIGatewayProvider(provider: Provider): boolean {
|
||||
return provider.type === 'ai-gateway'
|
||||
}
|
||||
|
||||
const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot', 'perplexity'] as const satisfies SystemProviderId[]
|
||||
|
||||
export const isSupportAPIVersionProvider = (provider: Provider) => {
|
||||
|
||||
@@ -86,7 +86,9 @@ const providerKeyMap = {
|
||||
aionly: 'provider.aionly',
|
||||
longcat: 'provider.longcat',
|
||||
huggingface: 'provider.huggingface',
|
||||
sophnet: 'provider.sophnet'
|
||||
sophnet: 'provider.sophnet',
|
||||
'ai-gateway': 'provider.ai-gateway',
|
||||
cerebras: 'provider.cerebras'
|
||||
} as const
|
||||
|
||||
/**
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "AI Gateway",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "AiOnly",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Baidu Cloud",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
|
||||
@@ -904,7 +904,7 @@
|
||||
"show_line_numbers": "代码显示行号",
|
||||
"temperature": {
|
||||
"label": "模型温度",
|
||||
"tip": "模型生成文本的随机程度。值越大,回复内容越赋有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7"
|
||||
"tip": "模型生成文本的随机程度。值越大,回复内容越富有多样性、创造性、随机性;设为 0 根据事实回答。日常聊天建议设置为 0.7"
|
||||
},
|
||||
"thought_auto_collapse": {
|
||||
"label": "思考内容自动折叠",
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "AI Gateway",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "唯一AI (AiOnly)",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "百度云千帆",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "阿里云百炼",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "AI 閘道器",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "唯一AI (AiOnly)",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "百度雲千帆",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "阿里雲百鍊",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "KI-Gateway",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "Einzige KI (AiOnly)",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Baidu Cloud Qianfan",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "Alibaba Cloud Bailian",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "Πύλη Τεχνητής Νοημοσύνης",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "AiOnly",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Baidu Cloud Qianfan",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "AliCloud Bailian",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "Puerta de enlace de IA",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "AiOnly",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Baidu Nube Qiánfān",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copiloto",
|
||||
"dashscope": "Álibaba Nube BaiLiàn",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "Passerelle IA",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "AiOnly",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Baidu Cloud Qianfan",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilote",
|
||||
"dashscope": "AliCloud BaiLian",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "AIゲートウェイ",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "AiOnly",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Baidu Cloud",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "Gateway de IA",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "AiOnly",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Nuvem Baidu",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copiloto",
|
||||
"dashscope": "Área de Atuação AliCloud",
|
||||
|
||||
@@ -2484,6 +2484,7 @@
|
||||
},
|
||||
"provider": {
|
||||
"302ai": "302.AI",
|
||||
"ai-gateway": "AI-шлюз",
|
||||
"aihubmix": "AiHubMix",
|
||||
"aionly": "AiOnly",
|
||||
"alayanew": "Alaya NeW",
|
||||
@@ -2494,6 +2495,7 @@
|
||||
"baidu-cloud": "Baidu Cloud",
|
||||
"burncloud": "BurnCloud",
|
||||
"cephalon": "Cephalon",
|
||||
"cerebras": "Cerebras AI",
|
||||
"cherryin": "CherryIN",
|
||||
"copilot": "GitHub Copilot",
|
||||
"dashscope": "Alibaba Cloud",
|
||||
|
||||
@@ -207,14 +207,15 @@ const ErrorDetailModal: React.FC<ErrorDetailModalProps> = ({ open, onClose, erro
|
||||
{t('common.close')}
|
||||
</Button>
|
||||
]}
|
||||
width={600}>
|
||||
width="80%"
|
||||
style={{ maxWidth: '1200px', minWidth: '600px' }}>
|
||||
<ErrorDetailContainer>{renderErrorDetails(error)}</ErrorDetailContainer>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const ErrorDetailContainer = styled.div`
|
||||
max-height: 400px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
`
|
||||
|
||||
@@ -347,16 +348,8 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
|
||||
return (
|
||||
<ErrorDetailList>
|
||||
<AiSdkErrorBase error={error} />
|
||||
|
||||
{(isSerializedAiSdkAPICallError(error) || isSerializedAiSdkDownloadError(error)) && (
|
||||
<>
|
||||
{error.statusCode && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.statusCode')}:</ErrorDetailLabel>
|
||||
<ErrorDetailValue>{error.statusCode}</ErrorDetailValue>
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
{error.url && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.requestUrl')}:</ErrorDetailLabel>
|
||||
@@ -374,12 +367,27 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
<CodeViewer value={error.responseBody} className="source-view" language="json" expanded />
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{error.requestBodyValues && (
|
||||
{(isSerializedAiSdkAPICallError(error) || isSerializedAiSdkDownloadError(error)) && (
|
||||
<>
|
||||
{error.statusCode && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.requestBodyValues')}:</ErrorDetailLabel>
|
||||
<ErrorDetailLabel>{t('error.statusCode')}:</ErrorDetailLabel>
|
||||
<ErrorDetailValue>{error.statusCode}</ErrorDetailValue>
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isSerializedAiSdkAPICallError(error) && (
|
||||
<>
|
||||
{error.responseHeaders && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.responseHeaders')}:</ErrorDetailLabel>
|
||||
<CodeViewer
|
||||
value={safeToString(error.requestBodyValues)}
|
||||
value={JSON.stringify(error.responseHeaders, null, 2)}
|
||||
className="source-view"
|
||||
language="json"
|
||||
expanded
|
||||
@@ -387,11 +395,11 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
{error.responseHeaders && (
|
||||
{error.requestBodyValues && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.responseHeaders')}:</ErrorDetailLabel>
|
||||
<ErrorDetailLabel>{t('error.requestBodyValues')}:</ErrorDetailLabel>
|
||||
<CodeViewer
|
||||
value={JSON.stringify(error.responseHeaders, null, 2)}
|
||||
value={safeToString(error.requestBodyValues)}
|
||||
className="source-view"
|
||||
language="json"
|
||||
expanded
|
||||
@@ -627,6 +635,8 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
<ErrorDetailValue>{error.functionality}</ErrorDetailValue>
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
<AiSdkErrorBase error={error} />
|
||||
</ErrorDetailList>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import type { Topic } from '@renderer/types'
|
||||
import type { Message } from '@renderer/types/newMessage'
|
||||
import { classNames } from '@renderer/utils'
|
||||
import { Popover } from 'antd'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { ComponentProps} from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { useChatMaxWidth } from '../Chat'
|
||||
@@ -43,9 +44,6 @@ const MessageGroup = ({ messages, topic, registerMessageElement }: Props) => {
|
||||
)
|
||||
const [selectedIndex, setSelectedIndex] = useState(messageLength - 1)
|
||||
|
||||
// Refs
|
||||
const prevMessageLengthRef = useRef(messageLength)
|
||||
|
||||
// 对于单模型消息,采用简单的样式,避免 overflow 影响内部的 sticky 效果
|
||||
const multiModelMessageStyle = useMemo(
|
||||
() => (messageLength < 2 ? 'fold' : _multiModelMessageStyle),
|
||||
@@ -83,24 +81,6 @@ const MessageGroup = ({ messages, topic, registerMessageElement }: Props) => {
|
||||
},
|
||||
[editMessage, selectedMessageId, setTimeoutTimer]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (messageLength > prevMessageLengthRef.current) {
|
||||
setSelectedIndex(messageLength - 1)
|
||||
const lastMessage = messages[messageLength - 1]
|
||||
if (lastMessage) {
|
||||
setSelectedMessage(lastMessage)
|
||||
}
|
||||
} else {
|
||||
const newIndex = messages.findIndex((msg) => msg.id === selectedMessageId)
|
||||
if (newIndex !== -1) {
|
||||
setSelectedIndex(newIndex)
|
||||
}
|
||||
}
|
||||
prevMessageLengthRef.current = messageLength
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [messageLength])
|
||||
|
||||
// 添加对流程图节点点击事件的监听
|
||||
useEffect(() => {
|
||||
// 只在组件挂载和消息数组变化时添加监听器
|
||||
@@ -223,7 +203,7 @@ const MessageGroup = ({ messages, topic, registerMessageElement }: Props) => {
|
||||
message,
|
||||
topic,
|
||||
index: message.index
|
||||
}
|
||||
} satisfies ComponentProps<typeof MessageItem>
|
||||
|
||||
const messageContent = (
|
||||
<MessageWrapper
|
||||
@@ -277,7 +257,7 @@ const MessageGroup = ({ messages, topic, registerMessageElement }: Props) => {
|
||||
isGrouped,
|
||||
topic,
|
||||
multiModelMessageStyle,
|
||||
messages.length,
|
||||
messages,
|
||||
selectedMessageId,
|
||||
onUpdateUseful,
|
||||
groupContextMessageId,
|
||||
|
||||
@@ -32,8 +32,26 @@ const NewAppButton: FC<Props> = ({ size = 60 }) => {
|
||||
|
||||
const handleAddCustomApp = async (values: any) => {
|
||||
try {
|
||||
const content = await window.api.file.read('custom-minapps.json')
|
||||
const customApps = JSON.parse(content)
|
||||
let content: string
|
||||
let customApps: MinAppType[]
|
||||
|
||||
try {
|
||||
content = await window.api.file.read('custom-minapps.json')
|
||||
customApps = JSON.parse(content)
|
||||
// 确保解析结果是数组
|
||||
if (!Array.isArray(customApps)) {
|
||||
customApps = []
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 如果文件不存在或解析失败,使用空数组
|
||||
if (error.message?.includes('ENOENT') || error.message?.includes('no such file')) {
|
||||
customApps = []
|
||||
} else {
|
||||
// 其他解析错误,使用空数组
|
||||
logger.warn('Failed to read custom-minapps.json, using empty array:', error as Error)
|
||||
customApps = []
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate ID
|
||||
if (customApps.some((app: MinAppType) => app.id === values.id)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup'
|
||||
import Selector from '@renderer/components/Selector'
|
||||
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
|
||||
import {
|
||||
isAIGatewayProvider,
|
||||
isAnthropicProvider,
|
||||
isAzureOpenAIProvider,
|
||||
isGeminiProvider,
|
||||
@@ -80,7 +81,8 @@ const ANTHROPIC_COMPATIBLE_PROVIDER_IDS = [
|
||||
SystemProviderIds.aihubmix,
|
||||
SystemProviderIds.grok,
|
||||
SystemProviderIds.cherryin,
|
||||
SystemProviderIds.longcat
|
||||
SystemProviderIds.longcat,
|
||||
SystemProviderIds.minimax
|
||||
] as const
|
||||
type AnthropicCompatibleProviderId = (typeof ANTHROPIC_COMPATIBLE_PROVIDER_IDS)[number]
|
||||
|
||||
@@ -305,6 +307,9 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
if (isVertexProvider(provider)) {
|
||||
return formatVertexApiHost(provider) + '/publishers/google'
|
||||
}
|
||||
if (isAIGatewayProvider(provider)) {
|
||||
return formatApiHost(apiHost) + '/language-model'
|
||||
}
|
||||
return formatApiHost(apiHost)
|
||||
}
|
||||
|
||||
@@ -520,24 +525,17 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
<SettingHelpText>{t('settings.provider.vertex_ai.api_host_help')}</SettingHelpText>
|
||||
</SettingHelpTextRow>
|
||||
)}
|
||||
{(isOpenAICompatibleProvider(provider) ||
|
||||
isAzureOpenAIProvider(provider) ||
|
||||
isAnthropicProvider(provider) ||
|
||||
isGeminiProvider(provider) ||
|
||||
isVertexProvider(provider) ||
|
||||
isOpenAIProvider(provider)) && (
|
||||
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
||||
<SettingHelpText
|
||||
style={{
|
||||
marginLeft: 6,
|
||||
marginRight: '1em',
|
||||
whiteSpace: 'break-spaces',
|
||||
wordBreak: 'break-all'
|
||||
}}>
|
||||
{t('settings.provider.api_host_preview', { url: hostPreview() })}
|
||||
</SettingHelpText>
|
||||
</SettingHelpTextRow>
|
||||
)}
|
||||
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
||||
<SettingHelpText
|
||||
style={{
|
||||
marginLeft: 6,
|
||||
marginRight: '1em',
|
||||
whiteSpace: 'break-spaces',
|
||||
wordBreak: 'break-all'
|
||||
}}>
|
||||
{t('settings.provider.api_host_preview', { url: hostPreview() })}
|
||||
</SettingHelpText>
|
||||
</SettingHelpTextRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -550,6 +548,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
onChange={(e) => setAnthropicHost(e.target.value)}
|
||||
onBlur={onUpdateAnthropicHost}
|
||||
/>
|
||||
{/* TODO: Add a reset button here. */}
|
||||
</Space.Compact>
|
||||
<SettingHelpTextRow style={{ flexDirection: 'column', alignItems: 'flex-start', gap: '4px' }}>
|
||||
<SettingHelpText style={{ marginLeft: 6, whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>
|
||||
|
||||
@@ -128,9 +128,9 @@ export async function uploadNotes(files: File[], targetPath: string): Promise<Up
|
||||
function getSorter(sortType: NotesSortType): (a: NotesTreeNode, b: NotesTreeNode) => number {
|
||||
switch (sortType) {
|
||||
case 'sort_a2z':
|
||||
return (a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'accent' })
|
||||
return (a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'accent' })
|
||||
case 'sort_z2a':
|
||||
return (a, b) => b.name.localeCompare(a.name, undefined, { sensitivity: 'accent' })
|
||||
return (a, b) => b.name.localeCompare(a.name, undefined, { numeric: true, sensitivity: 'accent' })
|
||||
case 'sort_updated_desc':
|
||||
return (a, b) => getTime(b.updatedAt) - getTime(a.updatedAt)
|
||||
case 'sort_updated_asc':
|
||||
@@ -140,7 +140,7 @@ function getSorter(sortType: NotesSortType): (a: NotesTreeNode, b: NotesTreeNode
|
||||
case 'sort_created_asc':
|
||||
return (a, b) => getTime(a.createdAt) - getTime(b.createdAt)
|
||||
default:
|
||||
return (a, b) => a.name.localeCompare(b.name)
|
||||
return (a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'accent' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 173,
|
||||
version: 174,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@@ -2802,6 +2802,23 @@ const migrateConfig = {
|
||||
logger.error('migrate 173 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'174': (state: RootState) => {
|
||||
try {
|
||||
addProvider(state, SystemProviderIds.longcat)
|
||||
|
||||
addProvider(state, SystemProviderIds['ai-gateway'])
|
||||
addProvider(state, 'cerebras')
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === SystemProviderIds.minimax) {
|
||||
provider.anthropicApiHost = 'https://api.minimaxi.com/anthropic'
|
||||
}
|
||||
})
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 174 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1446,7 +1446,7 @@ export const appendAssistantResponseThunk =
|
||||
}
|
||||
|
||||
// 2. Create the new assistant message stub
|
||||
const newAssistantStub = createAssistantMessage(assistant.id, topicId, {
|
||||
const newAssistantMessageStub = createAssistantMessage(assistant.id, topicId, {
|
||||
askId: askId, // Crucial: Use the original askId
|
||||
model: newModel,
|
||||
modelId: newModel.id,
|
||||
@@ -1459,9 +1459,14 @@ export const appendAssistantResponseThunk =
|
||||
const insertAtIndex = existingMessageIndex !== -1 ? existingMessageIndex + 1 : currentTopicMessageIds.length
|
||||
|
||||
// 4. Update Database (Save the stub to the topic's message list)
|
||||
await saveMessageAndBlocksToDB(newAssistantStub, [], insertAtIndex)
|
||||
await saveMessageAndBlocksToDB(newAssistantMessageStub, [], insertAtIndex)
|
||||
|
||||
dispatch(newMessagesActions.insertMessageAtIndex({ topicId, message: newAssistantStub, index: insertAtIndex }))
|
||||
dispatch(
|
||||
newMessagesActions.insertMessageAtIndex({ topicId, message: newAssistantMessageStub, index: insertAtIndex })
|
||||
)
|
||||
|
||||
dispatch(updateMessageAndBlocksThunk(topicId, { id: existingAssistantMessageId, foldSelected: false }, []))
|
||||
dispatch(updateMessageAndBlocksThunk(topicId, { id: newAssistantMessageStub.id, foldSelected: true }, []))
|
||||
|
||||
// 5. Prepare and queue the processing task
|
||||
const assistantConfigForThisCall = {
|
||||
@@ -1475,7 +1480,7 @@ export const appendAssistantResponseThunk =
|
||||
getState,
|
||||
topicId,
|
||||
assistantConfigForThisCall,
|
||||
newAssistantStub // Pass the newly created stub
|
||||
newAssistantMessageStub // Pass the newly created stub
|
||||
)
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
@@ -11,7 +11,8 @@ export const ProviderTypeSchema = z.enum([
|
||||
'mistral',
|
||||
'aws-bedrock',
|
||||
'vertex-anthropic',
|
||||
'new-api'
|
||||
'new-api',
|
||||
'ai-gateway'
|
||||
])
|
||||
|
||||
export type ProviderType = z.infer<typeof ProviderTypeSchema>
|
||||
@@ -176,7 +177,9 @@ export const SystemProviderIds = {
|
||||
poe: 'poe',
|
||||
aionly: 'aionly',
|
||||
longcat: 'longcat',
|
||||
huggingface: 'huggingface'
|
||||
huggingface: 'huggingface',
|
||||
'ai-gateway': 'ai-gateway',
|
||||
cerebras: 'cerebras'
|
||||
} as const
|
||||
|
||||
export type SystemProviderId = keyof typeof SystemProviderIds
|
||||
|
||||
@@ -97,6 +97,7 @@ export type ReasoningEffortOptionalParams = {
|
||||
}
|
||||
}
|
||||
}
|
||||
disable_reasoning?: boolean
|
||||
// Add any other potential reasoning-related keys here if they exist
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Simple wrapper for scrollIntoView with common default options.
|
||||
* Provides a unified interface with sensible defaults.
|
||||
*
|
||||
* @param element - The target element to scroll into view
|
||||
* @param options - Scroll options. If not provided, uses { behavior: 'smooth', block: 'center', inline: 'nearest' }
|
||||
*/
|
||||
export function scrollIntoView(element: HTMLElement, options?: ScrollIntoViewOptions): void {
|
||||
const defaultOptions: ScrollIntoViewOptions = {
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
}
|
||||
element.scrollIntoView(options ?? defaultOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Intelligently scrolls an element into view at the center position.
|
||||
* Prioritizes scrolling within the specified container to avoid scrolling the entire page.
|
||||
*
|
||||
* @param element - The target element to scroll into view
|
||||
* @param scrollContainer - Optional scroll container. If provided and scrollable, scrolling happens within it; otherwise uses browser default scrolling
|
||||
* @param behavior - Scroll behavior, defaults to 'smooth'
|
||||
*/
|
||||
export function scrollElementIntoView(
|
||||
element: HTMLElement,
|
||||
scrollContainer?: HTMLElement | null,
|
||||
behavior: ScrollBehavior = 'smooth'
|
||||
): void {
|
||||
if (!scrollContainer) {
|
||||
// No container specified, use browser default scrolling
|
||||
scrollIntoView(element, { behavior, block: 'center', inline: 'nearest' })
|
||||
return
|
||||
}
|
||||
|
||||
// Check if container is scrollable
|
||||
const canScroll =
|
||||
scrollContainer.scrollHeight > scrollContainer.clientHeight ||
|
||||
scrollContainer.scrollWidth > scrollContainer.clientWidth
|
||||
|
||||
if (canScroll) {
|
||||
// Container is scrollable, scroll within the container
|
||||
const containerRect = scrollContainer.getBoundingClientRect()
|
||||
const elRect = element.getBoundingClientRect()
|
||||
|
||||
// Calculate element's scrollable offset position relative to the container
|
||||
const elementTopWithinContainer = elRect.top - containerRect.top + scrollContainer.scrollTop
|
||||
const desiredTop = elementTopWithinContainer - Math.max(0, scrollContainer.clientHeight - elRect.height) / 2
|
||||
|
||||
scrollContainer.scrollTo({ top: Math.max(0, desiredTop), behavior })
|
||||
} else {
|
||||
// Container is not scrollable, fallback to browser default scrolling
|
||||
scrollIntoView(element, { behavior, block: 'center', inline: 'nearest' })
|
||||
}
|
||||
}
|
||||
@@ -214,6 +214,7 @@ export function uniqueObjectArray<T>(array: T[]): T[] {
|
||||
export * from './api'
|
||||
export * from './collection'
|
||||
export * from './dataLimit'
|
||||
export * from './dom'
|
||||
export * from './file'
|
||||
export * from './image'
|
||||
export * from './json'
|
||||
|
||||
@@ -127,6 +127,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/cerebras@npm:^1.0.31":
|
||||
version: 1.0.31
|
||||
resolution: "@ai-sdk/cerebras@npm:1.0.31"
|
||||
dependencies:
|
||||
"@ai-sdk/openai-compatible": "npm:1.0.27"
|
||||
"@ai-sdk/provider": "npm:2.0.0"
|
||||
"@ai-sdk/provider-utils": "npm:3.0.17"
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
checksum: 10c0/0723f0041b767acfb7a9d903d51d5c95af83c31c89b83f242cb5c02a076d8b98f6567334eb32dcdbc8565b55ded2aa5195ca68612bbe7b13e68253cf4ef412d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/deepseek@npm:^1.0.27":
|
||||
version: 1.0.27
|
||||
resolution: "@ai-sdk/deepseek@npm:1.0.27"
|
||||
@@ -153,6 +166,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/gateway@npm:^2.0.9":
|
||||
version: 2.0.9
|
||||
resolution: "@ai-sdk/gateway@npm:2.0.9"
|
||||
dependencies:
|
||||
"@ai-sdk/provider": "npm:2.0.0"
|
||||
"@ai-sdk/provider-utils": "npm:3.0.17"
|
||||
"@vercel/oidc": "npm:3.0.3"
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
checksum: 10c0/840f94795b96c0fa6e73897ea8dba95fc78af1f8482f3b7d8439b6233b4f4de6979a8b67206f4bbf32649baf2acfb1153a46792dfa20259ca9f5fd214fb25fa5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/google-vertex@npm:^3.0.62":
|
||||
version: 3.0.62
|
||||
resolution: "@ai-sdk/google-vertex@npm:3.0.62"
|
||||
@@ -242,6 +268,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/openai-compatible@npm:1.0.27":
|
||||
version: 1.0.27
|
||||
resolution: "@ai-sdk/openai-compatible@npm:1.0.27"
|
||||
dependencies:
|
||||
"@ai-sdk/provider": "npm:2.0.0"
|
||||
"@ai-sdk/provider-utils": "npm:3.0.17"
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
checksum: 10c0/9f656e4f2ea4d714dc05be588baafd962b2e0360e9195fef373e745efeb20172698ea87e1033c0c5e1f1aa6e0db76a32629427bc8433eb42bd1a0ee00e04af0c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/openai-compatible@npm:^1.0.19":
|
||||
version: 1.0.19
|
||||
resolution: "@ai-sdk/openai-compatible@npm:1.0.19"
|
||||
@@ -316,7 +354,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/provider-utils@npm:3.0.17, @ai-sdk/provider-utils@npm:^3.0.10, @ai-sdk/provider-utils@npm:^3.0.12":
|
||||
"@ai-sdk/provider-utils@npm:3.0.17, @ai-sdk/provider-utils@npm:^3.0.10":
|
||||
version: 3.0.17
|
||||
resolution: "@ai-sdk/provider-utils@npm:3.0.17"
|
||||
dependencies:
|
||||
@@ -329,6 +367,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/provider-utils@npm:^3.0.12":
|
||||
version: 3.0.12
|
||||
resolution: "@ai-sdk/provider-utils@npm:3.0.12"
|
||||
dependencies:
|
||||
"@ai-sdk/provider": "npm:2.0.0"
|
||||
"@standard-schema/spec": "npm:^1.0.0"
|
||||
eventsource-parser: "npm:^3.0.5"
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
checksum: 10c0/83886bf188cad0cc655b680b710a10413989eaba9ec59dd24a58b985c02a8a1d50ad0f96dd5259385c07592ec3c37a7769fdf4a1ef569a73c9edbdb2cd585915
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ai-sdk/provider@npm:2.0.0, @ai-sdk/provider@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "@ai-sdk/provider@npm:2.0.0"
|
||||
@@ -9879,6 +9930,8 @@ __metadata:
|
||||
"@agentic/searxng": "npm:^7.3.3"
|
||||
"@agentic/tavily": "npm:^7.3.3"
|
||||
"@ai-sdk/amazon-bedrock": "npm:^3.0.53"
|
||||
"@ai-sdk/cerebras": "npm:^1.0.31"
|
||||
"@ai-sdk/gateway": "npm:^2.0.9"
|
||||
"@ai-sdk/google-vertex": "npm:^3.0.62"
|
||||
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch"
|
||||
"@ai-sdk/mistral": "npm:^2.0.23"
|
||||
|
||||
Reference in New Issue
Block a user