mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 18:10:26 +08:00
feat(icons): add new lightbulb icons and update reasoning effort settings in translations
- Introduced new SVG icons for lightbulb states (off, on at 10%, 50%, and 90%). - Added "Off" reasoning effort option in English, Japanese, Russian, Simplified Chinese, and Traditional Chinese translations. - Refactored Inputbar and ThinkingButton components to integrate new reasoning effort logic and icon display.
This commit is contained in:
parent
9120136f45
commit
d618cdf19e
@ -11,3 +11,47 @@ export const StreamlineGoodHealthAndWellBeing = (props: SVGProps<SVGSVGElement>)
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function MdiLightbulbOffOutline(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||||
|
{/* Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE */}
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12 2C9.76 2 7.78 3.05 6.5 4.68l1.43 1.43C8.84 4.84 10.32 4 12 4a5 5 0 0 1 5 5c0 1.68-.84 3.16-2.11 4.06l1.42 1.44C17.94 13.21 19 11.24 19 9a7 7 0 0 0-7-7M3.28 4L2 5.27L5.04 8.3C5 8.53 5 8.76 5 9c0 2.38 1.19 4.47 3 5.74V17a1 1 0 0 0 1 1h5.73l4 4L20 20.72zm3.95 6.5l5.5 5.5H10v-2.42a5 5 0 0 1-2.77-3.08M9 20v1a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-1z"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MdiLightbulbOn10(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||||
|
{/* Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE */}
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M1 11h3v2H1zm18.1-7.5L17 5.6L18.4 7l2.1-2.1zM11 1h2v3h-2zM4.9 3.5L3.5 4.9L5.6 7L7 5.6zM10 22c0 .6.4 1 1 1h2c.6 0 1-.4 1-1v-1h-4zm2-16c-3.3 0-6 2.7-6 6c0 2.2 1.2 4.2 3 5.2V19c0 .6.4 1 1 1h4c.6 0 1-.4 1-1v-1.8c1.8-1 3-3 3-5.2c0-3.3-2.7-6-6-6m1 9.9V17h-2v-1.1c-1.7-.4-3-2-3-3.9c0-2.2 1.8-4 4-4s4 1.8 4 4c0 1.9-1.3 3.4-3 3.9m7-4.9h3v2h-3z"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MdiLightbulbOn50(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||||
|
{/* Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE */}
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M1 11h3v2H1zm9 11c0 .6.4 1 1 1h2c.6 0 1-.4 1-1v-1h-4zm3-21h-2v3h2zM4.9 3.5L3.5 4.9L5.6 7L7 5.6zM20 11v2h3v-2zm-.9-7.5L17 5.6L18.4 7l2.1-2.1zM18 12c0 2.2-1.2 4.2-3 5.2V19c0 .6-.4 1-1 1h-4c-.6 0-1-.4-1-1v-1.8c-1.8-1-3-3-3-5.2c0-3.3 2.7-6 6-6s6 2.7 6 6M8 12c0 .35.05.68.14 1h7.72c.09-.32.14-.65.14-1c0-2.21-1.79-4-4-4s-4 1.79-4 4"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MdiLightbulbOn90(props: SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||||
|
{/* Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE */}
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M7 5.6L5.6 7L3.5 4.9l1.4-1.4zM10 22c0 .6.4 1 1 1h2c.6 0 1-.4 1-1v-1h-4zm-9-9h3v-2H1zM13 1h-2v3h2zm7 10v2h3v-2zm-.9-7.5L17 5.6L18.4 7l2.1-2.1zM18 12c0 2.2-1.2 4.2-3 5.2V19c0 .6-.4 1-1 1h-4c-.6 0-1-.4-1-1v-1.8c-1.8-1-3-3-3-5.2c0-3.3 2.7-6 6-6s6 2.7 6 6m-6-4c-1 0-1.91.38-2.61 1h5.22C13.91 8.38 13 8 12 8"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
"settings.preset_messages": "Preset Messages",
|
"settings.preset_messages": "Preset Messages",
|
||||||
"settings.prompt": "Prompt Settings",
|
"settings.prompt": "Prompt Settings",
|
||||||
"settings.reasoning_effort": "Reasoning effort",
|
"settings.reasoning_effort": "Reasoning effort",
|
||||||
|
"settings.reasoning_effort.off": "Off",
|
||||||
"settings.reasoning_effort.high": "Think harder",
|
"settings.reasoning_effort.high": "Think harder",
|
||||||
"settings.reasoning_effort.low": "Think less",
|
"settings.reasoning_effort.low": "Think less",
|
||||||
"settings.reasoning_effort.medium": "Think normally",
|
"settings.reasoning_effort.medium": "Think normally",
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
"settings.preset_messages": "プリセットメッセージ",
|
"settings.preset_messages": "プリセットメッセージ",
|
||||||
"settings.prompt": "プロンプト設定",
|
"settings.prompt": "プロンプト設定",
|
||||||
"settings.reasoning_effort": "思考連鎖の長さ",
|
"settings.reasoning_effort": "思考連鎖の長さ",
|
||||||
|
"settings.reasoning_effort.off": "オフ",
|
||||||
"settings.reasoning_effort.high": "最大限の思考",
|
"settings.reasoning_effort.high": "最大限の思考",
|
||||||
"settings.reasoning_effort.low": "少しの思考",
|
"settings.reasoning_effort.low": "少しの思考",
|
||||||
"settings.reasoning_effort.medium": "普通の思考",
|
"settings.reasoning_effort.medium": "普通の思考",
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
"settings.model": "Настройки модели",
|
"settings.model": "Настройки модели",
|
||||||
"settings.preset_messages": "Предустановленные сообщения",
|
"settings.preset_messages": "Предустановленные сообщения",
|
||||||
"settings.prompt": "Настройки промптов",
|
"settings.prompt": "Настройки промптов",
|
||||||
|
"settings.reasoning_effort.off": "Выключить",
|
||||||
"settings.reasoning_effort.high": "Стараюсь думать",
|
"settings.reasoning_effort.high": "Стараюсь думать",
|
||||||
"settings.reasoning_effort.low": "Меньше думать",
|
"settings.reasoning_effort.low": "Меньше думать",
|
||||||
"settings.reasoning_effort.medium": "Среднее",
|
"settings.reasoning_effort.medium": "Среднее",
|
||||||
|
|||||||
@ -56,9 +56,10 @@
|
|||||||
"settings.preset_messages": "预设消息",
|
"settings.preset_messages": "预设消息",
|
||||||
"settings.prompt": "提示词设置",
|
"settings.prompt": "提示词设置",
|
||||||
"settings.reasoning_effort": "思维链长度",
|
"settings.reasoning_effort": "思维链长度",
|
||||||
"settings.reasoning_effort.low": "稍微思考",
|
"settings.reasoning_effort.off": "关闭",
|
||||||
"settings.reasoning_effort.medium": "正常思考",
|
"settings.reasoning_effort.low": "浮想",
|
||||||
"settings.reasoning_effort.high": "尽力思考",
|
"settings.reasoning_effort.medium": "斟酌",
|
||||||
|
"settings.reasoning_effort.high": "沉思",
|
||||||
"settings.more": "助手设置"
|
"settings.more": "助手设置"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
"settings.preset_messages": "預設訊息",
|
"settings.preset_messages": "預設訊息",
|
||||||
"settings.prompt": "提示詞設定",
|
"settings.prompt": "提示詞設定",
|
||||||
"settings.reasoning_effort": "思維鏈長度",
|
"settings.reasoning_effort": "思維鏈長度",
|
||||||
|
"settings.reasoning_effort.off": "關閉",
|
||||||
"settings.reasoning_effort.high": "盡力思考",
|
"settings.reasoning_effort.high": "盡力思考",
|
||||||
"settings.reasoning_effort.low": "稍微思考",
|
"settings.reasoning_effort.low": "稍微思考",
|
||||||
"settings.reasoning_effort.medium": "正常思考",
|
"settings.reasoning_effort.medium": "正常思考",
|
||||||
|
|||||||
@ -1,13 +1,7 @@
|
|||||||
import { HolderOutlined } from '@ant-design/icons'
|
import { HolderOutlined } from '@ant-design/icons'
|
||||||
import { QuickPanelListItem, QuickPanelView, useQuickPanel } from '@renderer/components/QuickPanel'
|
import { QuickPanelListItem, QuickPanelView, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||||
import ThinkingPanel from '@renderer/components/ThinkingPanel'
|
|
||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import {
|
import { isGenerateImageModel, isReasoningModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||||
isGenerateImageModel,
|
|
||||||
isSupportedReasoningEffortModel,
|
|
||||||
isVisionModel,
|
|
||||||
isWebSearchModel
|
|
||||||
} from '@renderer/config/models'
|
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
|
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
|
||||||
@ -59,7 +53,6 @@ import React, { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useS
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { isSupportedThinkingTokenModel } from '../../../config/models'
|
|
||||||
import NarrowLayout from '../Messages/NarrowLayout'
|
import NarrowLayout from '../Messages/NarrowLayout'
|
||||||
import AttachmentButton, { AttachmentButtonRef } from './AttachmentButton'
|
import AttachmentButton, { AttachmentButtonRef } from './AttachmentButton'
|
||||||
import AttachmentPreview from './AttachmentPreview'
|
import AttachmentPreview from './AttachmentPreview'
|
||||||
@ -72,7 +65,7 @@ import MentionModelsInput from './MentionModelsInput'
|
|||||||
import NewContextButton from './NewContextButton'
|
import NewContextButton from './NewContextButton'
|
||||||
import QuickPhrasesButton, { QuickPhrasesButtonRef } from './QuickPhrasesButton'
|
import QuickPhrasesButton, { QuickPhrasesButtonRef } from './QuickPhrasesButton'
|
||||||
import SendMessageButton from './SendMessageButton'
|
import SendMessageButton from './SendMessageButton'
|
||||||
import ThinkingButton from './ThinkingButton'
|
import ThinkingButton, { ThinkingButtonRef } from './ThinkingButton'
|
||||||
import TokenCount from './TokenCount'
|
import TokenCount from './TokenCount'
|
||||||
import WebSearchButton, { WebSearchButtonRef } from './WebSearchButton'
|
import WebSearchButton, { WebSearchButtonRef } from './WebSearchButton'
|
||||||
|
|
||||||
@ -118,7 +111,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
|
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
|
||||||
const [mentionModels, setMentionModels] = useState<Model[]>([])
|
const [mentionModels, setMentionModels] = useState<Model[]>([])
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [thinkingPanelVisible, setThinkingPanelVisible] = useState(false)
|
|
||||||
const [textareaHeight, setTextareaHeight] = useState<number>()
|
const [textareaHeight, setTextareaHeight] = useState<number>()
|
||||||
const startDragY = useRef<number>(0)
|
const startDragY = useRef<number>(0)
|
||||||
const startHeight = useRef<number>(0)
|
const startHeight = useRef<number>(0)
|
||||||
@ -139,7 +131,8 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
const knowledgeBaseButtonRef = useRef<KnowledgeBaseButtonRef>(null)
|
const knowledgeBaseButtonRef = useRef<KnowledgeBaseButtonRef>(null)
|
||||||
const mcpToolsButtonRef = useRef<MCPToolsButtonRef>(null)
|
const mcpToolsButtonRef = useRef<MCPToolsButtonRef>(null)
|
||||||
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
||||||
const webSearchButtonRef = useRef<WebSearchButtonRef>(null)
|
const webSearchButtonRef = useRef<WebSearchButtonRef | null>(null)
|
||||||
|
const thinkingButtonRef = useRef<ThinkingButtonRef | null>(null)
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const debouncedEstimate = useCallback(
|
const debouncedEstimate = useCallback(
|
||||||
@ -770,9 +763,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
|
|
||||||
const textareaRows = window.innerHeight >= 1000 || isBubbleStyle ? 2 : 1
|
const textareaRows = window.innerHeight >= 1000 || isBubbleStyle ? 2 : 1
|
||||||
|
|
||||||
const isSupportedReasoningEffort = isSupportedReasoningEffortModel(model)
|
|
||||||
const isSupportedThinkingToken = isSupportedThinkingTokenModel(model)
|
|
||||||
|
|
||||||
const handleKnowledgeBaseSelect = (bases?: KnowledgeBase[]) => {
|
const handleKnowledgeBaseSelect = (bases?: KnowledgeBase[]) => {
|
||||||
updateAssistant({ ...assistant, knowledge_bases: bases })
|
updateAssistant({ ...assistant, knowledge_bases: bases })
|
||||||
setSelectedKnowledgeBases(bases ?? [])
|
setSelectedKnowledgeBases(bases ?? [])
|
||||||
@ -795,21 +785,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
updateAssistant({ ...assistant, enableGenerateImage: !assistant.enableGenerateImage })
|
updateAssistant({ ...assistant, enableGenerateImage: !assistant.enableGenerateImage })
|
||||||
}
|
}
|
||||||
|
|
||||||
const onToggleThinking = () => {
|
|
||||||
const newEnableThinking = !assistant.enableThinking
|
|
||||||
updateAssistant({ ...assistant, enableThinking: newEnableThinking })
|
|
||||||
|
|
||||||
if (!newEnableThinking) {
|
|
||||||
setThinkingPanelVisible(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTogglePanel = () => {
|
|
||||||
if (isSupportedThinkingToken || isSupportedReasoningEffort) {
|
|
||||||
setThinkingPanelVisible((prev) => !prev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isWebSearchModel(model) && assistant.enableWebSearch) {
|
if (!isWebSearchModel(model) && assistant.enableWebSearch) {
|
||||||
updateAssistant({ ...assistant, enableWebSearch: false })
|
updateAssistant({ ...assistant, enableWebSearch: false })
|
||||||
@ -945,21 +920,14 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
setFiles={setFiles}
|
setFiles={setFiles}
|
||||||
ToolbarButton={ToolbarButton}
|
ToolbarButton={ToolbarButton}
|
||||||
/>
|
/>
|
||||||
<ThinkingButtonContainer>
|
{isReasoningModel(model) && (
|
||||||
{thinkingPanelVisible && (
|
|
||||||
<ThinkingPanelContainer>
|
|
||||||
<ThinkingPanel model={model} assistant={assistant} />
|
|
||||||
</ThinkingPanelContainer>
|
|
||||||
)}
|
|
||||||
<ThinkingButton
|
<ThinkingButton
|
||||||
|
ref={thinkingButtonRef}
|
||||||
model={model}
|
model={model}
|
||||||
assistant={assistant}
|
assistant={assistant}
|
||||||
ToolbarButton={ToolbarButton}
|
ToolbarButton={ToolbarButton}
|
||||||
onToggleThinking={onToggleThinking}
|
|
||||||
onTogglePanel={onTogglePanel}
|
|
||||||
showPanel={thinkingPanelVisible}
|
|
||||||
/>
|
/>
|
||||||
</ThinkingButtonContainer>
|
)}
|
||||||
<WebSearchButton ref={webSearchButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />
|
<WebSearchButton ref={webSearchButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />
|
||||||
{showKnowledgeIcon && (
|
{showKnowledgeIcon && (
|
||||||
<KnowledgeBaseButton
|
<KnowledgeBaseButton
|
||||||
@ -1155,25 +1123,4 @@ const ToolbarButton = styled(Button)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const ThinkingPanelContainer = styled.div`
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
background-color: var(--color-background);
|
|
||||||
border: 0.5px solid var(--color-border);
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
|
||||||
min-width: 250px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const ThinkingButtonContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default Inputbar
|
export default Inputbar
|
||||||
|
|||||||
@ -1,72 +1,245 @@
|
|||||||
import {
|
import {
|
||||||
isReasoningModel,
|
MdiLightbulbOffOutline,
|
||||||
|
MdiLightbulbOn10,
|
||||||
|
MdiLightbulbOn50,
|
||||||
|
MdiLightbulbOn90
|
||||||
|
} from '@renderer/components/Icons/SVGIcon'
|
||||||
|
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
|
||||||
|
import {
|
||||||
|
isSupportedReasoningEffortGrokModel,
|
||||||
isSupportedReasoningEffortModel,
|
isSupportedReasoningEffortModel,
|
||||||
isSupportedThinkingTokenModel
|
isSupportedThinkingTokenModel
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { Assistant, Model } from '@renderer/types'
|
import { Assistant, Model } from '@renderer/types'
|
||||||
import { Tooltip } from 'antd'
|
import { Tooltip } from 'antd'
|
||||||
import { Atom, ChevronDown, ChevronUp } from 'lucide-react'
|
import { FC, ReactElement, useCallback, useImperativeHandle, useMemo } from 'react'
|
||||||
import { FC } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
export type ReasoningEffortOptions = 'low' | 'medium' | 'high'
|
||||||
|
|
||||||
|
const THINKING_TOKEN_MAP: Record<string, { min: number; max: number }> = {
|
||||||
|
// Gemini models
|
||||||
|
'gemini-.*$': { min: 0, max: 24576 },
|
||||||
|
|
||||||
|
// Qwen models
|
||||||
|
'qwen-plus-.*$': { min: 0, max: 38912 },
|
||||||
|
'qwen-turbo-.*$': { min: 0, max: 38912 },
|
||||||
|
'qwen3-0\\.6b$': { min: 0, max: 30720 },
|
||||||
|
'qwen3-1\\.7b$': { min: 0, max: 30720 },
|
||||||
|
'qwen3-.*$': { min: 0, max: 38912 },
|
||||||
|
|
||||||
|
// Claude models
|
||||||
|
'claude-3[.-]7.*sonnet.*$': { min: 0, max: 64000 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to find matching token limit
|
||||||
|
const findTokenLimit = (modelId: string): { min: number; max: number } | undefined => {
|
||||||
|
for (const [pattern, limits] of Object.entries(THINKING_TOKEN_MAP)) {
|
||||||
|
if (new RegExp(pattern).test(modelId)) {
|
||||||
|
return limits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据模型和选择的思考档位计算thinking_budget值
|
||||||
|
const calculateThinkingBudget = (model: Model, option: ReasoningEffortOptions | null): number | undefined => {
|
||||||
|
if (!option || !isSupportedThinkingTokenModel(model)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenLimits = findTokenLimit(model.id)
|
||||||
|
if (!tokenLimits) return undefined
|
||||||
|
|
||||||
|
const { min, max } = tokenLimits
|
||||||
|
|
||||||
|
switch (option) {
|
||||||
|
case 'low':
|
||||||
|
return Math.floor(min + (max - min) * 0.25)
|
||||||
|
case 'medium':
|
||||||
|
return Math.floor(min + (max - min) * 0.5)
|
||||||
|
case 'high':
|
||||||
|
return Math.floor(min + (max - min) * 0.75)
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThinkingButtonRef {
|
||||||
|
openQuickPanel: () => void
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
ref?: React.RefObject<ThinkingButtonRef | null>
|
||||||
model: Model
|
model: Model
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
ToolbarButton: any
|
ToolbarButton: any
|
||||||
onToggleThinking: () => void
|
|
||||||
onTogglePanel: () => void
|
|
||||||
showPanel: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThinkingButton: FC<Props> = ({ model, assistant, ToolbarButton, onToggleThinking, onTogglePanel, showPanel }) => {
|
const ThinkingButton: FC<Props> = ({ ref, model, assistant, ToolbarButton }): ReactElement => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const quickPanel = useQuickPanel()
|
||||||
|
const { updateAssistantSettings } = useAssistant(assistant.id)
|
||||||
|
|
||||||
|
const supportedThinkingToken = isSupportedThinkingTokenModel(model)
|
||||||
|
const supportedReasoningEffort = isSupportedReasoningEffortModel(model)
|
||||||
|
const isGrokModel = isSupportedReasoningEffortGrokModel(model)
|
||||||
|
|
||||||
|
// 根据thinking_budget逆推思考档位
|
||||||
|
const inferReasoningEffortFromBudget = useCallback(
|
||||||
|
(model: Model, budget: number | undefined): ReasoningEffortOptions | null => {
|
||||||
|
if (!budget || !supportedThinkingToken) return null
|
||||||
|
|
||||||
|
const tokenLimits = findTokenLimit(model.id)
|
||||||
|
if (!tokenLimits) return null
|
||||||
|
|
||||||
|
const { min, max } = tokenLimits
|
||||||
|
const range = max - min
|
||||||
|
|
||||||
|
// 计算预算在范围内的百分比
|
||||||
|
const normalizedBudget = (budget - min) / range
|
||||||
|
|
||||||
|
// 根据百分比确定档位
|
||||||
|
if (normalizedBudget <= 0.33) return 'low'
|
||||||
|
if (normalizedBudget <= 0.66) return 'medium'
|
||||||
|
return 'high'
|
||||||
|
},
|
||||||
|
[supportedThinkingToken]
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentReasoningEffort = useMemo(() => {
|
||||||
|
// 优先使用显式设置的reasoning_effort
|
||||||
|
if (assistant.settings?.reasoning_effort) {
|
||||||
|
return assistant.settings.reasoning_effort
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有thinking_budget但没有reasoning_effort,则推导档位
|
||||||
|
if (assistant.settings?.thinking_budget) {
|
||||||
|
return inferReasoningEffortFromBudget(model, assistant.settings.thinking_budget)
|
||||||
|
}
|
||||||
|
|
||||||
if (!isReasoningModel(model)) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}, [assistant.settings?.reasoning_effort, assistant.settings?.thinking_budget, inferReasoningEffortFromBudget, model])
|
||||||
const isSupportedThinkingToken = isSupportedThinkingTokenModel(model)
|
|
||||||
const isSupportedReasoningEffort = isSupportedReasoningEffortModel(model)
|
const createThinkingIcon = useCallback((option: ReasoningEffortOptions | null, isActive: boolean = false) => {
|
||||||
|
const iconColor = isActive ? 'var(--color-link)' : 'var(--color-icon)'
|
||||||
|
|
||||||
|
switch (true) {
|
||||||
|
case option === 'low':
|
||||||
|
return <MdiLightbulbOn10 width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
|
||||||
|
case option === 'medium':
|
||||||
|
return <MdiLightbulbOn50 width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
|
||||||
|
case option === 'high':
|
||||||
|
return <MdiLightbulbOn90 width={18} height={18} style={{ color: iconColor, marginTop: -2 }} />
|
||||||
|
default:
|
||||||
|
return <MdiLightbulbOffOutline width={18} height={18} style={{ color: iconColor }} />
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onThinkingChange = useCallback(
|
||||||
|
(option: ReasoningEffortOptions | null) => {
|
||||||
|
if (!option) {
|
||||||
|
// 禁用思考
|
||||||
|
updateAssistantSettings({
|
||||||
|
reasoning_effort: undefined,
|
||||||
|
thinking_budget: undefined
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启用思考
|
||||||
|
if (supportedReasoningEffort) {
|
||||||
|
updateAssistantSettings({
|
||||||
|
reasoning_effort: option
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportedThinkingToken) {
|
||||||
|
const budget = calculateThinkingBudget(model, option)
|
||||||
|
updateAssistantSettings({
|
||||||
|
reasoning_effort: option,
|
||||||
|
thinking_budget: budget
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[model, supportedReasoningEffort, supportedThinkingToken, updateAssistantSettings]
|
||||||
|
)
|
||||||
|
|
||||||
|
const baseOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
level: null,
|
||||||
|
label: t('assistants.settings.reasoning_effort.off'),
|
||||||
|
description: '',
|
||||||
|
icon: createThinkingIcon(null),
|
||||||
|
isSelected: currentReasoningEffort === null,
|
||||||
|
action: () => onThinkingChange(null)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 'low',
|
||||||
|
label: t('assistants.settings.reasoning_effort.low'),
|
||||||
|
description: '',
|
||||||
|
icon: createThinkingIcon('low'),
|
||||||
|
isSelected: currentReasoningEffort === 'low',
|
||||||
|
action: () => onThinkingChange('low')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 'medium',
|
||||||
|
label: t('assistants.settings.reasoning_effort.medium'),
|
||||||
|
description: '',
|
||||||
|
icon: createThinkingIcon('medium'),
|
||||||
|
isSelected: currentReasoningEffort === 'medium',
|
||||||
|
action: () => onThinkingChange('medium')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: 'high',
|
||||||
|
label: t('assistants.settings.reasoning_effort.high'),
|
||||||
|
description: '',
|
||||||
|
icon: createThinkingIcon('high'),
|
||||||
|
isSelected: currentReasoningEffort === 'high',
|
||||||
|
action: () => onThinkingChange('high')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[currentReasoningEffort, onThinkingChange, t, createThinkingIcon]
|
||||||
|
)
|
||||||
|
|
||||||
|
const panelItems = useMemo<QuickPanelListItem[]>(() => {
|
||||||
|
return isGrokModel ? baseOptions.filter((option) => option.level === 'low' || option.level === 'high') : baseOptions
|
||||||
|
}, [baseOptions, isGrokModel])
|
||||||
|
|
||||||
|
const openQuickPanel = useCallback(() => {
|
||||||
|
quickPanel.open({
|
||||||
|
title: t('chat.input.thinking'),
|
||||||
|
list: panelItems,
|
||||||
|
symbol: 'thinking'
|
||||||
|
})
|
||||||
|
}, [quickPanel, panelItems, t])
|
||||||
|
|
||||||
|
const handleOpenQuickPanel = useCallback(() => {
|
||||||
|
if (quickPanel.isVisible && quickPanel.symbol === 'thinking') {
|
||||||
|
quickPanel.close()
|
||||||
|
} else {
|
||||||
|
openQuickPanel()
|
||||||
|
}
|
||||||
|
}, [openQuickPanel, quickPanel])
|
||||||
|
|
||||||
|
// 获取当前应显示的图标
|
||||||
|
const getThinkingIcon = useCallback(() => {
|
||||||
|
return createThinkingIcon(currentReasoningEffort, currentReasoningEffort !== null)
|
||||||
|
}, [createThinkingIcon, currentReasoningEffort])
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
openQuickPanel
|
||||||
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip placement="top" title={t('chat.input.thinking')} arrow>
|
<Tooltip placement="top" title={t('assistants.settings.reasoning_effort')} arrow>
|
||||||
<ButtonContainer>
|
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
|
||||||
<ToolbarButton type="text" disabled={!isReasoningModel(model)} onClick={onToggleThinking}>
|
{getThinkingIcon()}
|
||||||
<Atom size={18} color={assistant.enableThinking ? 'var(--color-link)' : 'var(--color-icon)'} />
|
</ToolbarButton>
|
||||||
</ToolbarButton>
|
|
||||||
<ChevronButton onClick={onTogglePanel} disabled={!isSupportedThinkingToken && !isSupportedReasoningEffort}>
|
|
||||||
{showPanel ? (
|
|
||||||
<ChevronUp size={18} color={assistant.enableThinking ? 'var(--color-link)' : 'var(--color-icon)'} />
|
|
||||||
) : (
|
|
||||||
<ChevronDown size={18} color={assistant.enableThinking ? 'var(--color-link)' : 'var(--color-icon)'} />
|
|
||||||
)}
|
|
||||||
</ChevronButton>
|
|
||||||
</ButtonContainer>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const ChevronButton = styled.button`
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-icon);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-left: -8px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export default ThinkingButton
|
export default ThinkingButton
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user