Merge branch 'main' of github.com:CherryHQ/cherry-studio into feat/sora2

This commit is contained in:
icarus 2025-10-12 01:51:51 +08:00
commit c7c6561b77
43 changed files with 1259 additions and 449 deletions

View File

@ -1,8 +1,8 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index 110f37ec18c98b1d55ae2b73cc716194e6f9094d..17e109b7778cbebb904f1919e768d21a2833d965 100644
index b957cb824faa79cf01ba3a504f221870bd8e306a..4d71d30f655775d61537d9d8b73f6e17d41fa67e 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -448,7 +448,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
@@ -452,7 +452,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
// src/get-model-path.ts
function getModelPath(modelId) {

View File

@ -99,10 +99,10 @@
"@agentic/exa": "^7.3.3",
"@agentic/searxng": "^7.3.3",
"@agentic/tavily": "^7.3.3",
"@ai-sdk/amazon-bedrock": "^3.0.29",
"@ai-sdk/google-vertex": "^3.0.33",
"@ai-sdk/mistral": "^2.0.17",
"@ai-sdk/perplexity": "^2.0.11",
"@ai-sdk/amazon-bedrock": "^3.0.35",
"@ai-sdk/google-vertex": "^3.0.40",
"@ai-sdk/mistral": "^2.0.19",
"@ai-sdk/perplexity": "^2.0.13",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@anthropic-ai/sdk": "^0.41.0",
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
@ -221,7 +221,7 @@
"@viz-js/lang-dot": "^1.0.5",
"@viz-js/viz": "^3.14.0",
"@xyflow/react": "^12.4.4",
"ai": "^5.0.59",
"ai": "^5.0.68",
"antd": "patch:antd@npm%3A5.27.0#~/.yarn/patches/antd-npm-5.27.0-aa91c36546.patch",
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
@ -381,7 +381,7 @@
"undici": "6.21.2",
"vite": "npm:rolldown-vite@latest",
"tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
"@ai-sdk/google@npm:2.0.14": "patch:@ai-sdk/google@npm%3A2.0.14#~/.yarn/patches/@ai-sdk-google-npm-2.0.14-376d8b03cc.patch"
"@ai-sdk/google@npm:2.0.17": "patch:@ai-sdk/google@npm%3A2.0.17#~/.yarn/patches/@ai-sdk-google-npm-2.0.17-fd88491de4.patch"
},
"packageManager": "yarn@4.9.1",
"lint-staged": {

View File

@ -36,14 +36,14 @@
"ai": "^5.0.26"
},
"dependencies": {
"@ai-sdk/anthropic": "^2.0.22",
"@ai-sdk/azure": "^2.0.42",
"@ai-sdk/deepseek": "^1.0.20",
"@ai-sdk/openai": "^2.0.42",
"@ai-sdk/openai-compatible": "^1.0.19",
"@ai-sdk/anthropic": "^2.0.27",
"@ai-sdk/azure": "^2.0.49",
"@ai-sdk/deepseek": "^1.0.23",
"@ai-sdk/openai": "^2.0.48",
"@ai-sdk/openai-compatible": "^1.0.22",
"@ai-sdk/provider": "^2.0.0",
"@ai-sdk/provider-utils": "^3.0.10",
"@ai-sdk/xai": "^2.0.23",
"@ai-sdk/provider-utils": "^3.0.12",
"@ai-sdk/xai": "^2.0.26",
"zod": "^4.1.5"
},
"devDependencies": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -549,7 +549,7 @@ const MinappPopupContainer: React.FC = () => {
{/* 在所有小程序中显示GoogleLoginTip */}
<GoogleLoginTip isReady={isReady} currentUrl={currentUrl} currentAppId={currentMinappId} />
{!isReady && (
<EmptyView>
<EmptyView style={{ backgroundColor: 'var(--color-background-soft)' }}>
<Avatar
src={currentAppInfo?.logo}
size={80}

View File

@ -22,8 +22,6 @@ import GithubCopilotLogo from '@renderer/assets/images/apps/github-copilot.webp?
import GoogleAppLogo from '@renderer/assets/images/apps/google.svg?url'
import GrokAppLogo from '@renderer/assets/images/apps/grok.png?url'
import GrokXAppLogo from '@renderer/assets/images/apps/grok-x.png?url'
import HikaLogo from '@renderer/assets/images/apps/hika.webp?url'
import HuggingChatLogo from '@renderer/assets/images/apps/huggingchat.svg?url'
import KimiAppLogo from '@renderer/assets/images/apps/kimi.webp?url'
import LambdaChatLogo from '@renderer/assets/images/apps/lambdachat.webp?url'
import LeChatLogo from '@renderer/assets/images/apps/lechat.png?url'
@ -32,13 +30,13 @@ import MetasoAppLogo from '@renderer/assets/images/apps/metaso.webp?url'
import MonicaLogo from '@renderer/assets/images/apps/monica.webp?url'
import n8nLogo from '@renderer/assets/images/apps/n8n.svg?url'
import NamiAiLogo from '@renderer/assets/images/apps/nm.png?url'
import NamiAiSearchLogo from '@renderer/assets/images/apps/nm-search.webp?url'
import NotebookLMAppLogo from '@renderer/assets/images/apps/notebooklm.svg?url'
import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp?url'
import PoeAppLogo from '@renderer/assets/images/apps/poe.webp?url'
import QwenlmAppLogo from '@renderer/assets/images/apps/qwenlm.webp?url'
import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.png?url'
import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.webp?url'
import StepfunAppLogo from '@renderer/assets/images/apps/stepfun.png?url'
import ThinkAnyLogo from '@renderer/assets/images/apps/thinkany.webp?url'
import TiangongAiLogo from '@renderer/assets/images/apps/tiangong.png?url'
import WanZhiAppLogo from '@renderer/assets/images/apps/wanzhi.jpg?url'
@ -46,7 +44,6 @@ import WPSLingXiLogo from '@renderer/assets/images/apps/wpslingxi.webp?url'
import XiaoYiAppLogo from '@renderer/assets/images/apps/xiaoyi.webp?url'
import YouLogo from '@renderer/assets/images/apps/you.jpg?url'
import TencentYuanbaoAppLogo from '@renderer/assets/images/apps/yuanbao.webp?url'
import YuewenAppLogo from '@renderer/assets/images/apps/yuewen.png?url'
import ZaiAppLogo from '@renderer/assets/images/apps/zai.png?url'
import ZhihuAppLogo from '@renderer/assets/images/apps/zhihu.png?url'
import ClaudeAppLogo from '@renderer/assets/images/models/claude.png?url'
@ -150,9 +147,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'stepfun',
name: i18n.t('minapps.yuewen'),
url: 'https://yuewen.cn/chats/new',
logo: YuewenAppLogo,
name: i18n.t('minapps.stepfun'),
url: 'https://stepfun.com',
logo: StepfunAppLogo,
bodered: true
},
{
@ -263,13 +260,6 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
url: 'https://www.tiangong.cn/',
bodered: true
},
{
id: 'hugging-chat',
name: 'HuggingChat',
logo: HuggingChatLogo,
url: 'https://huggingface.co/chat/',
bodered: true
},
{
id: 'Felo',
name: 'Felo',
@ -297,13 +287,6 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
url: 'https://bot.n.cn/',
bodered: true
},
{
id: 'nm-search',
name: i18n.t('minapps.nami-ai-search'),
logo: NamiAiSearchLogo,
url: 'https://www.n.cn/',
bodered: true
},
{
id: 'thinkany',
name: 'ThinkAny',
@ -314,13 +297,6 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
padding: 5
}
},
{
id: 'hika',
name: 'Hika',
logo: HikaLogo,
url: 'https://hika.fyi/',
bodered: true
},
{
id: 'github-copilot',
name: 'GitHub Copilot',

View File

@ -25,7 +25,7 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
// Default quick assistant model
glm45FlashModel
],
// cherryin: [],
cherryin: [],
vertexai: [],
'302ai': [
{

View File

@ -82,16 +82,16 @@ export const CHERRYAI_PROVIDER: SystemProvider = {
}
export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> = {
// cherryin: {
// id: 'cherryin',
// name: 'CherryIN',
// type: 'openai',
// apiKey: '',
// apiHost: 'https://open.cherryin.ai',
// models: [],
// isSystem: true,
// enabled: true
// },
cherryin: {
id: 'cherryin',
name: 'CherryIN',
type: 'openai',
apiKey: '',
apiHost: 'https://open.cherryin.net',
models: [],
isSystem: true,
enabled: true
},
silicon: {
id: 'silicon',
name: 'Silicon',
@ -742,17 +742,17 @@ type ProviderUrls = {
}
export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
// cherryin: {
// api: {
// url: 'https://open.cherryin.ai'
// },
// websites: {
// official: 'https://open.cherryin.ai',
// apiKey: 'https://open.cherryin.ai/console/token',
// docs: 'https://open.cherryin.ai',
// models: 'https://open.cherryin.ai/pricing'
// }
// },
cherryin: {
api: {
url: 'https://open.cherryin.net'
},
websites: {
official: 'https://open.cherryin.ai',
apiKey: 'https://open.cherryin.ai/console/token',
docs: 'https://open.cherryin.ai',
models: 'https://open.cherryin.ai/pricing'
}
},
ph8: {
api: {
url: 'https://ph8.co'

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "Nami AI Search",
"qwen": "Qwen",
"sensechat": "SenseChat",
"stepfun": "Stepfun",
"tencent-yuanbao": "Yuanbao",
"tiangong-ai": "Skywork",
"wanzhi": "Wanzhi",
"wenxin": "ERNIE",
"wps-copilot": "WPS Copilot",
"xiaoyi": "Xiaoyi",
"yuewen": "Yuewen",
"zhihu": "Zhihu"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "纳米AI搜索",
"qwen": "通义千问",
"sensechat": "商量",
"stepfun": "阶跃AI",
"tencent-yuanbao": "腾讯元宝",
"tiangong-ai": "天工AI",
"wanzhi": "万知",
"wenxin": "文心一言",
"wps-copilot": "WPS灵犀",
"xiaoyi": "小艺",
"yuewen": "跃问",
"zhihu": "知乎直答"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "納米AI搜索",
"qwen": "通義千問",
"sensechat": "商量",
"stepfun": "階躍AI",
"tencent-yuanbao": "騰訊元寶",
"tiangong-ai": "天工AI",
"wanzhi": "萬知",
"wenxin": "文心一言",
"wps-copilot": "WPS靈犀",
"xiaoyi": "小藝",
"yuewen": "躍問",
"zhihu": "知乎直答"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "Nami AI Search",
"qwen": "Qwen",
"sensechat": "SenseChat",
"stepfun": "Stepfun",
"tencent-yuanbao": "Yuanbao",
"tiangong-ai": "Skywork",
"wanzhi": "Wanzhi",
"wenxin": "ERNIE",
"wps-copilot": "WPS Copilot",
"xiaoyi": "Xiaoyi",
"yuewen": "Yuewen",
"zhihu": "Zhihu"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "Nami AI Search",
"qwen": "Qwen",
"sensechat": "SenseChat",
"stepfun": "Stepfun",
"tencent-yuanbao": "Yuanbao",
"tiangong-ai": "Skywork",
"wanzhi": "Wanzhi",
"wenxin": "ERNIE",
"wps-copilot": "WPS Copilot",
"xiaoyi": "Xiaoyi",
"yuewen": "Yuewen",
"zhihu": "Zhihu"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "Nami AI Search",
"qwen": "Qwen",
"sensechat": "SenseChat",
"stepfun": "Stepfun",
"tencent-yuanbao": "Yuanbao",
"tiangong-ai": "Skywork",
"wanzhi": "Wanzhi",
"wenxin": "ERNIE",
"wps-copilot": "WPS Copilot",
"xiaoyi": "Xiaoyi",
"yuewen": "Yuewen",
"zhihu": "Zhihu"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "Nami AI Search",
"qwen": "通義千問",
"sensechat": "SenseChat",
"stepfun": "Stepfun",
"tencent-yuanbao": "騰訊元宝",
"tiangong-ai": "Skywork",
"wanzhi": "万知",
"wenxin": "ERNIE",
"wps-copilot": "WPS Copilot",
"xiaoyi": "小藝",
"yuewen": "躍問",
"zhihu": "知乎直答"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "Nami AI Search",
"qwen": "Qwen",
"sensechat": "SenseChat",
"stepfun": "Stepfun",
"tencent-yuanbao": "Yuanbao",
"tiangong-ai": "Skywork",
"wanzhi": "Wanzhi",
"wenxin": "ERNIE",
"wps-copilot": "WPS Copilot",
"xiaoyi": "Xiaoyi",
"yuewen": "Yuewen",
"zhihu": "Zhihu"
},
"miniwindow": {

View File

@ -1806,13 +1806,13 @@
"nami-ai-search": "Nami AI Search",
"qwen": "Qwen",
"sensechat": "SenseChat",
"stepfun": "Stepfun",
"tencent-yuanbao": "Tencent Yuanbao",
"tiangong-ai": "Skywork",
"wanzhi": "Wanzhi",
"wenxin": "ERNIE",
"wps-copilot": "WPS Copilot",
"xiaoyi": "Xiaoyi",
"yuewen": "Yuewen",
"zhihu": "Zhihu"
},
"miniwindow": {

View File

@ -359,8 +359,7 @@ const GridContainer = styled(Scrollbar)<{ $count: number; $gridColumns: number }
&.vertical {
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 8px;
overflow-y: auto;
overflow-x: hidden;
overflow: hidden;
}
&.grid {
grid-template-columns: repeat(

View File

@ -1,10 +1,26 @@
import { Alert, Spinner } from '@heroui/react'
import Scrollbar from '@renderer/components/Scrollbar'
import { Assistant } from '@renderer/types'
import { FC, useRef } from 'react'
import { useAgents } from '@renderer/hooks/agents/useAgents'
import { useAssistants } from '@renderer/hooks/useAssistant'
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
import { useTags } from '@renderer/hooks/useTags'
import { useAppDispatch } from '@renderer/store'
import { addIknowAction } from '@renderer/store/runtime'
import { Assistant, AssistantsSortType } from '@renderer/types'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { AgentSection } from './components/AgentSection'
import Assistants from './components/Assistants'
import UnifiedAddButton from './components/UnifiedAddButton'
import { UnifiedList } from './components/UnifiedList'
import { UnifiedTagGroups } from './components/UnifiedTagGroups'
import { useActiveAgent } from './hooks/useActiveAgent'
import { useUnifiedGrouping } from './hooks/useUnifiedGrouping'
import { useUnifiedItems } from './hooks/useUnifiedItems'
import { useUnifiedSorting } from './hooks/useUnifiedSorting'
interface AssistantsTabProps {
activeAssistant: Assistant
@ -13,12 +29,143 @@ interface AssistantsTabProps {
onCreateDefaultAssistant: () => void
}
const ALERT_KEY = 'enable_api_server_to_use_agent'
const AssistantsTab: FC<AssistantsTabProps> = (props) => {
const { activeAssistant, setActiveAssistant, onCreateAssistant, onCreateDefaultAssistant } = props
const containerRef = useRef<HTMLDivElement>(null)
const { t } = useTranslation()
const { apiServer } = useSettings()
const { iknow, chat } = useRuntime()
const dispatch = useAppDispatch()
// Agent related hooks
const { agents, deleteAgent, isLoading: agentsLoading, error: agentsError } = useAgents()
const { activeAgentId } = chat
const { setActiveAgentId } = useActiveAgent()
// Assistant related hooks
const { assistants, removeAssistant, copyAssistant, updateAssistants } = useAssistants()
const { addAssistantPreset } = useAssistantPresets()
const { collapsedTags, toggleTagCollapse } = useTags()
const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType()
const [dragging, setDragging] = useState(false)
// Unified items management
const { unifiedItems, handleUnifiedListReorder } = useUnifiedItems({
agents,
assistants,
apiServerEnabled: apiServer.enabled,
agentsLoading,
agentsError,
updateAssistants
})
// Sorting
const { sortByPinyinAsc, sortByPinyinDesc } = useUnifiedSorting({
unifiedItems,
updateAssistants
})
// Grouping
const { groupedUnifiedItems, handleUnifiedGroupReorder } = useUnifiedGrouping({
unifiedItems,
assistants,
agents,
apiServerEnabled: apiServer.enabled,
agentsLoading,
agentsError,
updateAssistants
})
useEffect(() => {
if (!agentsLoading && agents.length > 0 && !activeAgentId && apiServer.enabled) {
setActiveAgentId(agents[0].id)
}
}, [agentsLoading, agents, activeAgentId, setActiveAgentId, apiServer.enabled])
const onDeleteAssistant = useCallback(
(assistant: Assistant) => {
const remaining = assistants.filter((a) => a.id !== assistant.id)
if (assistant.id === activeAssistant?.id) {
const newActive = remaining[remaining.length - 1]
newActive ? setActiveAssistant(newActive) : onCreateDefaultAssistant()
}
removeAssistant(assistant.id)
},
[activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
)
const handleSortByChange = useCallback(
(sortType: AssistantsSortType) => {
setAssistantsTabSortType(sortType)
},
[setAssistantsTabSortType]
)
return (
<Container className="assistants-tab" ref={containerRef}>
<AgentSection />
<Assistants {...props} />
{!apiServer.enabled && !iknow[ALERT_KEY] && (
<Alert
color="warning"
title={t('agent.warning.enable_server')}
isClosable
onClose={() => {
dispatch(addIknowAction(ALERT_KEY))
}}
/>
)}
<UnifiedAddButton onCreateAssistant={onCreateAssistant} />
{agentsLoading && <Spinner />}
{apiServer.enabled && agentsError && <Alert color="danger" title={t('agent.list.error.failed')} />}
{assistantsTabSortType === 'tags' ? (
<UnifiedTagGroups
groupedItems={groupedUnifiedItems}
activeAssistantId={activeAssistant.id}
activeAgentId={activeAgentId}
sortBy={assistantsTabSortType}
collapsedTags={collapsedTags}
onGroupReorder={handleUnifiedGroupReorder}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}
onToggleTagCollapse={toggleTagCollapse}
onAssistantSwitch={setActiveAssistant}
onAssistantDelete={onDeleteAssistant}
onAgentDelete={deleteAgent}
onAgentPress={setActiveAgentId}
addPreset={addAssistantPreset}
copyAssistant={copyAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant}
handleSortByChange={handleSortByChange}
sortByPinyinAsc={sortByPinyinAsc}
sortByPinyinDesc={sortByPinyinDesc}
/>
) : (
<UnifiedList
items={unifiedItems}
activeAssistantId={activeAssistant.id}
activeAgentId={activeAgentId}
sortBy={assistantsTabSortType}
onReorder={handleUnifiedListReorder}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}
onAssistantSwitch={setActiveAssistant}
onAssistantDelete={onDeleteAssistant}
onAgentDelete={deleteAgent}
onAgentPress={setActiveAgentId}
addPreset={addAssistantPreset}
copyAssistant={copyAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant}
handleSortByChange={handleSortByChange}
sortByPinyinAsc={sortByPinyinAsc}
sortByPinyinDesc={sortByPinyinDesc}
/>
)}
{!dragging && <div style={{ minHeight: 10 }}></div>}
</Container>
)
}
@ -27,7 +174,6 @@ const Container = styled(Scrollbar)`
display: flex;
flex-direction: column;
padding: 10px;
margin-top: 3px;
`
export default AssistantsTab

View File

@ -0,0 +1,24 @@
import { Button, ButtonProps, cn } from '@heroui/react'
import { PlusIcon } from 'lucide-react'
import { FC } from 'react'
interface Props extends ButtonProps {
children: React.ReactNode
}
const AddButton: FC<Props> = ({ children, className, ...props }) => {
return (
<Button
{...props}
onPress={props.onPress}
className={cn(
'h-9 w-[calc(var(--assistants-width)-20px)] justify-start rounded-full bg-transparent px-3 text-[13px] text-[var(--color-text-2)] hover:bg-[var(--color-list-item)]',
className
)}
startContent={<PlusIcon size={16} className="shrink-0" />}>
{children}
</Button>
)
}
export default AddButton

View File

@ -1,11 +1,14 @@
import { Button, Chip, cn } from '@heroui/react'
import { cn } from '@heroui/react'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { useSessions } from '@renderer/hooks/agents/useSessions'
import { useSettings } from '@renderer/hooks/useSettings'
import AgentSettingsPopup from '@renderer/pages/settings/AgentSettings/AgentSettingsPopup'
import { AgentLabel } from '@renderer/pages/settings/AgentSettings/shared'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { AgentEntity } from '@renderer/types'
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
import { FC, memo } from 'react'
import { Bot } from 'lucide-react'
import { FC, memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
// const logger = loggerService.withContext('AgentItem')
@ -20,81 +23,107 @@ interface AgentItemProps {
const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) => {
const { t } = useTranslation()
const { sessions } = useSessions(agent.id)
const { clickAssistantToShowTopic, topicPosition } = useSettings()
const handlePress = useCallback(() => {
// Show session sidebar if setting is enabled (reusing the assistant setting for consistency)
if (clickAssistantToShowTopic) {
if (topicPosition === 'left') {
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
}
}
onPress()
}, [clickAssistantToShowTopic, topicPosition, onPress])
return (
<>
<ContextMenu modal={false}>
<ContextMenuTrigger>
<ButtonContainer onPress={onPress} className={isActive ? 'active' : ''}>
<AssistantNameRow className="name flex w-full justify-between" title={agent.name ?? agent.id}>
<ContextMenu modal={false}>
<ContextMenuTrigger>
<Container onClick={handlePress} isActive={isActive}>
<AssistantNameRow className="name" title={agent.name ?? agent.id}>
<AgentNameWrapper>
<AgentLabel agent={agent} />
{isActive && (
<Chip
variant="bordered"
size="sm"
radius="full"
className="aspect-square h-5 w-5 items-center justify-center border-[0.5px] bg-background text-[10px]">
{sessions.length}
</Chip>
)}
</AssistantNameRow>
</ButtonContainer>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
key="edit"
onClick={async () => {
// onOpen()
await AgentSettingsPopup.show({
agentId: agent.id
})
}}>
<EditIcon size={14} />
{t('common.edit')}
</ContextMenuItem>
<ContextMenuItem
key="delete"
className="text-danger"
onClick={() => {
window.modal.confirm({
title: t('agent.delete.title'),
content: t('agent.delete.content'),
centered: true,
okButtonProps: { danger: true },
onOk: () => onDelete(agent)
})
}}>
<DeleteIcon size={14} className="lucide-custom text-danger" />
<span className="text-danger">{t('common.delete')}</span>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
{/* <AgentModal isOpen={isOpen} onClose={onClose} agent={agent} /> */}
</>
</AgentNameWrapper>
</AssistantNameRow>
<MenuButton>
{isActive ? <SessionCount>{sessions.length}</SessionCount> : <Bot size={12} className="text-primary" />}
</MenuButton>
</Container>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
key="edit"
onClick={async () => {
// onOpen()
await AgentSettingsPopup.show({
agentId: agent.id
})
}}>
<EditIcon size={14} />
{t('common.edit')}
</ContextMenuItem>
<ContextMenuItem
key="delete"
className="text-danger"
onClick={() => {
window.modal.confirm({
title: t('agent.delete.title'),
content: t('agent.delete.content'),
centered: true,
okButtonProps: { danger: true },
onOk: () => onDelete(agent)
})
}}>
<DeleteIcon size={14} className="lucide-custom text-danger" />
<span className="text-danger">{t('common.delete')}</span>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}
const ButtonContainer: React.FC<React.ComponentProps<typeof Button>> = ({ className, children, ...props }) => (
<Button
{...props}
export const Container: React.FC<{ isActive?: boolean } & React.HTMLAttributes<HTMLDivElement>> = ({
className,
isActive,
...props
}) => (
<div
className={cn(
'relative mb-2 flex h-[37px] flex-row justify-between p-2.5',
'rounded-[var(--list-item-border-radius)]',
'border-[0.5px] border-transparent',
'w-[calc(var(--assistants-width)_-_20px)]',
'bg-transparent hover:bg-[var(--color-list-item)] hover:shadow-sm',
'cursor-pointer',
className?.includes('active') && 'bg-[var(--color-list-item)] shadow-sm',
'relative flex h-[37px] w-[calc(var(--assistants-width)-20px)] cursor-pointer flex-row justify-between rounded-[var(--list-item-border-radius)] border border-transparent px-2 hover:bg-[var(--color-list-item-hover)]',
isActive && 'bg-[var(--color-list-item)] shadow-[0_1px_2px_0_rgba(0,0,0,0.05)]',
className
)}>
{children}
</Button>
)}
{...props}
/>
)
const AssistantNameRow: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
export const AssistantNameRow: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
<div
className={cn('flex min-w-0 flex-1 flex-row items-center gap-2 text-[13px] text-[var(--color-text)]', className)}
{...props}
/>
)
export const AgentNameWrapper: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
<div className={cn('min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap', className)} {...props} />
)
export const MenuButton: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
<div
className={cn(
'absolute top-[6px] right-[9px] flex h-[22px] min-h-[22px] w-[22px] flex-row items-center justify-center rounded-full border border-[var(--color-border)] bg-[var(--color-background)] px-[5px]',
className
)}
{...props}
/>
)
export const SessionCount: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
<div
className={cn(
'flex flex-row items-center justify-center rounded-full text-[10px] text-[var(--color-text)]',
className
)}
{...props}
className={cn('text-[13px] text-[var(--color-text)]', 'flex flex-row items-center gap-2', className)}
/>
)

View File

@ -1,39 +0,0 @@
import { Alert } from '@heroui/react'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
import { addIknowAction } from '@renderer/store/runtime'
import { useTranslation } from 'react-i18next'
import { Agents } from './Agents'
import { SectionName } from './SectionName'
const ALERT_KEY = 'enable_api_server_to_use_agent'
export const AgentSection = () => {
const { t } = useTranslation()
const { apiServer } = useSettings()
const { iknow } = useRuntime()
const dispatch = useAppDispatch()
if (!apiServer.enabled) {
if (iknow[ALERT_KEY]) return null
return (
<Alert
color="warning"
title={t('agent.warning.enable_server')}
isClosable
onClose={() => {
dispatch(addIknowAction(ALERT_KEY))
}}
/>
)
}
return (
<div className="agents-tab mb-2 h-full w-full">
<SectionName name={t('common.agent_other')} />
<Agents />
</div>
)
}

View File

@ -1,3 +1,4 @@
import { cn } from '@heroui/react'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import EmojiIcon from '@renderer/components/EmojiIcon'
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
@ -28,9 +29,8 @@ import {
Tag,
Tags
} from 'lucide-react'
import { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'
import { FC, memo, PropsWithChildren, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import * as tinyPinyin from 'tiny-pinyin'
import AssistantTagsPopup from './AssistantTagsPopup'
@ -46,6 +46,8 @@ interface AssistantItemProps {
copyAssistant: (assistant: Assistant) => void
onTagClick?: (tag: string) => void
handleSortByChange?: (sortType: AssistantsSortType) => void
sortByPinyinAsc?: () => void
sortByPinyinDesc?: () => void
}
const AssistantItem: FC<AssistantItemProps> = ({
@ -56,7 +58,9 @@ const AssistantItem: FC<AssistantItemProps> = ({
onDelete,
addPreset,
copyAssistant,
handleSortByChange
handleSortByChange,
sortByPinyinAsc: externalSortByPinyinAsc,
sortByPinyinDesc: externalSortByPinyinDesc
}) => {
const { t } = useTranslation()
const { allTags } = useTags()
@ -78,14 +82,19 @@ const AssistantItem: FC<AssistantItemProps> = ({
setIsPending(hasPending)
}, [isActive, assistant.topics])
const sortByPinyinAsc = useCallback(() => {
// Local sort functions
const localSortByPinyinAsc = useCallback(() => {
updateAssistants(sortAssistantsByPinyin(assistants, true))
}, [assistants, updateAssistants])
const sortByPinyinDesc = useCallback(() => {
const localSortByPinyinDesc = useCallback(() => {
updateAssistants(sortAssistantsByPinyin(assistants, false))
}, [assistants, updateAssistants])
// Use external sort functions if provided, otherwise use local ones
const sortByPinyinAsc = externalSortByPinyinAsc || localSortByPinyinAsc
const sortByPinyinDesc = externalSortByPinyinDesc || localSortByPinyinDesc
const menuItems = useMemo(
() =>
getMenuItems({
@ -145,7 +154,7 @@ const AssistantItem: FC<AssistantItemProps> = ({
menu={{ items: menuItems }}
trigger={['contextMenu']}
popupRender={(menu) => <div onPointerDown={(e) => e.stopPropagation()}>{menu}</div>}>
<Container onClick={handleSwitch} className={isActive ? 'active' : ''}>
<Container onClick={handleSwitch} isActive={isActive}>
<AssistantNameRow className="name" title={fullAssistantName}>
{assistantIconType === 'model' ? (
<ModelAvatar
@ -380,65 +389,75 @@ function getMenuItems({
]
}
const Container = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 8px;
height: 37px;
position: relative;
border-radius: var(--list-item-border-radius);
border: 0.5px solid transparent;
width: calc(var(--assistants-width) - 20px);
cursor: pointer;
const Container = ({
children,
isActive,
className,
...props
}: PropsWithChildren<{ isActive?: boolean } & React.HTMLAttributes<HTMLDivElement>>) => (
<div
{...props}
className={cn(
'relative flex h-[37px] w-[calc(var(--assistants-width)-20px)] cursor-pointer flex-row justify-between rounded-[var(--list-item-border-radius)] border-[0.5px] border-transparent px-2 hover:bg-[var(--color-list-item-hover)]',
isActive && 'bg-[var(--color-list-item)] shadow-[0_1px_2px_0_rgba(0,0,0,0.05)]',
className
)}>
{children}
</div>
)
&:hover {
background-color: var(--color-list-item-hover);
}
&.active {
background-color: var(--color-list-item);
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
`
const AssistantNameRow = ({
children,
className,
...props
}: PropsWithChildren<{} & React.HTMLAttributes<HTMLDivElement>>) => (
<div
{...props}
className={cn('flex min-w-0 flex-1 flex-row items-center gap-2 text-[13px] text-[var(--color-text)]', className)}>
{children}
</div>
)
const AssistantNameRow = styled.div`
color: var(--color-text);
font-size: 13px;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
`
const AssistantName = ({
children,
className,
...props
}: PropsWithChildren<{} & React.HTMLAttributes<HTMLDivElement>>) => (
<div
{...props}
className={cn('min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-[13px]', className)}>
{children}
</div>
)
const AssistantName = styled.div`
font-size: 13px;
`
const MenuButton = ({
children,
className,
...props
}: PropsWithChildren<{} & React.HTMLAttributes<HTMLDivElement>>) => (
<div
{...props}
className={cn(
'absolute top-[6px] right-[9px] flex h-[22px] min-h-[22px] min-w-[22px] flex-row items-center justify-center rounded-[11px] border-[0.5px] border-[var(--color-border)] bg-[var(--color-background)] px-[5px]',
className
)}>
{children}
</div>
)
const MenuButton = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
min-width: 22px;
height: 22px;
min-height: 22px;
border-radius: 11px;
position: absolute;
background-color: var(--color-background);
right: 9px;
top: 6px;
padding: 0 5px;
border: 0.5px solid var(--color-border);
`
const TopicCount = styled.div`
color: var(--color-text);
font-size: 10px;
border-radius: 10px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
`
const TopicCount = ({
children,
className,
...props
}: PropsWithChildren<{} & React.HTMLAttributes<HTMLDivElement>>) => (
<div
{...props}
className={cn(
'flex flex-row items-center justify-center rounded-[10px] text-[10px] text-[var(--color-text)]',
className
)}>
{children}
</div>
)
export default memo(AssistantItem)

View File

@ -1,4 +1,4 @@
import { Alert, Button, Spinner } from '@heroui/react'
import { Alert, Spinner } from '@heroui/react'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { useAgent } from '@renderer/hooks/agents/useAgent'
import { useSessions } from '@renderer/hooks/agents/useSessions'
@ -13,10 +13,10 @@ import {
import { CreateSessionForm } from '@renderer/types'
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
import { AnimatePresence, motion } from 'framer-motion'
import { Plus } from 'lucide-react'
import { memo, useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import AddButton from './AddButton'
import SessionItem from './SessionItem'
// const logger = loggerService.withContext('SessionsTab')
@ -115,12 +115,9 @@ const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
transition={{ duration: 0.3 }}
className="sessions-tab flex h-full w-full flex-col p-2">
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
<Button
onPress={handleCreateSession}
className="mb-2 w-full justify-start bg-transparent text-foreground-500 hover:bg-accent">
<Plus size={16} className="mr-1 shrink-0" />
<AddButton onPress={handleCreateSession} className="mb-2">
{t('agent.session.add.title')}
</Button>
</AddButton>
</motion.div>
<AnimatePresence>
{/* h-9 */}

View File

@ -0,0 +1,63 @@
import { DownOutlined, RightOutlined } from '@ant-design/icons'
import { cn } from '@heroui/react'
import { Tooltip } from 'antd'
import { FC, ReactNode } from 'react'
interface TagGroupProps {
tag: string
isCollapsed: boolean
onToggle: (tag: string) => void
showTitle?: boolean
children: ReactNode
}
export const TagGroup: FC<TagGroupProps> = ({ tag, isCollapsed, onToggle, showTitle = true, children }) => {
return (
<TagsContainer>
{showTitle && (
<GroupTitle onClick={() => onToggle(tag)}>
<Tooltip title={tag}>
<GroupTitleName>
{isCollapsed ? (
<RightOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
) : (
<DownOutlined style={{ fontSize: '10px', marginRight: '5px' }} />
)}
{tag}
</GroupTitleName>
</Tooltip>
<GroupTitleDivider />
</GroupTitle>
)}
{!isCollapsed && <div>{children}</div>}
</TagsContainer>
)
}
const TagsContainer: FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, ...props }) => (
<div className={cn('flex flex-col gap-2')} {...props}>
{children}
</div>
)
const GroupTitle: FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, ...props }) => (
<div
className={cn(
'my-1 flex h-6 cursor-pointer flex-row items-center justify-between font-medium text-[var(--color-text-2)] text-xs'
)}
{...props}>
{children}
</div>
)
const GroupTitleName: FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, ...props }) => (
<div
className={cn('mr-1 box-border flex max-w-[50%] truncate px-1 text-[13px] text-[var(--color-text)] leading-6')}
{...props}>
{children}
</div>
)
const GroupTitleDivider: FC<React.HTMLAttributes<HTMLDivElement>> = (props) => (
<div className={cn('flex-1 border-[var(--color-border)] border-t')} {...props} />
)

View File

@ -41,7 +41,6 @@ import {
PackagePlus,
PinIcon,
PinOffIcon,
PlusIcon,
Save,
Sparkles,
UploadIcon,
@ -52,6 +51,8 @@ import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import styled from 'styled-components'
import AddButton from './AddButton'
interface Props {
assistant: Assistant
activeTopic: Topic
@ -497,13 +498,12 @@ export const Topics: React.FC<Props> = ({ assistant: _assistant, activeTopic, se
className="topics-tab"
list={sortedTopics}
onUpdate={updateTopics}
style={{ height: '100%', padding: '13px 0 10px 10px' }}
style={{ height: '100%', padding: '11px 0 10px 10px' }}
itemContainerStyle={{ paddingBottom: '8px' }}
header={
<AddTopicButton onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)}>
<PlusIcon size={16} />
<AddButton onPress={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} className="mb-2">
{t('chat.add.topic.title')}
</AddTopicButton>
</AddButton>
}>
{(topic) => {
const isActive = topic.id === activeTopic?.id
@ -740,31 +740,6 @@ const FulfilledIndicator = styled.div.attrs({
background-color: var(--color-status-success);
`
const AddTopicButton = styled.div`
display: flex;
align-items: center;
gap: 6px;
width: calc(100% - 10px);
padding: 7px 12px;
margin-bottom: 8px;
background: transparent;
color: var(--color-text-2);
font-size: 13px;
border-radius: var(--list-item-border-radius);
cursor: pointer;
transition: all 0.2s;
margin-top: -5px;
&:hover {
background-color: var(--color-list-item-hover);
color: var(--color-text-1);
}
.anticon {
font-size: 12px;
}
`
const TopicPromptText = styled.div`
color: var(--color-text-2);
font-size: 12px;

View File

@ -0,0 +1,61 @@
import { Button, Popover, PopoverContent, PopoverTrigger } from '@heroui/react'
import { AgentModal } from '@renderer/components/Popups/agent/AgentModal'
import { Bot, MessageSquare } from 'lucide-react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AddButton from './AddButton'
interface UnifiedAddButtonProps {
onCreateAssistant: () => void
}
const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant }) => {
const { t } = useTranslation()
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
const [isAgentModalOpen, setIsAgentModalOpen] = useState(false)
const handleAddAssistant = () => {
setIsPopoverOpen(false)
onCreateAssistant()
}
const handleAddAgent = () => {
setIsPopoverOpen(false)
setIsAgentModalOpen(true)
}
return (
<div className="mb-1">
<Popover
isOpen={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
placement="bottom"
classNames={{ content: 'p-0 min-w-[200px]' }}>
<PopoverTrigger>
<AddButton>{t('chat.add.assistant.title')}</AddButton>
</PopoverTrigger>
<PopoverContent>
<div className="flex w-full flex-col gap-1 p-1">
<Button
onPress={handleAddAssistant}
className="w-full justify-start bg-transparent hover:bg-[var(--color-list-item)]"
startContent={<MessageSquare size={16} className="shrink-0" />}>
{t('chat.add.assistant.title')}
</Button>
<Button
onPress={handleAddAgent}
className="w-full justify-start bg-transparent hover:bg-[var(--color-list-item)]"
startContent={<Bot size={16} className="shrink-0" />}>
{t('agent.add.title')}
</Button>
</div>
</PopoverContent>
</Popover>
<AgentModal isOpen={isAgentModalOpen} onClose={() => setIsAgentModalOpen(false)} />
</div>
)
}
export default UnifiedAddButton

View File

@ -0,0 +1,108 @@
import { DraggableList } from '@renderer/components/DraggableList'
import { Assistant, AssistantsSortType } from '@renderer/types'
import { FC, useCallback } from 'react'
import { UnifiedItem } from '../hooks/useUnifiedItems'
import AgentItem from './AgentItem'
import AssistantItem from './AssistantItem'
interface UnifiedListProps {
items: UnifiedItem[]
activeAssistantId: string
activeAgentId: string | null
sortBy: AssistantsSortType
onReorder: (newList: UnifiedItem[]) => void
onDragStart: () => void
onDragEnd: () => void
onAssistantSwitch: (assistant: Assistant) => void
onAssistantDelete: (assistant: Assistant) => void
onAgentDelete: (agentId: string) => void
onAgentPress: (agentId: string) => void
addPreset: (assistant: Assistant) => void
copyAssistant: (assistant: Assistant) => void
onCreateDefaultAssistant: () => void
handleSortByChange: (sortType: AssistantsSortType) => void
sortByPinyinAsc: () => void
sortByPinyinDesc: () => void
}
export const UnifiedList: FC<UnifiedListProps> = (props) => {
const {
items,
activeAssistantId,
activeAgentId,
sortBy,
onReorder,
onDragStart,
onDragEnd,
onAssistantSwitch,
onAssistantDelete,
onAgentDelete,
onAgentPress,
addPreset,
copyAssistant,
onCreateDefaultAssistant,
handleSortByChange,
sortByPinyinAsc,
sortByPinyinDesc
} = props
const renderUnifiedItem = useCallback(
(item: UnifiedItem) => {
if (item.type === 'agent') {
return (
<AgentItem
key={`agent-${item.data.id}`}
agent={item.data}
isActive={item.data.id === activeAgentId}
onDelete={() => onAgentDelete(item.data.id)}
onPress={() => onAgentPress(item.data.id)}
/>
)
} else {
return (
<AssistantItem
key={`assistant-${item.data.id}`}
assistant={item.data}
isActive={item.data.id === activeAssistantId}
sortBy={sortBy}
onSwitch={onAssistantSwitch}
onDelete={onAssistantDelete}
addPreset={addPreset}
copyAssistant={copyAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant}
handleSortByChange={handleSortByChange}
sortByPinyinAsc={sortByPinyinAsc}
sortByPinyinDesc={sortByPinyinDesc}
/>
)
}
},
[
activeAgentId,
activeAssistantId,
sortBy,
onAssistantSwitch,
onAssistantDelete,
onAgentDelete,
onAgentPress,
addPreset,
copyAssistant,
onCreateDefaultAssistant,
handleSortByChange,
sortByPinyinAsc,
sortByPinyinDesc
]
)
return (
<DraggableList
list={items}
itemKey={(item) => `${item.type}-${item.data.id}`}
onUpdate={onReorder}
onDragStart={onDragStart}
onDragEnd={onDragEnd}>
{renderUnifiedItem}
</DraggableList>
)
}

View File

@ -0,0 +1,132 @@
import { DraggableList } from '@renderer/components/DraggableList'
import { Assistant, AssistantsSortType } from '@renderer/types'
import { FC, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { UnifiedItem } from '../hooks/useUnifiedItems'
import AgentItem from './AgentItem'
import AssistantItem from './AssistantItem'
import { TagGroup } from './TagGroup'
interface GroupedItems {
tag: string
items: UnifiedItem[]
}
interface UnifiedTagGroupsProps {
groupedItems: GroupedItems[]
activeAssistantId: string
activeAgentId: string | null
sortBy: AssistantsSortType
collapsedTags: Record<string, boolean>
onGroupReorder: (tag: string, newList: UnifiedItem[]) => void
onDragStart: () => void
onDragEnd: () => void
onToggleTagCollapse: (tag: string) => void
onAssistantSwitch: (assistant: Assistant) => void
onAssistantDelete: (assistant: Assistant) => void
onAgentDelete: (agentId: string) => void
onAgentPress: (agentId: string) => void
addPreset: (assistant: Assistant) => void
copyAssistant: (assistant: Assistant) => void
onCreateDefaultAssistant: () => void
handleSortByChange: (sortType: AssistantsSortType) => void
sortByPinyinAsc: () => void
sortByPinyinDesc: () => void
}
export const UnifiedTagGroups: FC<UnifiedTagGroupsProps> = (props) => {
const {
groupedItems,
activeAssistantId,
activeAgentId,
sortBy,
collapsedTags,
onGroupReorder,
onDragStart,
onDragEnd,
onToggleTagCollapse,
onAssistantSwitch,
onAssistantDelete,
onAgentDelete,
onAgentPress,
addPreset,
copyAssistant,
onCreateDefaultAssistant,
handleSortByChange,
sortByPinyinAsc,
sortByPinyinDesc
} = props
const { t } = useTranslation()
const renderUnifiedItem = useCallback(
(item: UnifiedItem) => {
if (item.type === 'agent') {
return (
<AgentItem
key={`agent-${item.data.id}`}
agent={item.data}
isActive={item.data.id === activeAgentId}
onDelete={() => onAgentDelete(item.data.id)}
onPress={() => onAgentPress(item.data.id)}
/>
)
} else {
return (
<AssistantItem
key={`assistant-${item.data.id}`}
assistant={item.data}
isActive={item.data.id === activeAssistantId}
sortBy={sortBy}
onSwitch={onAssistantSwitch}
onDelete={onAssistantDelete}
addPreset={addPreset}
copyAssistant={copyAssistant}
onCreateDefaultAssistant={onCreateDefaultAssistant}
handleSortByChange={handleSortByChange}
sortByPinyinAsc={sortByPinyinAsc}
sortByPinyinDesc={sortByPinyinDesc}
/>
)
}
},
[
activeAgentId,
activeAssistantId,
sortBy,
onAssistantSwitch,
onAssistantDelete,
onAgentDelete,
onAgentPress,
addPreset,
copyAssistant,
onCreateDefaultAssistant,
handleSortByChange,
sortByPinyinAsc,
sortByPinyinDesc
]
)
return (
<div>
{groupedItems.map((group) => (
<TagGroup
key={group.tag}
tag={group.tag}
isCollapsed={collapsedTags[group.tag]}
onToggle={onToggleTagCollapse}
showTitle={group.tag !== t('assistants.tags.untagged')}>
<DraggableList
list={group.items}
itemKey={(item) => `${item.type}-${item.data.id}`}
onUpdate={(newList) => onGroupReorder(group.tag, newList)}
onDragStart={onDragStart}
onDragEnd={onDragEnd}>
{renderUnifiedItem}
</DraggableList>
</TagGroup>
))}
</div>
)
}

View File

@ -0,0 +1,19 @@
import { useAgentSessionInitializer } from '@renderer/hooks/agents/useAgentSessionInitializer'
import { useAppDispatch } from '@renderer/store'
import { setActiveAgentId as setActiveAgentIdAction } from '@renderer/store/runtime'
import { useCallback } from 'react'
export const useActiveAgent = () => {
const dispatch = useAppDispatch()
const { initializeAgentSession } = useAgentSessionInitializer()
const setActiveAgentId = useCallback(
async (id: string) => {
dispatch(setActiveAgentIdAction(id))
await initializeAgentSession(id)
},
[dispatch, initializeAgentSession]
)
return { setActiveAgentId }
}

View File

@ -0,0 +1,140 @@
import { useAppDispatch } from '@renderer/store'
import { setUnifiedListOrder } from '@renderer/store/assistants'
import { AgentEntity, Assistant } from '@renderer/types'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { UnifiedItem } from './useUnifiedItems'
interface UseUnifiedGroupingOptions {
unifiedItems: UnifiedItem[]
assistants: Assistant[]
agents: AgentEntity[]
apiServerEnabled: boolean
agentsLoading: boolean
agentsError: Error | null
updateAssistants: (assistants: Assistant[]) => void
}
export const useUnifiedGrouping = (options: UseUnifiedGroupingOptions) => {
const { unifiedItems, assistants, agents, apiServerEnabled, agentsLoading, agentsError, updateAssistants } = options
const { t } = useTranslation()
const dispatch = useAppDispatch()
// Group unified items by tags
const groupedUnifiedItems = useMemo(() => {
const groups = new Map<string, UnifiedItem[]>()
unifiedItems.forEach((item) => {
if (item.type === 'agent') {
// Agents go to untagged group
const groupKey = t('assistants.tags.untagged')
if (!groups.has(groupKey)) {
groups.set(groupKey, [])
}
groups.get(groupKey)!.push(item)
} else {
// Assistants use their tags
const tags = item.data.tags?.length ? item.data.tags : [t('assistants.tags.untagged')]
tags.forEach((tag) => {
if (!groups.has(tag)) {
groups.set(tag, [])
}
groups.get(tag)!.push(item)
})
}
})
// Sort groups: untagged first, then tagged groups
const untaggedKey = t('assistants.tags.untagged')
const sortedGroups = Array.from(groups.entries()).sort(([tagA], [tagB]) => {
if (tagA === untaggedKey) return -1
if (tagB === untaggedKey) return 1
return 0
})
return sortedGroups.map(([tag, items]) => ({ tag, items }))
}, [unifiedItems, t])
const handleUnifiedGroupReorder = useCallback(
(tag: string, newGroupList: UnifiedItem[]) => {
// Extract only assistants from the new list for updating
const newAssistants = newGroupList.filter((item) => item.type === 'assistant').map((item) => item.data)
// Update assistants state
let insertIndex = 0
const updatedAssistants = assistants.map((a) => {
const tags = a.tags?.length ? a.tags : [t('assistants.tags.untagged')]
if (tags.includes(tag)) {
const replaced = newAssistants[insertIndex]
insertIndex += 1
return replaced || a
}
return a
})
updateAssistants(updatedAssistants)
// Rebuild unified order and save to Redux
const newUnifiedItems: UnifiedItem[] = []
const availableAgents = new Map<string, AgentEntity>()
const availableAssistants = new Map<string, Assistant>()
if (apiServerEnabled && !agentsLoading && !agentsError) {
agents.forEach((agent) => availableAgents.set(agent.id, agent))
}
updatedAssistants.forEach((assistant) => availableAssistants.set(assistant.id, assistant))
// Reconstruct order based on current groupedUnifiedItems structure
groupedUnifiedItems.forEach((group) => {
if (group.tag === tag) {
// Use the new group list for this tag
newGroupList.forEach((item) => {
newUnifiedItems.push(item)
if (item.type === 'agent') {
availableAgents.delete(item.data.id)
} else {
availableAssistants.delete(item.data.id)
}
})
} else {
// Keep existing order for other tags
group.items.forEach((item) => {
newUnifiedItems.push(item)
if (item.type === 'agent') {
availableAgents.delete(item.data.id)
} else {
availableAssistants.delete(item.data.id)
}
})
}
})
// Add any remaining items
availableAgents.forEach((agent) => newUnifiedItems.push({ type: 'agent', data: agent }))
availableAssistants.forEach((assistant) => newUnifiedItems.push({ type: 'assistant', data: assistant }))
// Save to Redux
const orderToSave = newUnifiedItems.map((item) => ({
type: item.type,
id: item.data.id
}))
dispatch(setUnifiedListOrder(orderToSave))
},
[
assistants,
t,
updateAssistants,
apiServerEnabled,
agentsLoading,
agentsError,
agents,
groupedUnifiedItems,
dispatch
]
)
return {
groupedUnifiedItems,
handleUnifiedGroupReorder
}
}

View File

@ -0,0 +1,73 @@
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setUnifiedListOrder } from '@renderer/store/assistants'
import { AgentEntity, Assistant } from '@renderer/types'
import { useCallback, useMemo } from 'react'
export type UnifiedItem = { type: 'agent'; data: AgentEntity } | { type: 'assistant'; data: Assistant }
interface UseUnifiedItemsOptions {
agents: AgentEntity[]
assistants: Assistant[]
apiServerEnabled: boolean
agentsLoading: boolean
agentsError: Error | null
updateAssistants: (assistants: Assistant[]) => void
}
export const useUnifiedItems = (options: UseUnifiedItemsOptions) => {
const { agents, assistants, apiServerEnabled, agentsLoading, agentsError, updateAssistants } = options
const dispatch = useAppDispatch()
const unifiedListOrder = useAppSelector((state) => state.assistants.unifiedListOrder || [])
// Create unified items list (agents + assistants) with saved order
const unifiedItems = useMemo(() => {
const items: UnifiedItem[] = []
// Collect all available items
const availableAgents = new Map<string, AgentEntity>()
const availableAssistants = new Map<string, Assistant>()
if (apiServerEnabled && !agentsLoading && !agentsError) {
agents.forEach((agent) => availableAgents.set(agent.id, agent))
}
assistants.forEach((assistant) => availableAssistants.set(assistant.id, assistant))
// Apply saved order
unifiedListOrder.forEach((item) => {
if (item.type === 'agent' && availableAgents.has(item.id)) {
items.push({ type: 'agent', data: availableAgents.get(item.id)! })
availableAgents.delete(item.id)
} else if (item.type === 'assistant' && availableAssistants.has(item.id)) {
items.push({ type: 'assistant', data: availableAssistants.get(item.id)! })
availableAssistants.delete(item.id)
}
})
// Add new items (not in saved order) to the end
availableAgents.forEach((agent) => items.push({ type: 'agent', data: agent }))
availableAssistants.forEach((assistant) => items.push({ type: 'assistant', data: assistant }))
return items
}, [agents, assistants, apiServerEnabled, agentsLoading, agentsError, unifiedListOrder])
const handleUnifiedListReorder = useCallback(
(newList: UnifiedItem[]) => {
// Save the unified order to Redux
const orderToSave = newList.map((item) => ({
type: item.type,
id: item.data.id
}))
dispatch(setUnifiedListOrder(orderToSave))
// Extract and update assistants order
const newAssistants = newList.filter((item) => item.type === 'assistant').map((item) => item.data)
updateAssistants(newAssistants)
},
[dispatch, updateAssistants]
)
return {
unifiedItems,
handleUnifiedListReorder
}
}

View File

@ -0,0 +1,56 @@
import { useAppDispatch } from '@renderer/store'
import { setUnifiedListOrder } from '@renderer/store/assistants'
import { Assistant } from '@renderer/types'
import { useCallback } from 'react'
import * as tinyPinyin from 'tiny-pinyin'
import { UnifiedItem } from './useUnifiedItems'
interface UseUnifiedSortingOptions {
unifiedItems: UnifiedItem[]
updateAssistants: (assistants: Assistant[]) => void
}
export const useUnifiedSorting = (options: UseUnifiedSortingOptions) => {
const { unifiedItems, updateAssistants } = options
const dispatch = useAppDispatch()
const sortUnifiedItemsByPinyin = useCallback((items: UnifiedItem[], isAscending: boolean) => {
return [...items].sort((a, b) => {
const nameA = a.type === 'agent' ? a.data.name || a.data.id : a.data.name
const nameB = b.type === 'agent' ? b.data.name || b.data.id : b.data.name
const pinyinA = tinyPinyin.convertToPinyin(nameA, '', true)
const pinyinB = tinyPinyin.convertToPinyin(nameB, '', true)
return isAscending ? pinyinA.localeCompare(pinyinB) : pinyinB.localeCompare(pinyinA)
})
}, [])
const sortByPinyinAsc = useCallback(() => {
const sorted = sortUnifiedItemsByPinyin(unifiedItems, true)
const orderToSave = sorted.map((item) => ({
type: item.type,
id: item.data.id
}))
dispatch(setUnifiedListOrder(orderToSave))
// Also update assistants order
const newAssistants = sorted.filter((item) => item.type === 'assistant').map((item) => item.data)
updateAssistants(newAssistants)
}, [unifiedItems, sortUnifiedItemsByPinyin, dispatch, updateAssistants])
const sortByPinyinDesc = useCallback(() => {
const sorted = sortUnifiedItemsByPinyin(unifiedItems, false)
const orderToSave = sorted.map((item) => ({
type: item.type,
id: item.data.id
}))
dispatch(setUnifiedListOrder(orderToSave))
// Also update assistants order
const newAssistants = sorted.filter((item) => item.type === 'assistant').map((item) => item.data)
updateAssistants(newAssistants)
}, [unifiedItems, sortUnifiedItemsByPinyin, dispatch, updateAssistants])
return {
sortByPinyinAsc,
sortByPinyinDesc
}
}

View File

@ -121,7 +121,7 @@ const WebviewSearch: FC<WebviewSearchProps> = ({ webviewRef, isWebviewReady, app
const nextWebview = webviewRef.current ?? null
if (currentWebview === nextWebview) return
setCurrentWebview(nextWebview)
})
}, [currentWebview, webviewRef])
useEffect(() => {
const target = currentWebview

View File

@ -22,7 +22,6 @@ import { ExtractResults } from '@renderer/utils/extract'
import { fetchWebContents } from '@renderer/utils/fetch'
import { consolidateReferencesByUrl, selectReferences } from '@renderer/utils/websearch'
import dayjs from 'dayjs'
import { LRUCache } from 'lru-cache'
import { sliceByTokens } from 'tokenx'
import { getKnowledgeBaseParams } from './KnowledgeService'
@ -32,7 +31,6 @@ const logger = loggerService.withContext('WebSearchService')
interface RequestState {
signal: AbortSignal | null
searchBase?: KnowledgeBase
isPaused: boolean
createdAt: number
}
@ -49,16 +47,7 @@ class WebSearchService {
isPaused = false
// 管理不同请求的状态
private requestStates = new LRUCache<string, RequestState>({
max: 5, // 最多5个并发请求
ttl: 1000 * 60 * 2, // 2分钟过期
dispose: (requestState: RequestState, requestId: string) => {
if (!requestState.searchBase) return
window.api.knowledgeBase
.delete(removeSpecialCharactersForFileName(requestState.searchBase.id))
.catch((error) => logger.warn(`Failed to cleanup search base for ${requestId}:`, error))
}
})
private requestStates = new Map<string, RequestState>()
/**
*
@ -209,7 +198,7 @@ class WebSearchService {
}
/**
*
*
*/
private async ensureSearchBase(
config: CompressionConfig,
@ -218,25 +207,13 @@ class WebSearchService {
): Promise<KnowledgeBase> {
// requestId: eg: openai-responses-openai/gpt-5-timestamp-uuid
const baseId = `websearch-compression-${requestId}`
const state = this.getRequestState(requestId)
// 如果已存在且配置未变,直接复用
if (state.searchBase && this.isConfigMatched(state.searchBase, config)) {
return state.searchBase
}
// 清理旧的知识库
if (state.searchBase) {
// 将requestId中的 '/' 映射为 '_'
await window.api.knowledgeBase.delete(removeSpecialCharactersForFileName(state.searchBase.id))
}
if (!config.embeddingModel) {
throw new Error('Embedding model is required for RAG compression')
}
// 创建新的知识库
state.searchBase = {
const searchBase: KnowledgeBase = {
id: baseId,
name: `WebSearch-RAG-${requestId}`,
model: config.embeddingModel,
@ -249,25 +226,23 @@ class WebSearchService {
version: 1
}
// 更新LRU cache
this.requestStates.set(requestId, state)
// 创建知识库
const baseParams = getKnowledgeBaseParams(state.searchBase)
const baseParams = getKnowledgeBaseParams(searchBase)
await window.api.knowledgeBase.create(baseParams)
return state.searchBase
return searchBase
}
/**
*
*
*/
private isConfigMatched(base: KnowledgeBase, config: CompressionConfig): boolean {
return (
base.model.id === config.embeddingModel?.id &&
base.rerankModel?.id === config.rerankModel?.id &&
base.dimensions === config.embeddingDimensions
)
private async cleanupSearchBase(searchBase: KnowledgeBase): Promise<void> {
try {
await window.api.knowledgeBase.delete(removeSpecialCharactersForFileName(searchBase.id))
logger.debug(`Cleaned up search base: ${searchBase.id}`)
} catch (error) {
logger.warn(`Failed to cleanup search base ${searchBase.id}:`, error as Error)
}
}
/**
@ -334,45 +309,50 @@ class WebSearchService {
const searchBase = await this.ensureSearchBase(config, totalDocumentCount, requestId)
logger.debug('Search base for RAG compression: ', searchBase)
// 1. 清空知识库
const baseParams = getKnowledgeBaseParams(searchBase)
await window.api.knowledgeBase.reset(baseParams)
try {
// 1. 清空知识库
const baseParams = getKnowledgeBaseParams(searchBase)
await window.api.knowledgeBase.reset(baseParams)
logger.debug('Search base parameters for RAG compression: ', baseParams)
logger.debug('Search base parameters for RAG compression: ', baseParams)
// 2. 顺序添加所有搜索结果到知识库
// FIXME: 目前的知识库 add 不支持并发
for (const result of rawResults) {
const item: KnowledgeItem & { sourceUrl?: string } = {
id: uuid(),
type: 'note',
content: result.content,
sourceUrl: result.url, // 设置 sourceUrl 用于映射
created_at: Date.now(),
updated_at: Date.now(),
processingStatus: 'pending'
// 2. 顺序添加所有搜索结果到知识库
// FIXME: 目前的知识库 add 不支持并发
for (const result of rawResults) {
const item: KnowledgeItem & { sourceUrl?: string } = {
id: uuid(),
type: 'note',
content: result.content,
sourceUrl: result.url, // 设置 sourceUrl 用于映射
created_at: Date.now(),
updated_at: Date.now(),
processingStatus: 'pending'
}
await window.api.knowledgeBase.add({
base: getKnowledgeBaseParams(searchBase),
item
})
}
await window.api.knowledgeBase.add({
base: getKnowledgeBaseParams(searchBase),
item
// 3. 对知识库执行多问题搜索获取压缩结果
const references = await this.querySearchBase(questions, searchBase)
// 4. 使用 Round Robin 策略选择引用
const selectedReferences = selectReferences(rawResults, references, totalDocumentCount)
logger.verbose('With RAG, the number of search results:', {
raw: rawResults.length,
retrieved: references.length,
selected: selectedReferences.length
})
// 5. 按 sourceUrl 分组并合并同源片段
return consolidateReferencesByUrl(rawResults, selectedReferences)
} finally {
// 无论成功或失败都立即清理知识库
await this.cleanupSearchBase(searchBase)
}
// 3. 对知识库执行多问题搜索获取压缩结果
const references = await this.querySearchBase(questions, searchBase)
// 4. 使用 Round Robin 策略选择引用
const selectedReferences = selectReferences(rawResults, references, totalDocumentCount)
logger.verbose('With RAG, the number of search results:', {
raw: rawResults.length,
retrieved: references.length,
selected: selectedReferences.length
})
// 5. 按 sourceUrl 分组并合并同源片段
return consolidateReferencesByUrl(rawResults, selectedReferences)
}
/**

View File

@ -13,6 +13,7 @@ export interface AssistantsState {
tagsOrder: string[]
collapsedTags: Record<string, boolean>
presets: AssistantPreset[]
unifiedListOrder: Array<{ type: 'agent' | 'assistant'; id: string }>
}
const initialState: AssistantsState = {
@ -20,7 +21,8 @@ const initialState: AssistantsState = {
assistants: [getDefaultAssistant()],
tagsOrder: [],
collapsedTags: {},
presets: []
presets: [],
unifiedListOrder: []
}
const assistantsSlice = createSlice({
@ -96,6 +98,9 @@ const assistantsSlice = createSlice({
[tag]: !prev[tag]
}
},
setUnifiedListOrder: (state, action: PayloadAction<Array<{ type: 'agent' | 'assistant'; id: string }>>) => {
state.unifiedListOrder = action.payload
},
addTopic: (state, action: PayloadAction<{ assistantId: string; topic: Topic }>) => {
const topic = action.payload.topic
topic.createdAt = topic.createdAt || new Date().toISOString()
@ -244,6 +249,7 @@ export const {
setTagsOrder,
updateAssistantSettings,
updateTagCollapse,
setUnifiedListOrder,
setAssistantPresets,
addAssistantPreset,
removeAssistantPreset,

View File

@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 161,
version: 163,
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
migrate
},

View File

@ -68,6 +68,7 @@ function removeMiniAppIconsFromState(state: RootState) {
function removeMiniAppFromState(state: RootState, id: string) {
if (state.minapps) {
state.minapps.pinned = state.minapps.pinned.filter((app) => app.id !== id)
state.minapps.enabled = state.minapps.enabled.filter((app) => app.id !== id)
state.minapps.disabled = state.minapps.disabled.filter((app) => app.id !== id)
}
@ -2596,16 +2597,30 @@ const migrateConfig = {
return state
}
},
'160': (state: RootState) => {
'161': (state: RootState) => {
try {
removeMiniAppFromState(state, 'nm-search')
removeMiniAppFromState(state, 'hika')
removeMiniAppFromState(state, 'hugging-chat')
addProvider(state, 'cherryin')
state.llm.providers = moveProvider(state.llm.providers, 'cherryin', 1)
return state
} catch (error) {
logger.error('migrate 161 error', error as Error)
return state
}
},
'162': (state: RootState) => {
try {
// @ts-ignore
if (state?.agents?.agents) {
// @ts-ignore
state.assistants.presets = [...state.agents.agents]
// @ts-ignore
delete state.agents.agents
}
if (state.settings.sidebarIcons) {
state.settings.sidebarIcons.visible = state.settings.sidebarIcons.visible.map((icon) => {
// @ts-ignore
return icon === 'agents' ? 'store' : icon
@ -2615,6 +2630,7 @@ const migrateConfig = {
return icon === 'agents' ? 'store' : icon
})
}
state.llm.providers.forEach((provider) => {
if (provider.anthropicApiHost) {
return
@ -2642,20 +2658,17 @@ const migrateConfig = {
case 'new-api':
provider.anthropicApiHost = 'http://localhost:3000'
break
case 'cherryai':
provider.anthropicApiHost = 'https://api.cherry-ai.com'
break
case 'grok':
provider.anthropicApiHost = 'https://api.x.ai'
}
})
return state
} catch (error) {
logger.error('migrate 160 error', error as Error)
logger.error('migrate 162 error', error as Error)
return state
}
},
'161': (state: RootState) => {
'163': (state: RootState) => {
try {
if (state.settings && state.settings.sidebarIcons) {
if (!state.settings.sidebarIcons.visible.includes('video')) {

View File

@ -106,7 +106,7 @@ export type Provider = {
}
export const SystemProviderIds = {
// cherryin: 'cherryin',
cherryin: 'cherryin',
silicon: 'silicon',
aihubmix: 'aihubmix',
ocoolai: 'ocoolai',

193
yarn.lock
View File

@ -74,112 +74,125 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/amazon-bedrock@npm:^3.0.29":
version: 3.0.29
resolution: "@ai-sdk/amazon-bedrock@npm:3.0.29"
"@ai-sdk/amazon-bedrock@npm:^3.0.35":
version: 3.0.35
resolution: "@ai-sdk/amazon-bedrock@npm:3.0.35"
dependencies:
"@ai-sdk/anthropic": "npm:2.0.22"
"@ai-sdk/anthropic": "npm:2.0.27"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
"@smithy/eventstream-codec": "npm:^4.0.1"
"@smithy/util-utf8": "npm:^4.0.0"
aws4fetch: "npm:^1.0.20"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/7add02e6c13774943929bb5d568b3110f6badc6d95cb56c6d3011cafc45778e27c0133417dd7fe835e7f0b1ae7767c22a7d5e3d39f725e2aa44e2b6e47d95fb7
checksum: 10c0/0e3e0ed1730fa6a14d8d7ca14b7823ec0b80c9d666435d97a505e7fb0c1818378343cdb647e3cc08d7f15d201cbeb04272c5128065f6cc6858b4404961eca761
languageName: node
linkType: hard
"@ai-sdk/anthropic@npm:2.0.22, @ai-sdk/anthropic@npm:^2.0.22":
version: 2.0.22
resolution: "@ai-sdk/anthropic@npm:2.0.22"
"@ai-sdk/anthropic@npm:2.0.27, @ai-sdk/anthropic@npm:^2.0.27":
version: 2.0.27
resolution: "@ai-sdk/anthropic@npm:2.0.27"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/d922d2ff606b2429fb14c099628ba6734ef7c9b0e9225635f3faaf2d067362dea6ae0e920a35c05ccf15a01c59fef93ead5f147a9609dd3dd8c3ac18a3123b85
checksum: 10c0/b568b3b8639af8ec7ea9b766061a4f18bcdef16f2bb12da3a4c4124c751bd6aab1b96dbe1a0eb8e38831d305871ce0787a6536d1a4d8a8ab8aaf03aca3e48e3f
languageName: node
linkType: hard
"@ai-sdk/azure@npm:^2.0.42":
version: 2.0.42
resolution: "@ai-sdk/azure@npm:2.0.42"
"@ai-sdk/azure@npm:^2.0.49":
version: 2.0.49
resolution: "@ai-sdk/azure@npm:2.0.49"
dependencies:
"@ai-sdk/openai": "npm:2.0.42"
"@ai-sdk/openai": "npm:2.0.48"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/14d3d6edac691df57879a9a7efc46d5d00b6bde5b64cd62a67a7668455c341171119ae90a431e57ac37009bced19add50b3da26998376b7e56e080bc2c997c00
checksum: 10c0/d4dc5a8e0cbe0cefc8db987c4a7b784a9898d40cc55ef38618c71eba7f40dbef77b754aec1d507559f643fed49e538ffe2b677b327f001a2efc0474f6b544ba9
languageName: node
linkType: hard
"@ai-sdk/deepseek@npm:^1.0.20":
version: 1.0.20
resolution: "@ai-sdk/deepseek@npm:1.0.20"
"@ai-sdk/deepseek@npm:^1.0.23":
version: 1.0.23
resolution: "@ai-sdk/deepseek@npm:1.0.23"
dependencies:
"@ai-sdk/openai-compatible": "npm:1.0.19"
"@ai-sdk/openai-compatible": "npm:1.0.22"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/e66ece8cf6371c2bac5436ed82cd1e2bb5c367fae6df60090f91cff62bf241f4df0abded99c33558013f8dc0bcc7d962f2126086eba8587ba929da50afd3d806
checksum: 10c0/39736e9787420ce86d0f2ce6935ba51f2b721acfb4c0d2b77064a8b939cf22b0767a83b82a5c99efff1311080532a3aaa2f34804d7981133f671a050521ed197
languageName: node
linkType: hard
"@ai-sdk/gateway@npm:1.0.32":
version: 1.0.32
resolution: "@ai-sdk/gateway@npm:1.0.32"
"@ai-sdk/gateway@npm:1.0.39":
version: 1.0.39
resolution: "@ai-sdk/gateway@npm:1.0.39"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
"@vercel/oidc": "npm:3.0.2"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/82c98db6e4e8e235e1ff66410318ebe77cc1518ebf06d8d4757b4f30aaa3bf7075d3028816438551fef2f89e2d4c8c26e4efcd9913a06717aee1308dad3ddc30
checksum: 10c0/1b6eedf12ac641c96a1eb75e48e43474694b60eb7dca273f76a636a4e2bfc89efda1d9855d5abf9cc464e23cdbf5a3119fed65c3d22cec726e29a2bad3c3318b
languageName: node
linkType: hard
"@ai-sdk/google-vertex@npm:^3.0.33":
version: 3.0.33
resolution: "@ai-sdk/google-vertex@npm:3.0.33"
"@ai-sdk/google-vertex@npm:^3.0.40":
version: 3.0.40
resolution: "@ai-sdk/google-vertex@npm:3.0.40"
dependencies:
"@ai-sdk/anthropic": "npm:2.0.22"
"@ai-sdk/google": "npm:2.0.17"
"@ai-sdk/anthropic": "npm:2.0.27"
"@ai-sdk/google": "npm:2.0.20"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
google-auth-library: "npm:^9.15.0"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/d440e46f702385985a34f2260074eb41cf2516036598039c8c72d6155825114452942c3c012a181da7661341bee9a38958e5f9a53bba145b9c5dc4446411a651
checksum: 10c0/680a06e1b80bc036744e2f13e1a55b57661c3674000ab82b863d6536730edfc3696b1b0b2235f6354de11fa323c4ef817d8edbd2dbf94dc4037ea882e560c9ea
languageName: node
linkType: hard
"@ai-sdk/google@npm:2.0.17":
version: 2.0.17
resolution: "@ai-sdk/google@npm:2.0.17"
"@ai-sdk/google@npm:2.0.20":
version: 2.0.20
resolution: "@ai-sdk/google@npm:2.0.20"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/174bcde507e5bf4bf95f20dbe4eaba73870715b13779e320f3df44995606e4d7ccd1e1f4b759d224deaf58bdfc6aa2e43a24dcbe5fa335ddfe91df1b06114218
checksum: 10c0/9c73bb67061673b16f0996c85bf4e79ab9968c8a203c4f9731bf569e45960db88950dfc227aca69661ea805d381b285697ba1763faa03a38c01b86e6d2e90629
languageName: node
linkType: hard
"@ai-sdk/mistral@npm:^2.0.17":
version: 2.0.17
resolution: "@ai-sdk/mistral@npm:2.0.17"
"@ai-sdk/mistral@npm:^2.0.19":
version: 2.0.19
resolution: "@ai-sdk/mistral@npm:2.0.19"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/58a129357c93cc7f2b15b2ba6ccfb9df3fb72e06163641602ea41c858f835cd76985d66665a56e4ed3fa1eb19ca75a83ae12986d466ec41942e9bf13d558c441
checksum: 10c0/522d1e4631e9318f82f5993030c8fa2a28341e749bc920d32966d91d5cd5a4d1638980b7e0a62601aaaaf7a25e04fefed18b07ce50034c5c5d903ac5bebb65ec
languageName: node
linkType: hard
"@ai-sdk/openai-compatible@npm:1.0.19, @ai-sdk/openai-compatible@npm:^1.0.19":
"@ai-sdk/openai-compatible@npm:1.0.22, @ai-sdk/openai-compatible@npm:^1.0.22":
version: 1.0.22
resolution: "@ai-sdk/openai-compatible@npm:1.0.22"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/31eb07b63eaf07384391e81d824e16589af540f3af2fde1cb24f2a6d04dd07ddb843c9301dbceca78fa5ae5002cb235fc376c41532ab167d1564491526e6011b
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"
dependencies:
@ -191,15 +204,15 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/openai@npm:2.0.42":
version: 2.0.42
resolution: "@ai-sdk/openai@npm:2.0.42"
"@ai-sdk/openai@npm:2.0.48, @ai-sdk/openai@npm:^2.0.48":
version: 2.0.48
resolution: "@ai-sdk/openai@npm:2.0.48"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/b1ab158aafc86735e53c4621ffe125d469bc1732c533193652768a9f66ecd4d169303ce7ca59069b7baf725da49e55bcf81210848f09f66deaf2a8335399e6d7
checksum: 10c0/6c584d7ffb80025da6b7253106a83f8c7a023e8ca322fd32e6858453782d6a0a6d268d7afa7145e3ea743a9c6cbc882932bb59eb1a659750f5205639c414fb49
languageName: node
linkType: hard
@ -215,15 +228,15 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/perplexity@npm:^2.0.11":
version: 2.0.11
resolution: "@ai-sdk/perplexity@npm:2.0.11"
"@ai-sdk/perplexity@npm:^2.0.13":
version: 2.0.13
resolution: "@ai-sdk/perplexity@npm:2.0.13"
dependencies:
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/a8722b68f529b3d1baaa1ba4624c61efe732f22b24dfc20e27afae07bb25d72532bcb62d022191ab5e49df24496af619eabc092a4e6ad293b3fe231ef61b6467
checksum: 10c0/80434eebec088d5f373901f1beb77ca4ba564df5f04dec43c69a7996ea0d88344a3d86ca5e5ef2dc4f5c45f45fc478dabf3a0e44a3faea86a0190c087491a661
languageName: node
linkType: hard
@ -253,6 +266,19 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/provider-utils@npm:3.0.12, @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"
@ -271,16 +297,16 @@ __metadata:
languageName: node
linkType: hard
"@ai-sdk/xai@npm:^2.0.23":
version: 2.0.23
resolution: "@ai-sdk/xai@npm:2.0.23"
"@ai-sdk/xai@npm:^2.0.26":
version: 2.0.26
resolution: "@ai-sdk/xai@npm:2.0.26"
dependencies:
"@ai-sdk/openai-compatible": "npm:1.0.19"
"@ai-sdk/openai-compatible": "npm:1.0.22"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/4cf6b3bc71024797d1b2e37b57fb746f7387f9a7c1da530fd040aad1a840603a1a86fb7df7e428c723eba9b1547f89063d68f84e6e08444d2d4f152dee321dc3
checksum: 10c0/72fef55a96d9c3820de02beb9b63e53902649c5db906a892b7818a984b6e8afe161daa225b8d527b74f783e2c4eecd474af6e96efbb95761aca2c508e0c7c2d9
languageName: node
linkType: hard
@ -2401,14 +2427,14 @@ __metadata:
version: 0.0.0-use.local
resolution: "@cherrystudio/ai-core@workspace:packages/aiCore"
dependencies:
"@ai-sdk/anthropic": "npm:^2.0.22"
"@ai-sdk/azure": "npm:^2.0.42"
"@ai-sdk/deepseek": "npm:^1.0.20"
"@ai-sdk/openai": "npm:^2.0.42"
"@ai-sdk/openai-compatible": "npm:^1.0.19"
"@ai-sdk/anthropic": "npm:^2.0.27"
"@ai-sdk/azure": "npm:^2.0.49"
"@ai-sdk/deepseek": "npm:^1.0.23"
"@ai-sdk/openai": "npm:^2.0.48"
"@ai-sdk/openai-compatible": "npm:^1.0.22"
"@ai-sdk/provider": "npm:^2.0.0"
"@ai-sdk/provider-utils": "npm:^3.0.10"
"@ai-sdk/xai": "npm:^2.0.23"
"@ai-sdk/provider-utils": "npm:^3.0.12"
"@ai-sdk/xai": "npm:^2.0.26"
tsdown: "npm:^0.12.9"
typescript: "npm:^5.0.0"
vitest: "npm:^3.2.4"
@ -13941,6 +13967,13 @@ __metadata:
languageName: node
linkType: hard
"@vercel/oidc@npm:3.0.2":
version: 3.0.2
resolution: "@vercel/oidc@npm:3.0.2"
checksum: 10c0/8d4c8553baa5aed339ab7614d775139bc124a6d443b76877ab17e98c156daa4dbeb3cf2f3bf21fabfae2ac0dd3ff462ab43b9398708e02483e5923d302a1c4c8
languageName: node
linkType: hard
"@vimeo/player@npm:2.29.0":
version: 2.29.0
resolution: "@vimeo/player@npm:2.29.0"
@ -14244,10 +14277,10 @@ __metadata:
"@agentic/exa": "npm:^7.3.3"
"@agentic/searxng": "npm:^7.3.3"
"@agentic/tavily": "npm:^7.3.3"
"@ai-sdk/amazon-bedrock": "npm:^3.0.29"
"@ai-sdk/google-vertex": "npm:^3.0.33"
"@ai-sdk/mistral": "npm:^2.0.17"
"@ai-sdk/perplexity": "npm:^2.0.11"
"@ai-sdk/amazon-bedrock": "npm:^3.0.35"
"@ai-sdk/google-vertex": "npm:^3.0.40"
"@ai-sdk/mistral": "npm:^2.0.19"
"@ai-sdk/perplexity": "npm:^2.0.13"
"@ant-design/v5-patch-for-react-19": "npm:^1.0.3"
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.1#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.1-d937b73fed.patch"
"@anthropic-ai/sdk": "npm:^0.41.0"
@ -14371,7 +14404,7 @@ __metadata:
"@viz-js/lang-dot": "npm:^1.0.5"
"@viz-js/viz": "npm:^3.14.0"
"@xyflow/react": "npm:^12.4.4"
ai: "npm:^5.0.59"
ai: "npm:^5.0.68"
antd: "patch:antd@npm%3A5.27.0#~/.yarn/patches/antd-npm-5.27.0-aa91c36546.patch"
archiver: "npm:^7.0.1"
async-mutex: "npm:^0.5.0"
@ -14633,17 +14666,17 @@ __metadata:
languageName: node
linkType: hard
"ai@npm:^5.0.59":
version: 5.0.59
resolution: "ai@npm:5.0.59"
"ai@npm:^5.0.68":
version: 5.0.68
resolution: "ai@npm:5.0.68"
dependencies:
"@ai-sdk/gateway": "npm:1.0.32"
"@ai-sdk/gateway": "npm:1.0.39"
"@ai-sdk/provider": "npm:2.0.0"
"@ai-sdk/provider-utils": "npm:3.0.10"
"@ai-sdk/provider-utils": "npm:3.0.12"
"@opentelemetry/api": "npm:1.9.0"
peerDependencies:
zod: ^3.25.76 || ^4.1.8
checksum: 10c0/daa956e753b93fbc30afbfba5be2ebb73e3c280dae3064e13949f04d5a22c0f4ea5698cc87e24a23ed6585d9cf7febee61b915292dbbd4286dc40c449cf2b845
checksum: 10c0/0c042cd58c7193a47b06b3074a9e62790c4d5a8134e8e12bbb750714151e9aa217c641ee60c8cbe59d9869bade52ccbb283f9fcbf6d79711ebf1f774fa3feee3
languageName: node
linkType: hard