diff --git a/README.md b/README.md index 98a8bdafab..763ff5e542 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ The Enterprise Edition addresses core challenges in team collaboration by centra | Feature | Community Edition | Enterprise Edition | | :---------------- | :----------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | -| **Open Source** | ✅ Yes | ⭕️ Partially released to customers | +| **Open Source** | ✅ Yes | ⭕️ Partially released to customers | | **Cost** | Free for Personal Use / Commercial License | Buyout / Subscription Fee | | **Admin Backend** | — | ● Centralized **Model** Access
● **Employee** Management
● Shared **Knowledge Base**
● **Access** Control
● **Data** Backup | | **Server** | — | ✅ Dedicated Private Deployment | diff --git a/docs/technical/how-to-use-logger-en.md b/docs/technical/how-to-use-logger-en.md index d208b7c55f..4af08d25ed 100644 --- a/docs/technical/how-to-use-logger-en.md +++ b/docs/technical/how-to-use-logger-en.md @@ -145,8 +145,8 @@ In a development environment, you can define environment variables to filter dis Environment variables can be set in the terminal or defined in the `.env` file in the project's root directory. The available variables are as follows: -| Variable Name | Description | -| ------- | ------- | +| Variable Name | Description | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | CSLOGGER_MAIN_LEVEL | Log level for the `main` process. Logs below this level will not be displayed. | | CSLOGGER_MAIN_SHOW_MODULES | Filters log modules for the `main` process. Use a comma (`,`) to separate modules. The filter is case-sensitive. Only logs from modules in this list will be displayed. | | CSLOGGER_RENDERER_LEVEL | Log level for the `renderer` process. Logs below this level will not be displayed. | @@ -160,6 +160,7 @@ CSLOGGER_MAIN_SHOW_MODULES=MCPService,SelectionService ``` Note: + - Environment variables are only effective in the development environment. - These variables only affect the logs displayed in the terminal or DevTools. They do not affect file logging or the `logToMain` recording logic. @@ -168,7 +169,7 @@ Note: There are many log levels. The following are the guidelines that should be followed in CherryStudio for when to use each level: (Arranged from highest to lowest log level) -| Log Level | Core Definition & Use case | Example | +| Log Level | Core Definition & Use case | Example | | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`error`** | **Critical error causing the program to crash or core functionality to become unusable.**
This is the highest-priority log, usually requiring immediate reporting or user notification. | - Main or renderer process crash.
- Failure to read/write critical user data files (e.g., database, configuration files), preventing the application from running.
- All unhandled exceptions. | | **`warn`** | **Potential issue or unexpected situation that does not affect the program's core functionality.**
The program can recover or use a fallback. | - Configuration file `settings.json` is missing; started with default settings.
- Auto-update check failed, but does not affect the use of the current version.
- A non-essential plugin failed to load. | diff --git a/docs/technical/how-to-use-logger-zh.md b/docs/technical/how-to-use-logger-zh.md index 9082027b93..04cf80fe0d 100644 --- a/docs/technical/how-to-use-logger-zh.md +++ b/docs/technical/how-to-use-logger-zh.md @@ -145,12 +145,12 @@ const logger = loggerService.initWindowSource('Worker').withContext('LetsWork') 环境变量可以在终端中自行设置,或者在开发根目录的`.env`文件中进行定义,可以定义的变量如下: -| 变量名 | 含义 | -| ----- | ----- | -| CSLOGGER_MAIN_LEVEL | 用于`main`进程的日志级别,低于该级别的日志将不显示 | -| CSLOGGER_MAIN_SHOW_MODULES | 用于`main`进程的日志module筛选,用`,`分隔,区分大小写。只有在该列表中的module的日志才会显示 | -| CSLOGGER_RENDERER_LEVEL | 用于`renderer`进程的日志级别,低于该级别的日志将不显示 | -| CSLOGGER_RENDERER_SHOW_MODULES | 用于`renderer`进程的日志module筛选,用`,`分隔,区分大小写。只有在该列表中的module的日志才会显示 | +| 变量名 | 含义 | +| ------------------------------ | ----------------------------------------------------------------------------------------------- | +| CSLOGGER_MAIN_LEVEL | 用于`main`进程的日志级别,低于该级别的日志将不显示 | +| CSLOGGER_MAIN_SHOW_MODULES | 用于`main`进程的日志module筛选,用`,`分隔,区分大小写。只有在该列表中的module的日志才会显示 | +| CSLOGGER_RENDERER_LEVEL | 用于`renderer`进程的日志级别,低于该级别的日志将不显示 | +| CSLOGGER_RENDERER_SHOW_MODULES | 用于`renderer`进程的日志module筛选,用`,`分隔,区分大小写。只有在该列表中的module的日志才会显示 | 示例: @@ -160,6 +160,7 @@ CSLOGGER_MAIN_SHOW_MODULES=MCPService,SelectionService ``` 注意: + - 环境变量仅在开发环境中生效 - 该变量仅会改变在终端或在devTools中显示的日志,不会影响文件日志和`logToMain`的记录逻辑 diff --git a/src/renderer/src/components/ModelList/EditModelsPopup.tsx b/src/renderer/src/components/ModelList/EditModelsPopup.tsx index ecd8694d85..9431a9c2e1 100644 --- a/src/renderer/src/components/ModelList/EditModelsPopup.tsx +++ b/src/renderer/src/components/ModelList/EditModelsPopup.tsx @@ -23,7 +23,13 @@ import { useProvider } from '@renderer/hooks/useProvider' import FileItem from '@renderer/pages/files/FileItem' import { fetchModels } from '@renderer/services/ApiService' import { Model, Provider } from '@renderer/types' -import { getDefaultGroupName, isFreeModel, runAsyncFunction } from '@renderer/utils' +import { + filterModelsByKeywords, + getDefaultGroupName, + getFancyProviderName, + isFreeModel, + runAsyncFunction +} from '@renderer/utils' import { Avatar, Button, Empty, Flex, Modal, Spin, Tabs, Tooltip } from 'antd' import Input from 'antd/es/input/Input' import { groupBy, isEmpty, uniqBy } from 'lodash' @@ -86,34 +92,30 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { const systemModels = SYSTEM_MODELS[_provider.id] || [] const allModels = uniqBy([...systemModels, ...listModels, ...models], 'id') - const list = allModels.filter((model) => { - if ( - filterSearchText && - !model.id.toLocaleLowerCase().includes(filterSearchText.toLocaleLowerCase()) && - !model.name?.toLocaleLowerCase().includes(filterSearchText.toLocaleLowerCase()) - ) { - return false - } - - switch (actualFilterType) { - case 'reasoning': - return isReasoningModel(model) - case 'vision': - return isVisionModel(model) - case 'websearch': - return isWebSearchModel(model) - case 'free': - return isFreeModel(model) - case 'embedding': - return isEmbeddingModel(model) - case 'function_calling': - return isFunctionCallingModel(model) - case 'rerank': - return isRerankModel(model) - default: - return true - } - }) + const list = useMemo( + () => + filterModelsByKeywords(filterSearchText, allModels).filter((model) => { + switch (actualFilterType) { + case 'reasoning': + return isReasoningModel(model) + case 'vision': + return isVisionModel(model) + case 'websearch': + return isWebSearchModel(model) + case 'free': + return isFreeModel(model) + case 'embedding': + return isEmbeddingModel(model) + case 'function_calling': + return isFunctionCallingModel(model) + case 'rerank': + return isRerankModel(model) + default: + return true + } + }), + [filterSearchText, actualFilterType, allModels] + ) const modelGroups = useMemo( () => @@ -202,7 +204,7 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { return ( - {provider.isSystem ? t(`provider.${provider.id}`) : provider.name} + {getFancyProviderName(provider)} {i18n.language.startsWith('zh') ? '' : ' '} {t('common.models')} diff --git a/src/renderer/src/components/ModelList/ModelList.tsx b/src/renderer/src/components/ModelList/ModelList.tsx index fa629c0222..b9c54d0c9f 100644 --- a/src/renderer/src/components/ModelList/ModelList.tsx +++ b/src/renderer/src/components/ModelList/ModelList.tsx @@ -12,6 +12,7 @@ import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } import { useAppDispatch } from '@renderer/store' import { setModel } from '@renderer/store/assistants' import { Model } from '@renderer/types' +import { filterModelsByKeywords } from '@renderer/utils' import { Button, Flex, Tooltip } from 'antd' import { groupBy, sortBy, toPairs } from 'lodash' import { ListCheck, Plus } from 'lucide-react' @@ -51,9 +52,7 @@ const ModelList: React.FC = ({ providerId }) => { }, []) const modelGroups = useMemo(() => { - const filteredModels = searchText - ? models.filter((model) => model.name.toLowerCase().includes(searchText.toLowerCase())) - : models + const filteredModels = searchText ? filterModelsByKeywords(searchText, models) : models return groupBy(filteredModels, 'group') }, [searchText, models]) diff --git a/src/renderer/src/components/ModelSelector.tsx b/src/renderer/src/components/ModelSelector.tsx new file mode 100644 index 0000000000..f8b1a37ca9 --- /dev/null +++ b/src/renderer/src/components/ModelSelector.tsx @@ -0,0 +1,123 @@ +import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' +import { getModelUniqId } from '@renderer/services/ModelService' +import { Model, Provider } from '@renderer/types' +import { matchKeywordsInString } from '@renderer/utils' +import { getFancyProviderName } from '@renderer/utils/naming' +import { Select, SelectProps } from 'antd' +import { sortBy } from 'lodash' +import { BaseSelectRef } from 'rc-select' +import { memo, useCallback, useMemo } from 'react' + +interface ModelOption { + label: React.ReactNode + title: string + value: string +} + +interface GroupedModelOption { + label: string + title: string + options: ModelOption[] +} + +type SelectOption = ModelOption | GroupedModelOption + +interface ModelSelectorProps extends SelectProps { + providers?: Provider[] + predicate?: (model: Model) => boolean + grouped?: boolean + showAvatar?: boolean + showSuffix?: boolean +} + +/** + * 模型选择器,封装了 antd Select + * - 通过传入模型服务商列表和模型 predicate 来构造选项 + * - 支持按服务商分组 + * - 可以控制 avatar 和 suffix 显示与否 + * @param providers 服务商列表 + * @param predicate 模型过滤条件 + * @param grouped 是否按服务商分组 + * @param showAvatar 是否显示模型图标 + * @param showSuffix 是否在模型名称后显示服务商作为后缀 + */ +const ModelSelector = ({ + providers, + predicate, + grouped = true, + showAvatar = true, + showSuffix = true, + ref, + ...props +}: ModelSelectorProps & { ref?: React.Ref | null }) => { + // 单个 provider 的模型选项 + const getModelOptions = useCallback( + (p: Provider, fancyName: string) => { + const suffix = showSuffix ? {` | ${fancyName}`} : null + return sortBy(p.models, 'name') + .filter((model) => predicate?.(model) ?? true) + .map((m) => ({ + label: ( +
+ {showAvatar && } + + {m.name} + {suffix} + +
+ ), + title: `${m.name} | ${fancyName}`, + value: getModelUniqId(m) + })) + }, + [predicate, showAvatar, showSuffix] + ) + + // 所有 provider 的模型选项 + const options = useMemo((): SelectOption[] => { + if (!providers) return [] + + if (grouped) { + return providers.flatMap((p) => { + const fancyName = getFancyProviderName(p) + const modelOptions = getModelOptions(p, fancyName) + return modelOptions.length > 0 + ? [ + { + label: fancyName, + title: p.name, + options: modelOptions + } as GroupedModelOption + ] + : [] + }) + } + return providers.flatMap((p) => getModelOptions(p, getFancyProviderName(p))) + }, [providers, grouped, getModelOptions]) + + return { const model = value @@ -313,9 +279,10 @@ const PopupContainer: React.FC = ({ title, resolve }) => { - = ({ base: _base, resolve }) => { - + - + - setDefaultModel(find(allModels, JSON.parse(value)) as Model)} - options={selectOptions} - showSearch placeholder={t('settings.models.empty')} />