mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
fix: Groq verbosity setting (#11452)
* feat(settings): show OpenAI settings for supported service tier providers Add support for displaying OpenAI settings when provider supports service tiers. This includes refactoring the condition check and fixing variable naming consistency. * fix(settings): set openAI verbosity to undefined by default * fix(store): bump version to 178 and disable verbosity for groq provider Add migration to remove verbosity from groq provider and implement provider utility to check verbosity support Update provider types to include verbosity support flag * feat(provider): add verbosity option support for providers Add verbosity parameter support in provider API options settings * fix(aiCore): check provider support for verbosity before applying Add provider validation and check for verbosity support to prevent errors when unsupported providers are used with verbosity settings * feat(settings): add Groq settings group component and translations add new GroqSettingsGroup component for managing Groq provider settings update translations for Groq settings in both zh-cn and en-us locales refactor OpenAISettingsGroup to separate Groq-specific logic * feat(i18n): add groq settings and verbosity support translations add translations for groq settings title and verbosity parameter support in multiple languages * refactor(settings): simplify service tier mode fallback logic Remove conditional service tier mode fallback and use provider-specific defaults directly * fix(provider): remove redundant system provider check in verbosity support * test(provider): add tests for verbosity support detection * fix(OpenAISettingsGroup): add endpoint_type check for showSummarySetting condition Add model.endpoint_type check to properly determine when to show summary setting for OpenAI models * refactor(selector): simplify selector option types and add utility functions remove undefined and null from selector option types add utility functions to convert between option values and real values update groq and openai settings groups to use new utilities add new translation for "ignore" option * fix(ApiOptionsSettings): correct checked state for verbosity toggle * feat(i18n): add "ignore" translation for multiple languages * refactor(groq): remove unused model prop and related checks Clean up GroqSettingsGroup component by removing unused model prop and unnecessary service tier checks
This commit is contained in:
parent
69d31a1e2b
commit
e8de31ca64
@ -14,6 +14,7 @@ import {
|
||||
} from '@renderer/config/models'
|
||||
import { mapLanguageToQwenMTModel } from '@renderer/config/translate'
|
||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||
import { getProviderById } from '@renderer/services/ProviderService'
|
||||
import {
|
||||
type Assistant,
|
||||
type GroqServiceTier,
|
||||
@ -31,7 +32,7 @@ import {
|
||||
type ServiceTier
|
||||
} from '@renderer/types'
|
||||
import type { OpenAIVerbosity } from '@renderer/types/aiCoreTypes'
|
||||
import { isSupportServiceTierProvider } from '@renderer/utils/provider'
|
||||
import { isSupportServiceTierProvider, isSupportVerbosityProvider } from '@renderer/utils/provider'
|
||||
import type { JSONValue } from 'ai'
|
||||
import { t } from 'i18next'
|
||||
|
||||
@ -248,8 +249,13 @@ function buildOpenAIProviderOptions(
|
||||
...reasoningParams
|
||||
}
|
||||
}
|
||||
const provider = getProviderById(model.provider)
|
||||
|
||||
if (isSupportVerbosityModel(model)) {
|
||||
if (!provider) {
|
||||
throw new Error(`Provider ${model.provider} not found`)
|
||||
}
|
||||
|
||||
if (isSupportVerbosityModel(model) && isSupportVerbosityProvider(provider)) {
|
||||
const openAI = getStoreSetting<'openAI'>('openAI')
|
||||
const userVerbosity = openAI?.verbosity
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
interface SelectorOption<V = string | number | undefined | null> {
|
||||
interface SelectorOption<V = string | number> {
|
||||
label: string | ReactNode
|
||||
value: V
|
||||
type?: 'group'
|
||||
@ -14,7 +14,7 @@ interface SelectorOption<V = string | number | undefined | null> {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface BaseSelectorProps<V = string | number | undefined | null> {
|
||||
interface BaseSelectorProps<V = string | number> {
|
||||
options: SelectorOption<V>[]
|
||||
placeholder?: string
|
||||
placement?: 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight' | 'top' | 'bottom'
|
||||
@ -39,7 +39,7 @@ interface MultipleSelectorProps<V> extends BaseSelectorProps<V> {
|
||||
|
||||
export type SelectorProps<V> = SingleSelectorProps<V> | MultipleSelectorProps<V>
|
||||
|
||||
const Selector = <V extends string | number | undefined | null>({
|
||||
const Selector = <V extends string | number>({
|
||||
options,
|
||||
value,
|
||||
onChange = () => {},
|
||||
|
||||
@ -19,6 +19,7 @@ export function isSupportFlexServiceTierModel(model: Model): boolean {
|
||||
(modelId.includes('o3') && !modelId.includes('o3-mini')) || modelId.includes('o4-mini') || modelId.includes('gpt-5')
|
||||
)
|
||||
}
|
||||
|
||||
export function isSupportedFlexServiceTier(model: Model): boolean {
|
||||
return isSupportFlexServiceTierModel(model)
|
||||
}
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "Entered fullscreen mode. Press F11 to exit",
|
||||
"go_to_settings": "Go to settings",
|
||||
"i_know": "I know",
|
||||
"ignore": "Ignore",
|
||||
"inspect": "Inspect",
|
||||
"invalid_value": "Invalid Value",
|
||||
"knowledge_base": "Knowledge Base",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "View WebDAV settings"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Groq Settings"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "Disabling hardware acceleration requires restarting the app to take effect. Do you want to restart now?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "Does the provider support the stream_options parameter?",
|
||||
"label": "Support stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "Whether the provider supports the verbosity parameter",
|
||||
"label": "Support verbosity"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "已进入全屏模式,按 F11 退出",
|
||||
"go_to_settings": "前往设置",
|
||||
"i_know": "我知道了",
|
||||
"ignore": "忽略",
|
||||
"inspect": "检查",
|
||||
"invalid_value": "无效值",
|
||||
"knowledge_base": "知识库",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "查看 WebDAV 设置"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Groq 设置"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "禁用硬件加速需要重启应用才能生效,是否现在重启?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "该提供商是否支持 stream_options 参数",
|
||||
"label": "支持 stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "该提供商是否支持 verbosity 参数",
|
||||
"label": "支持 verbosity"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "已進入全螢幕模式,按 F11 結束",
|
||||
"go_to_settings": "前往設定",
|
||||
"i_know": "我知道了",
|
||||
"ignore": "忽略",
|
||||
"inspect": "檢查",
|
||||
"invalid_value": "無效值",
|
||||
"knowledge_base": "知識庫",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "檢視 WebDAV 設定"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Groq 設定"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "禁用硬件加速需要重新啟動應用程序才能生效。是否立即重新啟動?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "該提供商是否支援 stream_options 參數",
|
||||
"label": "支援 stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "提供者是否支援詳細程度參數",
|
||||
"label": "支援詳細資訊"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "Vollbildmodus aktiviert, F11 zum Beenden",
|
||||
"go_to_settings": "Zu Einstellungen",
|
||||
"i_know": "Verstanden",
|
||||
"ignore": "Ignorieren",
|
||||
"inspect": "Prüfen",
|
||||
"invalid_value": "Ungültiger Wert",
|
||||
"knowledge_base": "Wissensdatenbank",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "WebDAV-Einstellungen anzeigen"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Groq Einstellungen"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "Deaktivierung der Hardwarebeschleunigung erfordert Neustart. Jetzt neu starten?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "Unterstützt stream_options",
|
||||
"label": "Unterstützt stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "Ob der Anbieter den Ausführlichkeitsparameter unterstützt",
|
||||
"label": "Unterstützung der Ausführlichkeit"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "Εισήχθη σε πλήρη οθόνη, πατήστε F11 για να έξω",
|
||||
"go_to_settings": "Πηγαίνετε στις ρυθμίσεις",
|
||||
"i_know": "Το έχω καταλάβει",
|
||||
"ignore": "Αγνόησε",
|
||||
"inspect": "Επιθεώρηση",
|
||||
"invalid_value": "Μη έγκυρη τιμή",
|
||||
"knowledge_base": "Βάση Γνώσεων",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "Προβολή ρυθμίσεων WebDAV"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Ρυθμίσεις Groq"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "Η απενεργοποίηση της υλικοποιημένης επιτάχυνσης απαιτεί επανεκκίνηση της εφαρμογής για να τεθεί σε ισχύ. Θέλετε να επανεκκινήσετε τώρα;",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "Υποστηρίζει ο πάροχος την παράμετρο stream_options;",
|
||||
"label": "Υποστήριξη stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "Αν ο πάροχος υποστηρίζει την παράμετρο αναλυτικότητας",
|
||||
"label": "Υποστήριξη πολυλογίας"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "En modo pantalla completa, presione F11 para salir",
|
||||
"go_to_settings": "Ir a la configuración",
|
||||
"i_know": "Entendido",
|
||||
"ignore": "Ignorar",
|
||||
"inspect": "Inspeccionar",
|
||||
"invalid_value": "Valor inválido",
|
||||
"knowledge_base": "Base de conocimiento",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "Ver configuración WebDAV"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Configuración de Groq"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "La desactivación de la aceleración por hardware requiere reiniciar la aplicación para que surta efecto, ¿desea reiniciar ahora?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "¿Admite el proveedor el parámetro stream_options?",
|
||||
"label": "Admite stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "Si el proveedor admite el parámetro de verbosidad",
|
||||
"label": "Soporte de verbosidad"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "Mode plein écran, appuyez sur F11 pour quitter",
|
||||
"go_to_settings": "Aller aux paramètres",
|
||||
"i_know": "J'ai compris",
|
||||
"ignore": "Ignorer",
|
||||
"inspect": "Vérifier",
|
||||
"invalid_value": "valeur invalide",
|
||||
"knowledge_base": "Base de connaissances",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "Voir les paramètres WebDAV"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Paramètres Groq"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "La désactivation de l'accélération matérielle nécessite un redémarrage de l'application pour prendre effet. Voulez-vous redémarrer maintenant ?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "Le fournisseur prend-il en charge le paramètre stream_options ?",
|
||||
"label": "Prise en charge des options de flux"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "Si le fournisseur prend en charge le paramètre de verbosité",
|
||||
"label": "Prend en charge la verbosité"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "全画面モードに入りました。F11キーで終了します",
|
||||
"go_to_settings": "設定に移動",
|
||||
"i_know": "わかりました",
|
||||
"ignore": "無視",
|
||||
"inspect": "検査",
|
||||
"invalid_value": "無効な値",
|
||||
"knowledge_base": "ナレッジベース",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "WebDAV設定を表示"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Groq設定"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "ハードウェアアクセラレーションを無効にするには、アプリを再起動する必要があります。再起動しますか?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "このプロバイダーは stream_options パラメータをサポートしていますか",
|
||||
"label": "stream_options をサポート"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "プロバイダーが冗長度パラメータをサポートしているかどうか",
|
||||
"label": "冗長性のサポート"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "Entrou no modo de tela cheia, pressione F11 para sair",
|
||||
"go_to_settings": "Ir para configurações",
|
||||
"i_know": "Entendi",
|
||||
"ignore": "Pular",
|
||||
"inspect": "Verificar",
|
||||
"invalid_value": "Valor inválido",
|
||||
"knowledge_base": "Base de Conhecimento",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "Ver configurações WebDAV"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Configurações do Groq"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "A desativação da aceleração de hardware requer a reinicialização do aplicativo para entrar em vigor. Deseja reiniciar agora?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "O fornecedor suporta o parâmetro stream_options?",
|
||||
"label": "suporta stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "Se o provedor suporta o parâmetro de verbosidade",
|
||||
"label": "Suportar verbosidade"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
"fullscreen": "Вы вошли в полноэкранный режим. Нажмите F11 для выхода",
|
||||
"go_to_settings": "Перейти в настройки",
|
||||
"i_know": "Я понял",
|
||||
"ignore": "Игнорировать",
|
||||
"inspect": "Осмотреть",
|
||||
"invalid_value": "недопустимое значение",
|
||||
"knowledge_base": "База знаний",
|
||||
@ -3771,6 +3772,9 @@
|
||||
},
|
||||
"view_webdav_settings": "Просмотр настроек WebDAV"
|
||||
},
|
||||
"groq": {
|
||||
"title": "Настройки Groq"
|
||||
},
|
||||
"hardware_acceleration": {
|
||||
"confirm": {
|
||||
"content": "Отключение аппаратного ускорения требует перезапуска приложения для вступления в силу. Перезапустить приложение?",
|
||||
@ -4357,6 +4361,10 @@
|
||||
"stream_options": {
|
||||
"help": "Поддерживает ли этот провайдер параметр stream_options",
|
||||
"label": "Поддержка stream_options"
|
||||
},
|
||||
"verbosity": {
|
||||
"help": "Поддерживает ли провайдер параметр verbosity",
|
||||
"label": "Поддержка многословности"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
|
||||
@ -53,9 +53,10 @@ import {
|
||||
setThoughtAutoCollapse
|
||||
} from '@renderer/store/settings'
|
||||
import type { Assistant, AssistantSettings, CodeStyleVarious, MathEngine } from '@renderer/types'
|
||||
import { ThemeMode } from '@renderer/types'
|
||||
import { isGroqSystemProvider, ThemeMode } from '@renderer/types'
|
||||
import { modalConfirm } from '@renderer/utils'
|
||||
import { getSendMessageShortcutLabel } from '@renderer/utils/input'
|
||||
import { isSupportServiceTierProvider } from '@renderer/utils/provider'
|
||||
import { Button, Col, InputNumber, Row, Slider, Switch } from 'antd'
|
||||
import { Settings2 } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
@ -63,6 +64,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import GroqSettingsGroup from './components/GroqSettingsGroup'
|
||||
import OpenAISettingsGroup from './components/OpenAISettingsGroup'
|
||||
|
||||
interface Props {
|
||||
@ -181,7 +183,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
|
||||
const model = assistant.model || getDefaultModel()
|
||||
|
||||
const isOpenAI = isOpenAIModel(model)
|
||||
const showOpenAiSettings = isOpenAIModel(model) || isSupportServiceTierProvider(provider)
|
||||
|
||||
return (
|
||||
<Container className="settings-tab">
|
||||
@ -332,7 +334,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
</SettingGroup>
|
||||
</CollapsibleSettingGroup>
|
||||
)}
|
||||
{isOpenAI && (
|
||||
{showOpenAiSettings && (
|
||||
<OpenAISettingsGroup
|
||||
model={model}
|
||||
providerId={provider.id}
|
||||
@ -340,6 +342,9 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
SettingRowTitleSmall={SettingRowTitleSmall}
|
||||
/>
|
||||
)}
|
||||
{isGroqSystemProvider(provider) && (
|
||||
<GroqSettingsGroup SettingGroup={SettingGroup} SettingRowTitleSmall={SettingRowTitleSmall} />
|
||||
)}
|
||||
<CollapsibleSettingGroup title={t('settings.messages.title')} defaultExpanded={true}>
|
||||
<SettingGroup>
|
||||
<SettingRow>
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
import Selector from '@renderer/components/Selector'
|
||||
import { useProvider } from '@renderer/hooks/useProvider'
|
||||
import { SettingDivider, SettingRow } from '@renderer/pages/settings'
|
||||
import { CollapsibleSettingGroup } from '@renderer/pages/settings/SettingGroup'
|
||||
import type { GroqServiceTier, ServiceTier } from '@renderer/types'
|
||||
import { SystemProviderIds } from '@renderer/types'
|
||||
import { toOptionValue, toRealValue } from '@renderer/utils/select'
|
||||
import { Tooltip } from 'antd'
|
||||
import { CircleHelp } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type ServiceTierOptions = { value: NonNullable<GroqServiceTier> | 'undefined'; label: string }
|
||||
|
||||
interface Props {
|
||||
SettingGroup: FC<{ children: React.ReactNode }>
|
||||
SettingRowTitleSmall: FC<{ children: React.ReactNode }>
|
||||
}
|
||||
|
||||
const GroqSettingsGroup: FC<Props> = ({ SettingGroup, SettingRowTitleSmall }) => {
|
||||
const { t } = useTranslation()
|
||||
const { provider, updateProvider } = useProvider(SystemProviderIds.groq)
|
||||
const serviceTierMode = provider.serviceTier
|
||||
|
||||
const setServiceTierMode = useCallback(
|
||||
(value: ServiceTier) => {
|
||||
updateProvider({ serviceTier: value })
|
||||
},
|
||||
[updateProvider]
|
||||
)
|
||||
|
||||
const serviceTierOptions = useMemo(() => {
|
||||
const options = [
|
||||
{
|
||||
value: 'undefined',
|
||||
label: t('common.ignore')
|
||||
},
|
||||
{
|
||||
value: 'auto',
|
||||
label: t('settings.openai.service_tier.auto')
|
||||
},
|
||||
{
|
||||
value: 'on_demand',
|
||||
label: t('settings.openai.service_tier.on_demand')
|
||||
},
|
||||
{
|
||||
value: 'flex',
|
||||
label: t('settings.openai.service_tier.flex')
|
||||
}
|
||||
] as const satisfies ServiceTierOptions[]
|
||||
return options
|
||||
}, [t])
|
||||
|
||||
return (
|
||||
<CollapsibleSettingGroup title={t('settings.groq.title')} defaultExpanded={true}>
|
||||
<SettingGroup>
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('settings.openai.service_tier.title')}{' '}
|
||||
<Tooltip title={t('settings.openai.service_tier.tip')}>
|
||||
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<Selector
|
||||
value={toOptionValue(serviceTierMode)}
|
||||
onChange={(value) => {
|
||||
setServiceTierMode(toRealValue(value))
|
||||
}}
|
||||
options={serviceTierOptions}
|
||||
/>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
<SettingDivider />
|
||||
</CollapsibleSettingGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default GroqSettingsGroup
|
||||
@ -11,10 +11,11 @@ import { CollapsibleSettingGroup } from '@renderer/pages/settings/SettingGroup'
|
||||
import type { RootState } from '@renderer/store'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setOpenAISummaryText, setOpenAIVerbosity } from '@renderer/store/settings'
|
||||
import type { GroqServiceTier, Model, OpenAIServiceTier, ServiceTier } from '@renderer/types'
|
||||
import { GroqServiceTiers, OpenAIServiceTiers, SystemProviderIds } from '@renderer/types'
|
||||
import type { Model, OpenAIServiceTier, ServiceTier } from '@renderer/types'
|
||||
import { SystemProviderIds } from '@renderer/types'
|
||||
import type { OpenAISummaryText, OpenAIVerbosity } from '@renderer/types/aiCoreTypes'
|
||||
import { isSupportServiceTierProvider } from '@renderer/utils/provider'
|
||||
import { isSupportServiceTierProvider, isSupportVerbosityProvider } from '@renderer/utils/provider'
|
||||
import { toOptionValue, toRealValue } from '@renderer/utils/select'
|
||||
import { Tooltip } from 'antd'
|
||||
import { CircleHelp } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
@ -23,19 +24,16 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
type VerbosityOption = {
|
||||
value: OpenAIVerbosity
|
||||
value: NonNullable<OpenAIVerbosity> | 'undefined'
|
||||
label: string
|
||||
}
|
||||
|
||||
type SummaryTextOption = {
|
||||
value: OpenAISummaryText
|
||||
value: NonNullable<OpenAISummaryText> | 'undefined'
|
||||
label: string
|
||||
}
|
||||
|
||||
type OpenAIServiceTierOption = { value: OpenAIServiceTier; label: string }
|
||||
type GroqServiceTierOption = { value: GroqServiceTier; label: string }
|
||||
|
||||
type ServiceTierOptions = OpenAIServiceTierOption[] | GroqServiceTierOption[]
|
||||
type OpenAIServiceTierOption = { value: NonNullable<OpenAIServiceTier> | 'null' | 'undefined'; label: string }
|
||||
|
||||
interface Props {
|
||||
model: Model
|
||||
@ -52,13 +50,14 @@ const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, Setti
|
||||
const serviceTierMode = provider.serviceTier
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const isOpenAIReasoning =
|
||||
const showSummarySetting =
|
||||
isSupportedReasoningEffortOpenAIModel(model) &&
|
||||
!model.id.includes('o1-pro') &&
|
||||
(provider.type === 'openai-response' || provider.id === 'aihubmix')
|
||||
const isSupportVerbosity = isSupportVerbosityModel(model)
|
||||
(provider.type === 'openai-response' || model.endpoint_type === 'openai-response' || provider.id === 'aihubmix')
|
||||
const showVerbositySetting = isSupportVerbosityModel(model) && isSupportVerbosityProvider(provider)
|
||||
const isSupportFlexServiceTier = isSupportFlexServiceTierModel(model)
|
||||
const isSupportServiceTier = isSupportServiceTierProvider(provider)
|
||||
const isSupportedFlexServiceTier = isSupportFlexServiceTierModel(model)
|
||||
const showServiceTierSetting = isSupportServiceTier && providerId !== SystemProviderIds.groq
|
||||
|
||||
const setSummaryText = useCallback(
|
||||
(value: OpenAISummaryText) => {
|
||||
@ -83,8 +82,8 @@ const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, Setti
|
||||
|
||||
const summaryTextOptions = [
|
||||
{
|
||||
value: undefined,
|
||||
label: t('common.default')
|
||||
value: 'undefined',
|
||||
label: t('common.ignore')
|
||||
},
|
||||
{
|
||||
value: 'auto',
|
||||
@ -103,8 +102,8 @@ const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, Setti
|
||||
const verbosityOptions = useMemo(() => {
|
||||
const allOptions = [
|
||||
{
|
||||
value: undefined,
|
||||
label: t('common.default')
|
||||
value: 'undefined',
|
||||
label: t('common.ignore')
|
||||
},
|
||||
{
|
||||
value: 'low',
|
||||
@ -119,73 +118,44 @@ const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, Setti
|
||||
label: t('settings.openai.verbosity.high')
|
||||
}
|
||||
] as const satisfies VerbosityOption[]
|
||||
const supportedVerbosityLevels = getModelSupportedVerbosity(model)
|
||||
const supportedVerbosityLevels = getModelSupportedVerbosity(model).map((v) => toOptionValue(v))
|
||||
return allOptions.filter((option) => supportedVerbosityLevels.includes(option.value))
|
||||
}, [model, t])
|
||||
|
||||
const serviceTierOptions = useMemo(() => {
|
||||
let options: ServiceTierOptions
|
||||
if (provider.id === SystemProviderIds.groq) {
|
||||
options = [
|
||||
{
|
||||
value: null,
|
||||
label: t('common.off')
|
||||
},
|
||||
{
|
||||
value: undefined,
|
||||
label: t('common.default')
|
||||
},
|
||||
{
|
||||
value: 'auto',
|
||||
label: t('settings.openai.service_tier.auto')
|
||||
},
|
||||
{
|
||||
value: 'on_demand',
|
||||
label: t('settings.openai.service_tier.on_demand')
|
||||
},
|
||||
{
|
||||
value: 'flex',
|
||||
label: t('settings.openai.service_tier.flex')
|
||||
}
|
||||
] as const satisfies GroqServiceTierOption[]
|
||||
} else {
|
||||
// 其他情况默认是和 OpenAI 相同
|
||||
options = [
|
||||
{
|
||||
value: 'auto',
|
||||
label: t('settings.openai.service_tier.auto')
|
||||
},
|
||||
{
|
||||
value: 'default',
|
||||
label: t('settings.openai.service_tier.default')
|
||||
},
|
||||
{
|
||||
value: 'flex',
|
||||
label: t('settings.openai.service_tier.flex')
|
||||
},
|
||||
{
|
||||
value: 'priority',
|
||||
label: t('settings.openai.service_tier.priority')
|
||||
}
|
||||
] as const satisfies OpenAIServiceTierOption[]
|
||||
}
|
||||
const options = [
|
||||
{
|
||||
value: 'undefined',
|
||||
label: t('common.ignore')
|
||||
},
|
||||
{
|
||||
value: 'null',
|
||||
label: t('common.off')
|
||||
},
|
||||
{
|
||||
value: 'auto',
|
||||
label: t('settings.openai.service_tier.auto')
|
||||
},
|
||||
{
|
||||
value: 'default',
|
||||
label: t('settings.openai.service_tier.default')
|
||||
},
|
||||
{
|
||||
value: 'flex',
|
||||
label: t('settings.openai.service_tier.flex')
|
||||
},
|
||||
{
|
||||
value: 'priority',
|
||||
label: t('settings.openai.service_tier.priority')
|
||||
}
|
||||
] as const satisfies OpenAIServiceTierOption[]
|
||||
return options.filter((option) => {
|
||||
if (option.value === 'flex') {
|
||||
return isSupportedFlexServiceTier
|
||||
return isSupportFlexServiceTier
|
||||
}
|
||||
return true
|
||||
})
|
||||
}, [isSupportedFlexServiceTier, provider.id, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (serviceTierMode && !serviceTierOptions.some((option) => option.value === serviceTierMode)) {
|
||||
if (provider.id === SystemProviderIds.groq) {
|
||||
setServiceTierMode(GroqServiceTiers.on_demand)
|
||||
} else {
|
||||
setServiceTierMode(OpenAIServiceTiers.auto)
|
||||
}
|
||||
}
|
||||
}, [provider.id, serviceTierMode, serviceTierOptions, setServiceTierMode])
|
||||
}, [isSupportFlexServiceTier, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (verbosity && !verbosityOptions.some((option) => option.value === verbosity)) {
|
||||
@ -196,14 +166,14 @@ const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, Setti
|
||||
}
|
||||
}, [model, verbosity, verbosityOptions, setVerbosity])
|
||||
|
||||
if (!isOpenAIReasoning && !isSupportServiceTier && !isSupportVerbosity) {
|
||||
if (!showSummarySetting && !showServiceTierSetting && !showVerbositySetting) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<CollapsibleSettingGroup title={t('settings.openai.title')} defaultExpanded={true}>
|
||||
<SettingGroup>
|
||||
{isSupportServiceTier && (
|
||||
{showServiceTierSetting && (
|
||||
<>
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
@ -213,18 +183,17 @@ const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, Setti
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<Selector
|
||||
value={serviceTierMode}
|
||||
value={toOptionValue(serviceTierMode)}
|
||||
onChange={(value) => {
|
||||
setServiceTierMode(value as OpenAIServiceTier)
|
||||
setServiceTierMode(toRealValue(value))
|
||||
}}
|
||||
options={serviceTierOptions}
|
||||
placeholder={t('settings.openai.service_tier.auto')}
|
||||
/>
|
||||
</SettingRow>
|
||||
{(isOpenAIReasoning || isSupportVerbosity) && <SettingDivider />}
|
||||
{(showSummarySetting || showVerbositySetting) && <SettingDivider />}
|
||||
</>
|
||||
)}
|
||||
{isOpenAIReasoning && (
|
||||
{showSummarySetting && (
|
||||
<>
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
@ -241,10 +210,10 @@ const OpenAISettingsGroup: FC<Props> = ({ model, providerId, SettingGroup, Setti
|
||||
options={summaryTextOptions}
|
||||
/>
|
||||
</SettingRow>
|
||||
{isSupportVerbosity && <SettingDivider />}
|
||||
{showVerbositySetting && <SettingDivider />}
|
||||
</>
|
||||
)}
|
||||
{isSupportVerbosity && (
|
||||
{showVerbositySetting && (
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('settings.openai.verbosity.title')}{' '}
|
||||
|
||||
@ -76,6 +76,17 @@ const ApiOptionsSettings = ({ providerId }: Props) => {
|
||||
})
|
||||
},
|
||||
checked: !provider.apiOptions?.isNotSupportEnableThinking
|
||||
},
|
||||
{
|
||||
key: 'openai_verbosity',
|
||||
label: t('settings.provider.api.options.verbosity.label'),
|
||||
tip: t('settings.provider.api.options.verbosity.help'),
|
||||
onChange: (checked: boolean) => {
|
||||
updateProviderTransition({
|
||||
apiOptions: { ...provider.apiOptions, isNotSupportVerbosity: !checked }
|
||||
})
|
||||
},
|
||||
checked: !provider.apiOptions?.isNotSupportVerbosity
|
||||
}
|
||||
],
|
||||
[t, provider, updateProviderTransition]
|
||||
|
||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 177,
|
||||
version: 178,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -2871,6 +2871,19 @@ const migrateConfig = {
|
||||
logger.error('migrate 177 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'178': (state: RootState) => {
|
||||
try {
|
||||
const groq = state.llm.providers.find((p) => p.id === SystemProviderIds.groq)
|
||||
if (groq) {
|
||||
groq.verbosity = undefined
|
||||
}
|
||||
logger.info('migrate 178 success')
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 178 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -376,7 +376,7 @@ export const initialState: SettingsState = {
|
||||
openAI: {
|
||||
summaryText: 'auto',
|
||||
serviceTier: 'auto',
|
||||
verbosity: 'medium'
|
||||
verbosity: undefined
|
||||
},
|
||||
notification: {
|
||||
assistant: false,
|
||||
|
||||
@ -20,28 +20,30 @@ export const ProviderTypeSchema = z.enum([
|
||||
|
||||
export type ProviderType = z.infer<typeof ProviderTypeSchema>
|
||||
|
||||
// undefined 视为支持,默认支持
|
||||
// undefined is treated as supported, enabled by default
|
||||
export type ProviderApiOptions = {
|
||||
/** 是否不支持 message 的 content 为数组类型 */
|
||||
/** Whether message content of array type is not supported */
|
||||
isNotSupportArrayContent?: boolean
|
||||
/** 是否不支持 stream_options 参数 */
|
||||
/** Whether the stream_options parameter is not supported */
|
||||
isNotSupportStreamOptions?: boolean
|
||||
/**
|
||||
* @deprecated
|
||||
* 是否不支持 message 的 role 为 developer */
|
||||
* Whether message role 'developer' is not supported */
|
||||
isNotSupportDeveloperRole?: boolean
|
||||
/* 是否支持 message 的 role 为 developer */
|
||||
/* Whether message role 'developer' is supported */
|
||||
isSupportDeveloperRole?: boolean
|
||||
/**
|
||||
* @deprecated
|
||||
* 是否不支持 service_tier 参数. Only for OpenAI Models. */
|
||||
* Whether the service_tier parameter is not supported. Only for OpenAI Models. */
|
||||
isNotSupportServiceTier?: boolean
|
||||
/* 是否支持 service_tier 参数. Only for OpenAI Models. */
|
||||
/* Whether the service_tier parameter is supported. Only for OpenAI Models. */
|
||||
isSupportServiceTier?: boolean
|
||||
/** 是否不支持 enable_thinking 参数 */
|
||||
/** Whether the enable_thinking parameter is not supported */
|
||||
isNotSupportEnableThinking?: boolean
|
||||
/** 是否不支持 APIVersion */
|
||||
/** Whether APIVersion is not supported */
|
||||
isNotSupportAPIVersion?: boolean
|
||||
/** Whether verbosity is not supported. For OpenAI API (completions & responses). */
|
||||
isNotSupportVerbosity?: boolean
|
||||
}
|
||||
|
||||
// scale is not well supported now. It even lacks of docs
|
||||
@ -61,6 +63,7 @@ export function isOpenAIServiceTier(tier: string | null | undefined): tier is Op
|
||||
}
|
||||
|
||||
// https://console.groq.com/docs/api-reference#responses
|
||||
// null is not used.
|
||||
export type GroqServiceTier = 'auto' | 'on_demand' | 'flex' | undefined | null
|
||||
|
||||
export const GroqServiceTiers = {
|
||||
|
||||
@ -19,7 +19,8 @@ import {
|
||||
isSupportEnableThinkingProvider,
|
||||
isSupportServiceTierProvider,
|
||||
isSupportStreamOptionsProvider,
|
||||
isSupportUrlContextProvider
|
||||
isSupportUrlContextProvider,
|
||||
isSupportVerbosityProvider
|
||||
} from '../provider'
|
||||
|
||||
vi.mock('@renderer/store/settings', () => ({
|
||||
@ -104,6 +105,35 @@ describe('provider utils', () => {
|
||||
expect(isSupportServiceTierProvider(createSystemProvider({ id: SystemProviderIds.github }))).toBe(false)
|
||||
})
|
||||
|
||||
it('determines verbosity support', () => {
|
||||
// Custom providers with explicit flag
|
||||
expect(isSupportVerbosityProvider(createProvider({ apiOptions: { isNotSupportVerbosity: false } }))).toBe(true)
|
||||
expect(isSupportVerbosityProvider(createProvider({ apiOptions: { isNotSupportVerbosity: true } }))).toBe(false)
|
||||
|
||||
// Custom providers without apiOptions (should support by default)
|
||||
expect(isSupportVerbosityProvider(createProvider())).toBe(true)
|
||||
expect(isSupportVerbosityProvider(createProvider({ apiOptions: {} }))).toBe(true)
|
||||
|
||||
// System providers that support verbosity (default behavior)
|
||||
expect(isSupportVerbosityProvider(createSystemProvider())).toBe(true)
|
||||
expect(isSupportVerbosityProvider(createSystemProvider({ id: SystemProviderIds.openai }))).toBe(true)
|
||||
|
||||
// System providers in the NOT_SUPPORT_VERBOSITY_PROVIDERS list (cannot be overridden by apiOptions)
|
||||
expect(isSupportVerbosityProvider(createSystemProvider({ id: SystemProviderIds.groq }))).toBe(false)
|
||||
expect(
|
||||
isSupportVerbosityProvider(
|
||||
createSystemProvider({ id: SystemProviderIds.groq, apiOptions: { isNotSupportVerbosity: false } })
|
||||
)
|
||||
).toBe(false)
|
||||
|
||||
// apiOptions can disable verbosity for any provider
|
||||
expect(
|
||||
isSupportVerbosityProvider(
|
||||
createSystemProvider({ id: SystemProviderIds.openai, apiOptions: { isNotSupportVerbosity: true } })
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('detects URL context capable providers', () => {
|
||||
expect(isSupportUrlContextProvider(createProvider({ type: 'gemini' }))).toBe(true)
|
||||
expect(
|
||||
|
||||
@ -83,6 +83,21 @@ export const isSupportServiceTierProvider = (provider: Provider) => {
|
||||
)
|
||||
}
|
||||
|
||||
const NOT_SUPPORT_VERBOSITY_PROVIDERS = ['groq'] as const satisfies SystemProviderId[]
|
||||
|
||||
/**
|
||||
* Determines whether the provider supports the verbosity option.
|
||||
* Only applies to system providers that are not in the exclusion list.
|
||||
* @param provider - The provider to check
|
||||
* @returns true if the provider supports verbosity, false otherwise
|
||||
*/
|
||||
export const isSupportVerbosityProvider = (provider: Provider) => {
|
||||
return (
|
||||
provider.apiOptions?.isNotSupportVerbosity !== true &&
|
||||
!NOT_SUPPORT_VERBOSITY_PROVIDERS.some((pid) => pid === provider.id)
|
||||
)
|
||||
}
|
||||
|
||||
const SUPPORT_URL_CONTEXT_PROVIDER_TYPES = [
|
||||
'gemini',
|
||||
'vertexai',
|
||||
|
||||
36
src/renderer/src/utils/select.ts
Normal file
36
src/renderer/src/utils/select.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Convert a value (string | undefined | null) into an option-compatible string.
|
||||
* - `undefined` becomes the literal string `'undefined'`
|
||||
* - `null` becomes the literal string `'null'`
|
||||
* - Any other string is returned as-is
|
||||
*
|
||||
* @param v - The value to convert
|
||||
* @returns The string representation safe for option usage
|
||||
*/
|
||||
export function toOptionValue<T extends undefined | Exclude<string, null>>(v: T): NonNullable<T> | 'undefined'
|
||||
export function toOptionValue<T extends null | Exclude<string, undefined>>(v: T): NonNullable<T> | 'null'
|
||||
export function toOptionValue<T extends string | undefined | null>(v: T): NonNullable<T> | 'undefined' | 'null'
|
||||
export function toOptionValue<T extends Exclude<string, null | undefined>>(v: T): T
|
||||
export function toOptionValue(v: string | undefined | null) {
|
||||
if (v === undefined) return 'undefined'
|
||||
if (v === null) return 'null'
|
||||
return v
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an option string back to its original value.
|
||||
* - The literal string `'undefined'` becomes `undefined`
|
||||
* - The literal string `'null'` becomes `null`
|
||||
* - Any other string is returned as-is
|
||||
*
|
||||
* @param v - The option string to convert
|
||||
* @returns The real value (`undefined`, `null`, or the original string)
|
||||
*/
|
||||
export function toRealValue<T extends 'undefined'>(v: T): undefined
|
||||
export function toRealValue<T extends 'null'>(v: T): null
|
||||
export function toRealValue<T extends string>(v: T): Exclude<T, 'undefined' | 'null'>
|
||||
export function toRealValue(v: string) {
|
||||
if (v === 'undefined') return undefined
|
||||
if (v === 'null') return null
|
||||
return v
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user