tool setting support variable

This commit is contained in:
JzoNg 2025-02-08 17:11:24 +08:00
parent fec3bb4469
commit 26838eb42a
11 changed files with 455 additions and 49 deletions

View File

@ -5,7 +5,7 @@ import classNames from '@/utils/classnames'
type SwitchProps = {
onChange?: (value: boolean) => void
size?: 'sm' | 'md' | 'lg' | 'l'
size?: 'xs' | 'sm' | 'md' | 'lg' | 'l'
defaultValue?: boolean
disabled?: boolean
className?: string
@ -23,6 +23,7 @@ const Switch = React.forwardRef(
l: 'h-5 w-9',
md: 'h-4 w-7',
sm: 'h-3 w-5',
xs: 'h-2.5 w-3.5',
}
const circleStyle = {
@ -30,6 +31,7 @@ const Switch = React.forwardRef(
l: 'h-4 w-4',
md: 'h-3 w-3',
sm: 'h-2 w-2',
xs: 'h-1.5 w-1',
}
const translateLeft = {
@ -37,6 +39,7 @@ const Switch = React.forwardRef(
l: 'translate-x-4',
md: 'translate-x-3',
sm: 'translate-x-2',
xs: 'translate-x-1.5',
}
return (
<OriginalSwitch
@ -53,6 +56,7 @@ const Switch = React.forwardRef(
enabled ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked',
'relative inline-flex flex-shrink-0 cursor-pointer rounded-[5px] border-2 border-transparent transition-colors duration-200 ease-in-out',
disabled ? '!opacity-50 !cursor-not-allowed' : '',
size === 'xs' && 'rounded-sm',
className,
)}
>
@ -61,6 +65,7 @@ const Switch = React.forwardRef(
className={classNames(
circleStyle[size],
enabled ? translateLeft[size] : 'translate-x-0',
size === 'xs' && 'rounded-[1px]',
'pointer-events-none inline-block transform rounded-[3px] bg-components-toggle-knob shadow ring-0 transition duration-200 ease-in-out',
)}
/>

View File

@ -3,47 +3,51 @@ import type { FC } from 'react'
import React from 'react'
import cn from '@/utils/classnames'
interface Option {
type Option = {
value: string
text: string | JSX.Element
}
interface ItemProps {
type ItemProps = {
className?: string
isActive: boolean
onClick: (v: string) => void
option: Option
smallItem?: boolean
}
const Item: FC<ItemProps> = ({
className,
isActive,
onClick,
option,
smallItem,
}) => {
return (
<div
key={option.value}
className={cn(
'relative pb-2.5 system-xl-semibold',
'relative pb-2.5 ',
!isActive && 'cursor-pointer',
smallItem ? 'system-sm-semibold-uppercase' : 'system-xl-semibold',
className,
)}
onClick={() => !isActive && onClick(option.value)}
>
<div className={cn(isActive ? 'text-text-primary' : 'text-text-tertiary')}>{option.text}</div>
{isActive && (
<div className='absolute bottom-0 left-0 right-0 h-0.5 bg-util-colors-blue-blue-500'></div>
<div className='absolute bottom-0 left-0 right-0 h-0.5 bg-util-colors-blue-brand-blue-brand-600'></div>
)}
</div>
)
}
interface Props {
type Props = {
className?: string
value: string
onChange: (v: string) => void
options: Option[]
noBorderBottom?: boolean
smallItem?: boolean
itemClassName?: string
}
@ -54,6 +58,7 @@ const TabSlider: FC<Props> = ({
options,
noBorderBottom,
itemClassName,
smallItem,
}) => {
return (
<div className={cn(className, !noBorderBottom && 'border-b border-divider-subtle', 'flex space-x-6')}>
@ -64,6 +69,7 @@ const TabSlider: FC<Props> = ({
onClick={onChange}
key={option.value}
className={itemClassName}
smallItem={smallItem}
/>
))}
</div>

View File

@ -326,6 +326,8 @@ function Form<
</div>
<ToolSelector
scope={scope}
nodeOutputVars={[]}
availableNodes={[]}
disabled={readonly}
value={value[variable]}
// selectedTools={value[variable] ? [value[variable]] : []}
@ -351,6 +353,8 @@ function Form<
<div key={variable} className={cn(itemClassName, 'py-3')}>
<MultipleToolSelector
disabled={readonly}
nodeOutputVars={[]}
availableNodes={[]}
scope={scope}
label={label[language] || label.en_US}
required={required}

View File

@ -10,6 +10,8 @@ import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import Divider from '@/app/components/base/divider'
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
import type { Node } from 'reactflow'
import type { NodeOutPutVar } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
type Props = {
@ -21,6 +23,9 @@ type Props = {
supportCollapse?: boolean
scope?: string
onChange: (value: ToolValue[]) => void
supportVariables?: boolean
nodeOutputVars: NodeOutPutVar[],
availableNodes: Node[],
}
const MultipleToolSelector = ({
@ -32,6 +37,9 @@ const MultipleToolSelector = ({
supportCollapse,
scope,
onChange,
supportVariables,
nodeOutputVars,
availableNodes,
}: Props) => {
const { t } = useTranslation()
const enabledCount = value.filter(item => item.enabled).length
@ -121,6 +129,9 @@ const MultipleToolSelector = ({
{!collapse && (
<>
<ToolSelector
supportVariables={supportVariables}
nodeOutputVars={nodeOutputVars}
availableNodes={availableNodes}
scope={scope}
value={undefined}
selectedTools={value}
@ -140,6 +151,9 @@ const MultipleToolSelector = ({
{value.length > 0 && value.map((item, index) => (
<div className='mb-1' key={index}>
<ToolSelector
supportVariables={supportVariables}
nodeOutputVars={nodeOutputVars}
availableNodes={availableNodes}
scope={scope}
value={item}
selectedTools={value}

View File

@ -21,8 +21,10 @@ import ToolCredentialForm from '@/app/components/plugins/plugin-detail-panel/too
import Toast from '@/app/components/base/toast'
import Textarea from '@/app/components/base/textarea'
import Divider from '@/app/components/base/divider'
import TabSlider from '@/app/components/base/tab-slider-plain'
import ReasoningConfigForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
import { useAppContext } from '@/context/app-context'
import {
@ -41,6 +43,8 @@ import type {
Placement,
} from '@floating-ui/react'
import { MARKETPLACE_API_PREFIX } from '@/config'
import type { Node } from 'reactflow'
import type { NodeOutPutVar } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
type Props = {
@ -54,6 +58,7 @@ type Props = {
provider_name: string
tool_name: string
tool_label: string
settings?: Record<string, any>
parameters?: Record<string, any>
extra?: Record<string, any>
}) => void
@ -65,6 +70,9 @@ type Props = {
onControlledStateChange?: (state: boolean) => void
panelShowState?: boolean
onPanelShowStateChange?: (state: boolean) => void
supportVariables?: boolean
nodeOutputVars: NodeOutPutVar[],
availableNodes: Node[],
}
const ToolSelector: FC<Props> = ({
value,
@ -81,6 +89,8 @@ const ToolSelector: FC<Props> = ({
onControlledStateChange,
panelShowState,
onPanelShowStateChange,
nodeOutputVars,
availableNodes,
}) => {
const { t } = useTranslation()
const [isShow, onShowChange] = useState(false)
@ -107,12 +117,14 @@ const ToolSelector: FC<Props> = ({
const [isShowChooseTool, setIsShowChooseTool] = useState(false)
const handleSelectTool = (tool: ToolDefaultValue) => {
const paramValues = addDefaultValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any))
const settingValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any))
const paramValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form === 'llm') as any), true)
const toolValue = {
provider_name: tool.provider_id,
type: tool.provider_type,
tool_name: tool.tool_name,
tool_label: tool.tool_label,
settings: settingValues,
parameters: paramValues,
enabled: tool.is_team_authorization,
extra: {
@ -133,14 +145,33 @@ const ToolSelector: FC<Props> = ({
} as any)
}
const currentToolParams = useMemo(() => {
// tool settings & params
const currentToolSettings = useMemo(() => {
if (!currentProvider) return []
return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form !== 'llm') || []
}, [currentProvider, value])
const currentToolParams = useMemo(() => {
if (!currentProvider) return []
return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form === 'llm') || []
}, [currentProvider, value])
const [currType, setCurrType] = useState('settings')
const showTabSlider = currentToolSettings.length > 0 && currentToolParams.length > 0
const userSettingsOnly = currentToolSettings.length > 0 && !currentToolParams.length
const reasoningConfigOnly = currentToolParams.length > 0 && !currentToolSettings.length
const formSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams])
const settingsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolSettings), [currentToolSettings])
const paramsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams])
const handleFormChange = (v: Record<string, any>) => {
const handleSettingsFormChange = (v: Record<string, any>) => {
const newValue = getStructureValue(v)
const toolValue = {
...value,
settings: newValue,
}
onSelect(toolValue as any)
}
const handleParamsFormChange = (v: Record<string, any>) => {
const toolValue = {
...value,
parameters: v,
@ -281,12 +312,9 @@ const ToolSelector: FC<Props> = ({
</div>
{/* authorization */}
{currentProvider && currentProvider.type === CollectionType.builtIn && currentProvider.allow_delete && (
<div className='px-4 pt-3 flex flex-col'>
<div className='flex items-center gap-2'>
<div className='shrink-0 text-text-tertiary system-xs-medium-uppercase'>{t('plugin.detailPanel.toolSelector.auth')}</div>
<Divider bgStyle='gradient' className='grow' />
</div>
<div className='py-2'>
<>
<Divider className='my-1 w-full' />
<div className='px-4 py-2'>
{!currentProvider.is_team_authorization && (
<Button
variant='primary'
@ -309,37 +337,86 @@ const ToolSelector: FC<Props> = ({
</Button>
)}
</div>
</div>
</>
)}
{/* tool settings */}
{currentToolParams.length > 0 && currentProvider?.is_team_authorization && (
<div className='px-4 pt-3'>
<div className='flex items-center gap-2'>
<div className='shrink-0 text-text-tertiary system-xs-medium-uppercase'>{t('plugin.detailPanel.toolSelector.settings')}</div>
<Divider bgStyle='gradient' className='grow' />
</div>
<div className='py-2'>
<Form
value={value?.parameters || {}}
onChange={handleFormChange}
formSchemas={formSchemas as any}
isEditMode={true}
showOnVariableMap={{}}
validating={false}
inputClassName='bg-components-input-bg-normal hover:bg-components-input-bg-hover'
fieldMoreInfo={item => item.url
? (<a
href={item.url}
target='_blank' rel='noopener noreferrer'
className='inline-flex items-center text-xs text-text-accent'
>
{t('tools.howToGet')}
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
</a>)
: null}
{(currentToolSettings.length > 0 || currentToolParams.length > 0) && currentProvider?.is_team_authorization && (
<>
<Divider className='my-1 w-full' />
{/* tabs */}
{showTabSlider && (
<TabSlider
className='shrink-0 mt-1 px-4'
itemClassName='py-3'
noBorderBottom
smallItem
value={currType}
onChange={(value) => {
setCurrType(value)
}}
options={[
{ value: 'settings', text: t('plugin.detailPanel.toolSelector.settings')! },
{ value: 'params', text: t('plugin.detailPanel.toolSelector.params')! },
]}
/>
</div>
</div>
)}
{showTabSlider && currType === 'params' && (
<div className='px-4 py-2'>
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip1')}</div>
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip2')}</div>
</div>
)}
{/* user settings only */}
{userSettingsOnly && (
<div className='p-4 pb-1'>
<div className='text-text-primary system-sm-semibold-uppercase'>{t('plugin.detailPanel.toolSelector.settings')}</div>
</div>
)}
{/* reasoning config only */}
{reasoningConfigOnly && (
<div className='mb-1 p-4 pb-1'>
<div className='text-text-primary system-sm-semibold-uppercase'>{t('plugin.detailPanel.toolSelector.params')}</div>
<div className='pb-1'>
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip1')}</div>
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.toolSelector.paramsTip2')}</div>
</div>
</div>
)}
{/* user settings form */}
{(currType === 'settings' || userSettingsOnly) && (
<div className='px-4 py-2'>
<Form
value={getPlainValue(value?.settings || {})}
onChange={handleSettingsFormChange}
formSchemas={settingsFormSchemas as any}
isEditMode={true}
showOnVariableMap={{}}
validating={false}
inputClassName='bg-components-input-bg-normal hover:bg-components-input-bg-hover'
fieldMoreInfo={item => item.url
? (<a
href={item.url}
target='_blank' rel='noopener noreferrer'
className='inline-flex items-center text-xs text-text-accent'
>
{t('tools.howToGet')}
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
</a>)
: null}
/>
</div>
)}
{/* reasoning config form */}
{(currType === 'params' || reasoningConfigOnly) && (
<ReasoningConfigForm
value={value?.parameters || {}}
onChange={handleParamsFormChange}
schemas={paramsFormSchemas as any}
nodeOutputVars={nodeOutputVars}
availableNodes={availableNodes}
/>
)}
</>
)}
</>
)}

View File

@ -0,0 +1,258 @@
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
import {
RiArrowRightUpLine,
} from '@remixicon/react'
import Tooltip from '@/app/components/base/tooltip'
import Switch from '@/app/components/base/switch'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Node } from 'reactflow'
import type { NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types'
import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import cn from '@/utils/classnames'
type Props = {
value: Record<string, any>
onChange: (val: Record<string, any>) => void
schemas: any[]
nodeOutputVars: NodeOutPutVar[],
availableNodes: Node[],
}
const ReasoningConfigForm: React.FC<Props> = ({
value,
onChange,
schemas,
nodeOutputVars,
availableNodes,
}) => {
const { t } = useTranslation()
const language = useLanguage()
const handleFormChange = (key: string, val: string | boolean) => {
onChange({ ...value, [key]: val })
}
const handleAutomatic = (key: string, val: any) => {
onChange({
...value,
[key]: {
...value[key],
auto: val ? 1 : 0,
},
})
}
const [inputsIsFocus, setInputsIsFocus] = useState<Record<string, boolean>>({})
const handleInputFocus = useCallback((variable: string) => {
return (value: boolean) => {
setInputsIsFocus((prev) => {
return {
...prev,
[variable]: value,
}
})
}
}, [])
const handleNotMixedTypeChange = useCallback((variable: string) => {
return (varValue: ValueSelector | string, varKindType: VarKindType) => {
const newValue = produce(value, (draft: ToolVarInputs) => {
const target = draft[variable]
if (target) {
target.type = varKindType
target.value = varValue
}
else {
draft[variable] = {
type: varKindType,
value: varValue,
}
}
})
onChange(newValue)
}
}, [value, onChange])
const handleMixedTypeChange = useCallback((variable: string) => {
return (itemValue: string) => {
const newValue = produce(value, (draft: ToolVarInputs) => {
const target = draft[variable]
if (target) {
target.value = itemValue
}
else {
draft[variable] = {
type: VarKindType.mixed,
value: itemValue,
}
}
})
onChange(newValue)
}
}, [value, onChange])
const handleFileChange = useCallback((variable: string) => {
return (varValue: ValueSelector | string) => {
const newValue = produce(value, (draft: ToolVarInputs) => {
draft[variable] = {
type: VarKindType.variable,
value: varValue,
}
})
onChange(newValue)
}
}, [value, onChange])
const handleAppChange = useCallback((variable: string) => {
return (app: {
app_id: string
inputs: Record<string, any>
files?: any[]
}) => {
const newValue = produce(value, (draft: ToolVarInputs) => {
draft[variable] = app as any
})
onChange(newValue)
}
}, [onChange, value])
const handleModelChange = useCallback((variable: string) => {
return (model: any) => {
const newValue = produce(value, (draft: ToolVarInputs) => {
draft[variable] = {
...draft[variable],
...model,
} as any
})
onChange(newValue)
}
}, [onChange, value])
const renderField = (schema: any) => {
const {
variable,
label,
required,
tooltip,
type,
scope,
url,
} = schema
const auto = value[variable]?.auto
const tooltipContent = (tooltip && (
<Tooltip
popupContent={<div className='w-[200px]'>
{tooltip[language] || tooltip.en_US}
</div>}
triggerClassName='ml-1 w-4 h-4'
asChild={false} />
))
const varInput = value[variable].value
const isNumber = type === FormTypeEnum.textNumber
const isSelect = type === FormTypeEnum.select
const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files
const isAppSelector = type === FormTypeEnum.appSelector
const isModelSelector = type === FormTypeEnum.modelSelector
// const isToolSelector = type === FormTypeEnum.toolSelector
const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector
return (
<div key={variable} className='space-y-1'>
<div className='flex items-center justify-between py-2 system-sm-semibold text-text-secondary'>
<div className='flex items-center space-x-2'>
<span className={cn('text-text-secondary code-sm-semibold')}>{label[language] || label.en_US}</span>
{required && (
<span className='ml-1 text-red-500'>*</span>
)}
{tooltipContent}
</div>
<div className='flex items-center gap-1 px-2 py-1 rounded-[6px] border border-divider-subtle bg-background-default-lighter cursor-pointer hover:bg-state-base-hover' onClick={() => handleAutomatic(variable, !auto)}>
<span className='text-text-secondary system-xs-medium'>automatic</span>
<Switch
size='xs'
defaultValue={!!auto}
onChange={val => handleAutomatic(variable, val)}
/>
</div>
</div>
{auto === 0 && (
<>
{/* {isString && (
<Input
className={cn(inputsIsFocus[variable] ? 'shadow-xs bg-gray-50 border-gray-300' : 'bg-gray-100 border-gray-100', 'rounded-lg px-3 py-[6px] border')}
value={varInput?.value as string || ''}
onChange={handleMixedTypeChange(variable)}
nodesOutputVars={nodeOutputVars}
availableNodes={availableNodes}
onFocusChange={handleInputFocus(variable)}
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
placeholderClassName='!leading-[21px]'
/>
)} */}
{/* {(isNumber || isSelect) && (
<VarReferencePicker
readonly={readOnly}
isShowNodeName
nodeId={nodeId}
value={varInput?.type === VarKindType.constant ? (varInput?.value ?? '') : (varInput?.value ?? [])}
onChange={handleNotMixedTypeChange(variable)}
onOpen={handleOpen(index)}
defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)}
isSupportConstantValue={isSupportConstantValue}
filterVar={isNumber ? filterVar : undefined}
availableVars={isSelect ? availableVars : undefined}
schema={schema}
/>
)} */}
{/* {isFile && (
<VarReferencePicker
readonly={readOnly}
isShowNodeName
nodeId={nodeId}
value={varInput?.value || []}
onChange={handleFileChange(variable)}
onOpen={handleOpen(index)}
defaultVarKindType={VarKindType.variable}
filterVar={(varPayload: Var) => varPayload.type === VarType.file || varPayload.type === VarType.arrayFile}
/>
)} */}
{isAppSelector && (
<AppSelector
disabled={false}
scope={scope || 'all'}
value={varInput as any}
onSelect={handleAppChange(variable)}
/>
)}
{isModelSelector && (
<ModelParameterModal
popupClassName='!w-[387px]'
isAdvancedMode
isInWorkflow
value={varInput as any}
setModel={handleModelChange(variable)}
scope={scope}
/>
)}
</>
)}
{url && (
<a
href={url}
target='_blank' rel='noopener noreferrer'
className='inline-flex items-center text-xs text-text-accent'
>
{t('tools.howToGet')}
<RiArrowRightUpLine className='ml-1 w-3 h-3' />
</a>
)}
</div>
)
}
return (
<div className='px-4 py-2 space-y-3'>
{schemas.map(schema => renderField(schema))}
</div>
)
}
export default ReasoningConfigForm

View File

@ -63,3 +63,34 @@ export const addDefaultValue = (value: Record<string, any>, formSchemas: { varia
})
return newValues
}
export const generateFormValue = (value: Record<string, any>, formSchemas: { variable: string; default?: any }[], isReasoning = false) => {
const newValues = {} as any
formSchemas.forEach((formSchema) => {
const itemValue = value[formSchema.variable]
if ((formSchema.default !== undefined) && (value === undefined || itemValue === null || itemValue === '' || itemValue === undefined)) {
newValues[formSchema.variable] = {
...(isReasoning ? { value: null, auto: 1 } : { value: formSchema.default }),
}
}
})
return newValues
}
export const getPlainValue = (value: Record<string, any>) => {
const plainValue = { ...value } as any
Object.keys(plainValue).forEach((key) => {
plainValue[key] = value[key].value
})
return plainValue
}
export const getStructureValue = (value: Record<string, any>) => {
const newValue = { ...value } as any
Object.keys(newValue).forEach((key) => {
newValue[key] = {
value: value[key],
}
})
return newValue
}

View File

@ -35,6 +35,7 @@ export type ToolValue = {
provider_name: string
tool_name: string
tool_label: string
settings?: Record<string, any>
parameters?: Record<string, any>
enabled?: boolean
extra?: Record<string, any>

View File

@ -154,6 +154,9 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
tooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
>
<ToolSelector
supportVariables
nodeOutputVars={nodeOutputVars || []}
availableNodes={availableNodes || []}
scope={schema.scope}
value={value}
onSelect={item => onChange(item)}
@ -169,6 +172,9 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => {
}
return (
<MultipleToolSelector
supportVariables
nodeOutputVars={nodeOutputVars || []}
availableNodes={availableNodes || []}
scope={schema.scope}
value={value || []}
label={renderI18nObject(schema.label)}

View File

@ -78,8 +78,10 @@ const translation = {
descriptionLabel: 'Tool description',
descriptionPlaceholder: 'Brief description of the tool\'s purpose, e.g., get the temperature for a specific location.',
placeholder: 'Select a tool...',
auth: 'AUTHORIZATION',
settings: 'TOOL SETTINGS',
settings: 'USER SETTINGS',
params: 'REASONING CONFIG',
paramsTip1: 'Controls LLM inference parameters.',
paramsTip2: 'When \'Automatic\' is off, the default value is used.',
empty: 'Click the \'+\' button to add tools. You can add multiple tools.',
uninstalledTitle: 'Tool not installed',
uninstalledContent: 'This plugin is installed from the local/GitHub repository. Please use after installation.',

View File

@ -78,8 +78,10 @@ const translation = {
descriptionLabel: '工具描述',
descriptionPlaceholder: '简要描述工具目的,例如,获取特定位置的温度。',
placeholder: '选择工具',
auth: '授权',
settings: '工具设置',
settings: '用户设置',
params: '推理配置',
paramsTip1: '控制 LLM 推理参数。',
paramsTip2: '当“自动”关闭时,使用默认值。',
empty: '点击 "+" 按钮添加工具。您可以添加多个工具。',
uninstalledTitle: '工具未安装',
uninstalledContent: '此插件安装自 本地 / GitHub 仓库,请安装后使用。',