Merge branch 'main' into feat/workflow-add-block-shortcut

This commit is contained in:
Yi 2024-08-23 15:56:37 +08:00
commit 5408e923e3
47 changed files with 278 additions and 139 deletions

View File

@ -0,0 +1,9 @@
model: text-embedding-v3
model_type: text-embedding
model_properties:
context_size: 8192
max_chunks: 25
pricing:
input: "0.0007"
unit: "0.001"
currency: RMB

View File

@ -21,6 +21,7 @@ class LangfuseConfig(BaseTracingConfig):
""" """
public_key: str public_key: str
secret_key: str secret_key: str
project_key: str
host: str = 'https://api.langfuse.com' host: str = 'https://api.langfuse.com'
@field_validator("host") @field_validator("host")

View File

@ -419,3 +419,11 @@ class LangFuseDataTrace(BaseTraceInstance):
except Exception as e: except Exception as e:
logger.debug(f"LangFuse API check failed: {str(e)}") logger.debug(f"LangFuse API check failed: {str(e)}")
raise ValueError(f"LangFuse API check failed: {str(e)}") raise ValueError(f"LangFuse API check failed: {str(e)}")
def get_project_key(self):
try:
projects = self.langfuse_client.client.projects.get()
return projects.data[0].id
except Exception as e:
logger.debug(f"LangFuse get project key failed: {str(e)}")
raise ValueError(f"LangFuse get project key failed: {str(e)}")

View File

@ -38,7 +38,7 @@ provider_config_map = {
TracingProviderEnum.LANGFUSE.value: { TracingProviderEnum.LANGFUSE.value: {
'config_class': LangfuseConfig, 'config_class': LangfuseConfig,
'secret_keys': ['public_key', 'secret_key'], 'secret_keys': ['public_key', 'secret_key'],
'other_keys': ['host'], 'other_keys': ['host', 'project_key'],
'trace_instance': LangFuseDataTrace 'trace_instance': LangFuseDataTrace
}, },
TracingProviderEnum.LANGSMITH.value: { TracingProviderEnum.LANGSMITH.value: {
@ -123,7 +123,6 @@ class OpsTraceManager:
for key in other_keys: for key in other_keys:
new_config[key] = decrypt_tracing_config.get(key, "") new_config[key] = decrypt_tracing_config.get(key, "")
return config_class(**new_config).model_dump() return config_class(**new_config).model_dump()
@classmethod @classmethod
@ -252,6 +251,19 @@ class OpsTraceManager:
tracing_config = config_type(**tracing_config) tracing_config = config_type(**tracing_config)
return trace_instance(tracing_config).api_check() return trace_instance(tracing_config).api_check()
@staticmethod
def get_trace_config_project_key(tracing_config: dict, tracing_provider: str):
"""
get trace config is project key
:param tracing_config: tracing config
:param tracing_provider: tracing provider
:return:
"""
config_type, trace_instance = provider_config_map[tracing_provider]['config_class'], \
provider_config_map[tracing_provider]['trace_instance']
tracing_config = config_type(**tracing_config)
return trace_instance(tracing_config).get_project_key()
class TraceTask: class TraceTask:
def __init__( def __init__(

View File

@ -22,6 +22,10 @@ class OpsService:
# decrypt_token and obfuscated_token # decrypt_token and obfuscated_token
tenant_id = db.session.query(App).filter(App.id == app_id).first().tenant_id tenant_id = db.session.query(App).filter(App.id == app_id).first().tenant_id
decrypt_tracing_config = OpsTraceManager.decrypt_tracing_config(tenant_id, tracing_provider, trace_config_data.tracing_config) decrypt_tracing_config = OpsTraceManager.decrypt_tracing_config(tenant_id, tracing_provider, trace_config_data.tracing_config)
if tracing_provider == 'langfuse' and ('project_key' not in decrypt_tracing_config or not decrypt_tracing_config.get('project_key')):
project_key = OpsTraceManager.get_trace_config_project_key(decrypt_tracing_config, tracing_provider)
decrypt_tracing_config['project_key'] = project_key
decrypt_tracing_config = OpsTraceManager.obfuscated_decrypt_token(tracing_provider, decrypt_tracing_config) decrypt_tracing_config = OpsTraceManager.obfuscated_decrypt_token(tracing_provider, decrypt_tracing_config)
trace_config_data.tracing_config = decrypt_tracing_config trace_config_data.tracing_config = decrypt_tracing_config
@ -37,7 +41,7 @@ class OpsService:
:param tracing_config: tracing config :param tracing_config: tracing config
:return: :return:
""" """
if tracing_provider not in provider_config_map.keys() and tracing_provider != None: if tracing_provider not in provider_config_map.keys() and tracing_provider:
return {"error": f"Invalid tracing provider: {tracing_provider}"} return {"error": f"Invalid tracing provider: {tracing_provider}"}
config_class, other_keys = provider_config_map[tracing_provider]['config_class'], \ config_class, other_keys = provider_config_map[tracing_provider]['config_class'], \
@ -51,6 +55,9 @@ class OpsService:
if not OpsTraceManager.check_trace_config_is_effective(tracing_config, tracing_provider): if not OpsTraceManager.check_trace_config_is_effective(tracing_config, tracing_provider):
return {"error": "Invalid Credentials"} return {"error": "Invalid Credentials"}
# get project key
project_key = OpsTraceManager.get_trace_config_project_key(tracing_config, tracing_provider)
# check if trace config already exists # check if trace config already exists
trace_config_data: TraceAppConfig = db.session.query(TraceAppConfig).filter( trace_config_data: TraceAppConfig = db.session.query(TraceAppConfig).filter(
TraceAppConfig.app_id == app_id, TraceAppConfig.tracing_provider == tracing_provider TraceAppConfig.app_id == app_id, TraceAppConfig.tracing_provider == tracing_provider
@ -62,6 +69,8 @@ class OpsService:
# get tenant id # get tenant id
tenant_id = db.session.query(App).filter(App.id == app_id).first().tenant_id tenant_id = db.session.query(App).filter(App.id == app_id).first().tenant_id
tracing_config = OpsTraceManager.encrypt_tracing_config(tenant_id, tracing_provider, tracing_config) tracing_config = OpsTraceManager.encrypt_tracing_config(tenant_id, tracing_provider, tracing_config)
if tracing_provider == 'langfuse':
tracing_config['project_key'] = project_key
trace_config_data = TraceAppConfig( trace_config_data = TraceAppConfig(
app_id=app_id, app_id=app_id,
tracing_provider=tracing_provider, tracing_provider=tracing_provider,

View File

@ -6,6 +6,7 @@ import { TracingProvider } from './type'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { LangfuseIconBig, LangsmithIconBig } from '@/app/components/base/icons/src/public/tracing' import { LangfuseIconBig, LangsmithIconBig } from '@/app/components/base/icons/src/public/tracing'
import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' import { Settings04 } from '@/app/components/base/icons/src/vender/line/general'
import { Eye as View } from '@/app/components/base/icons/src/vender/solid/general'
const I18N_PREFIX = 'app.tracing' const I18N_PREFIX = 'app.tracing'
@ -13,6 +14,7 @@ type Props = {
type: TracingProvider type: TracingProvider
readOnly: boolean readOnly: boolean
isChosen: boolean isChosen: boolean
Config: any
onChoose: () => void onChoose: () => void
hasConfigured: boolean hasConfigured: boolean
onConfig: () => void onConfig: () => void
@ -29,6 +31,7 @@ const ProviderPanel: FC<Props> = ({
type, type,
readOnly, readOnly,
isChosen, isChosen,
Config,
onChoose, onChoose,
hasConfigured, hasConfigured,
onConfig, onConfig,
@ -41,6 +44,14 @@ const ProviderPanel: FC<Props> = ({
onConfig() onConfig()
}, [onConfig]) }, [onConfig])
const viewBtnClick = useCallback((e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
const url = `${Config?.host}/project/${Config?.project_key}`
window.open(url, '_blank', 'noopener,noreferrer')
}, [Config?.host, Config?.project_key])
const handleChosen = useCallback((e: React.MouseEvent) => { const handleChosen = useCallback((e: React.MouseEvent) => {
e.stopPropagation() e.stopPropagation()
if (isChosen || !hasConfigured || readOnly) if (isChosen || !hasConfigured || readOnly)
@ -58,12 +69,20 @@ const ProviderPanel: FC<Props> = ({
{isChosen && <div className='ml-1 flex items-center h-4 px-1 rounded-[4px] border border-primary-500 leading-4 text-xs font-medium text-primary-500 uppercase '>{t(`${I18N_PREFIX}.inUse`)}</div>} {isChosen && <div className='ml-1 flex items-center h-4 px-1 rounded-[4px] border border-primary-500 leading-4 text-xs font-medium text-primary-500 uppercase '>{t(`${I18N_PREFIX}.inUse`)}</div>}
</div> </div>
{!readOnly && ( {!readOnly && (
<div <div className={'flex justify-between items-center space-x-1'}>
className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1' {hasConfigured && (
onClick={handleConfigBtnClick} <div className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1' onClick={viewBtnClick} >
> <View className='w-3 h-3'/>
<Settings04 className='w-3 h-3' /> <div className='text-xs font-medium'>{t(`${I18N_PREFIX}.view`)}</div>
<div className='text-xs font-medium'>{t(`${I18N_PREFIX}.config`)}</div> </div>
)}
<div
className='flex px-2 items-center h-6 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer text-gray-700 space-x-1'
onClick={handleConfigBtnClick}
>
<Settings04 className='w-3 h-3' />
<div className='text-xs font-medium'>{t(`${I18N_PREFIX}.config`)}</div>
</div>
</div> </div>
)} )}

View File

@ -42,18 +42,19 @@ const ConfigModal: FC<IConfigModalProps> = ({
const { type, label, variable, options, max_length } = tempPayload const { type, label, variable, options, max_length } = tempPayload
const isStringInput = type === InputVarType.textInput || type === InputVarType.paragraph const isStringInput = type === InputVarType.textInput || type === InputVarType.paragraph
const checkVariableName = useCallback((value: string) => {
const { isValid, errorMessageKey } = checkKeys([value], false)
if (!isValid) {
Toast.notify({
type: 'error',
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('appDebug.variableConig.varName') }),
})
return false
}
return true
}, [t])
const handlePayloadChange = useCallback((key: string) => { const handlePayloadChange = useCallback((key: string) => {
return (value: any) => { return (value: any) => {
if (key === 'variable') {
const { isValid, errorKey, errorMessageKey } = checkKeys([value], true)
if (!isValid) {
Toast.notify({
type: 'error',
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }),
})
return
}
}
setTempPayload((prev) => { setTempPayload((prev) => {
const newPayload = { const newPayload = {
...prev, ...prev,
@ -63,19 +64,20 @@ const ConfigModal: FC<IConfigModalProps> = ({
return newPayload return newPayload
}) })
} }
}, [t]) }, [])
const handleVarKeyBlur = useCallback((e: any) => { const handleVarKeyBlur = useCallback((e: any) => {
if (tempPayload.label) const varName = e.target.value
if (!checkVariableName(varName) || tempPayload.label)
return return
setTempPayload((prev) => { setTempPayload((prev) => {
return { return {
...prev, ...prev,
label: e.target.value, label: varName,
} }
}) })
}, [tempPayload]) }, [checkVariableName, tempPayload.label])
const handleConfirm = () => { const handleConfirm = () => {
const moreInfo = tempPayload.variable === payload?.variable const moreInfo = tempPayload.variable === payload?.variable
@ -84,10 +86,11 @@ const ConfigModal: FC<IConfigModalProps> = ({
type: ChangeType.changeVarName, type: ChangeType.changeVarName,
payload: { beforeKey: payload?.variable || '', afterKey: tempPayload.variable }, payload: { beforeKey: payload?.variable || '', afterKey: tempPayload.variable },
} }
if (!tempPayload.variable) {
Toast.notify({ type: 'error', message: t('appDebug.variableConig.errorMsg.varNameRequired') }) const isVariableNameValid = checkVariableName(tempPayload.variable)
if (!isVariableNameValid)
return return
}
// TODO: check if key already exists. should the consider the edit case // TODO: check if key already exists. should the consider the edit case
// if (varKeys.map(key => key?.trim()).includes(tempPayload.variable.trim())) { // if (varKeys.map(key => key?.trim()).includes(tempPayload.variable.trim())) {
// Toast.notify({ // Toast.notify({

View File

@ -15,6 +15,7 @@ import type { ConversationVariable } from '@/app/components/workflow/types'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type' import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { checkKeys } from '@/utils/var'
export type ModalPropsType = { export type ModalPropsType = {
chatVar?: ConversationVariable chatVar?: ConversationVariable
@ -128,14 +129,16 @@ const ChatVariableModal = ({
} }
} }
const handleNameChange = (v: string) => { const checkVariableName = (value: string) => {
if (!v) const { isValid, errorMessageKey } = checkKeys([value], false)
return setName('') if (!isValid) {
if (!/^[a-zA-Z0-9_]+$/.test(v)) notify({
return notify({ type: 'error', message: 'name is can only contain letters, numbers and underscores' }) type: 'error',
if (/^[0-9]/.test(v)) message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('workflow.env.modal.name') }),
return notify({ type: 'error', message: 'name can not start with a number' }) })
setName(v) return false
}
return true
} }
const handleTypeChange = (v: ChatVarType) => { const handleTypeChange = (v: ChatVarType) => {
@ -211,8 +214,8 @@ const ChatVariableModal = ({
} }
const handleSave = () => { const handleSave = () => {
if (!name) if (!checkVariableName(name))
return notify({ type: 'error', message: 'name can not be empty' }) return
if (!chatVar && varList.some(chatVar => chatVar.name === name)) if (!chatVar && varList.some(chatVar => chatVar.name === name))
return notify({ type: 'error', message: 'name is existed' }) return notify({ type: 'error', message: 'name is existed' })
// if (type !== ChatVarType.Object && !value) // if (type !== ChatVarType.Object && !value)
@ -272,7 +275,8 @@ const ChatVariableModal = ({
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder' className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
placeholder={t('workflow.chatVariable.modal.namePlaceholder') || ''} placeholder={t('workflow.chatVariable.modal.namePlaceholder') || ''}
value={name} value={name}
onChange={e => handleNameChange(e.target.value)} onChange={e => setName(e.target.value || '')}
onBlur={e => checkVariableName(e.target.value)}
type='text' type='text'
/> />
</div> </div>

View File

@ -9,6 +9,7 @@ import { ToastContext } from '@/app/components/base/toast'
import { useStore } from '@/app/components/workflow/store' import { useStore } from '@/app/components/workflow/store'
import type { EnvironmentVariable } from '@/app/components/workflow/types' import type { EnvironmentVariable } from '@/app/components/workflow/types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { checkKeys } from '@/utils/var'
export type ModalPropsType = { export type ModalPropsType = {
env?: EnvironmentVariable env?: EnvironmentVariable
@ -28,19 +29,21 @@ const VariableModal = ({
const [name, setName] = React.useState('') const [name, setName] = React.useState('')
const [value, setValue] = React.useState<any>() const [value, setValue] = React.useState<any>()
const handleNameChange = (v: string) => { const checkVariableName = (value: string) => {
if (!v) const { isValid, errorMessageKey } = checkKeys([value], false)
return setName('') if (!isValid) {
if (!/^[a-zA-Z0-9_]+$/.test(v)) notify({
return notify({ type: 'error', message: 'name is can only contain letters, numbers and underscores' }) type: 'error',
if (/^[0-9]/.test(v)) message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: t('workflow.env.modal.name') }),
return notify({ type: 'error', message: 'name can not start with a number' }) })
setName(v) return false
}
return true
} }
const handleSave = () => { const handleSave = () => {
if (!name) if (!checkVariableName(name))
return notify({ type: 'error', message: 'name can not be empty' }) return
if (!value) if (!value)
return notify({ type: 'error', message: 'value can not be empty' }) return notify({ type: 'error', message: 'value can not be empty' })
if (!env && envList.some(env => env.name === name)) if (!env && envList.some(env => env.name === name))
@ -118,7 +121,8 @@ const VariableModal = ({
className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder' className='block px-3 w-full h-8 bg-components-input-bg-normal system-sm-regular radius-md border border-transparent appearance-none outline-none caret-primary-600 hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs placeholder:system-sm-regular placeholder:text-components-input-text-placeholder'
placeholder={t('workflow.env.modal.namePlaceholder') || ''} placeholder={t('workflow.env.modal.namePlaceholder') || ''}
value={name} value={name}
onChange={e => handleNameChange(e.target.value)} onChange={e => setName(e.target.value || '')}
onBlur={e => checkVariableName(e.target.value)}
type='text' type='text'
/> />
</div> </div>

View File

@ -237,11 +237,11 @@ const translation = {
typeSelect: 'Auswählen', typeSelect: 'Auswählen',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'Variablenschlüssel darf nicht leer sein', canNoBeEmpty: '{{key}} ist erforderlich',
tooLong: 'Variablenschlüssel: {{key}} zu lang. Darf nicht länger als 30 Zeichen sein', tooLong: '{{key}} zu lang. Darf nicht länger als 30 Zeichen sein',
notValid: 'Variablenschlüssel: {{key}} ist ungültig. Darf nur Buchstaben, Zahlen und Unterstriche enthalten', notValid: '{{key}} ist ungültig. Darf nur Buchstaben, Zahlen und Unterstriche enthalten',
notStartWithNumber: 'Variablenschlüssel: {{key}} darf nicht mit einer Zahl beginnen', notStartWithNumber: '{{key}} darf nicht mit einer Zahl beginnen',
keyAlreadyExists: 'Variablenschlüssel: :{{key}} existiert bereits', keyAlreadyExists: '{{key}} existiert bereits',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Prompt darf nicht leer sein', promptNoBeEmpty: 'Prompt darf nicht leer sein',

View File

@ -60,6 +60,10 @@ const translation = {
ukUA: 'Ukrainisch', ukUA: 'Ukrainisch',
viVN: 'Vietnamesisch', viVN: 'Vietnamesisch',
plPL: 'Polnisch', plPL: 'Polnisch',
roRO: 'Rumänisch',
hiIN: 'Hindi',
trTR: 'Türkisch',
faIR: 'Persisch',
}, },
}, },
unit: { unit: {

View File

@ -290,11 +290,11 @@ const translation = {
typeSelect: 'Select', typeSelect: 'Select',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'Variable key can not be empty', canNoBeEmpty: '{{key}} is required',
tooLong: 'Variable key: {{key}} too length. Can not be longer then 30 characters', tooLong: '{{key}} is too length. Can not be longer then 30 characters',
notValid: 'Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores', notValid: '{{key}} is invalid. Can only contain letters, numbers, and underscores',
notStartWithNumber: 'Variable key: {{key}} can not start with a number', notStartWithNumber: '{{key}} can not start with a number',
keyAlreadyExists: 'Variable key: :{{key}} already exists', keyAlreadyExists: '{{key}} already exists',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Prompt can not be empty', promptNoBeEmpty: 'Prompt can not be empty',
@ -323,7 +323,6 @@ const translation = {
'content': 'Content', 'content': 'Content',
'required': 'Required', 'required': 'Required',
'errorMsg': { 'errorMsg': {
varNameRequired: 'Variable name is required',
labelNameRequired: 'Label name is required', labelNameRequired: 'Label name is required',
varNameCanBeRepeat: 'Variable name can not be repeated', varNameCanBeRepeat: 'Variable name can not be repeated',
atLeastOneOption: 'At least one option is required', atLeastOneOption: 'At least one option is required',

View File

@ -95,6 +95,7 @@ const translation = {
title: 'Tracing app performance', title: 'Tracing app performance',
description: 'Configuring a Third-Party LLMOps provider and tracing app performance.', description: 'Configuring a Third-Party LLMOps provider and tracing app performance.',
config: 'Config', config: 'Config',
view: 'View',
collapse: 'Collapse', collapse: 'Collapse',
expand: 'Expand', expand: 'Expand',
tracing: 'Tracing', tracing: 'Tracing',

View File

@ -55,7 +55,7 @@ const translation = {
frFR: 'French', frFR: 'French',
esES: 'Spanish', esES: 'Spanish',
itIT: 'Italian', itIT: 'Italian',
thTH: 'Thai.', thTH: 'Thai',
idID: 'Indonesian', idID: 'Indonesian',
jaJP: 'Japanese', jaJP: 'Japanese',
koKR: 'Korean', koKR: 'Korean',
@ -64,6 +64,10 @@ const translation = {
ukUA: 'Ukrainian', ukUA: 'Ukrainian',
viVN: 'Vietnamese', viVN: 'Vietnamese',
plPL: 'Polish', plPL: 'Polish',
roRO: 'Romanian',
hiIN: 'Hindi',
trTR: 'Türkçe',
faIR: 'Farsi',
}, },
}, },
unit: { unit: {

View File

@ -248,11 +248,11 @@ const translation = {
typeSelect: 'Seleccionar', typeSelect: 'Seleccionar',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'La clave de la variable no puede estar vacía', canNoBeEmpty: 'Se requiere {{key}}',
tooLong: 'Clave de la variable: {{key}} demasiado larga. No puede tener más de 30 caracteres', tooLong: '{{key}} demasiado larga. No puede tener más de 30 caracteres',
notValid: 'Clave de la variable: {{key}} no es válida. Solo puede contener letras, números y guiones bajos', notValid: '{{key}} no es válida. Solo puede contener letras, números y guiones bajos',
notStartWithNumber: 'Clave de la variable: {{key}} no puede comenzar con un número', notStartWithNumber: '{{key}} no puede comenzar con un número',
keyAlreadyExists: 'Clave de la variable: {{key}} ya existe', keyAlreadyExists: '{{key}} ya existe',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'La indicación no puede estar vacía', promptNoBeEmpty: 'La indicación no puede estar vacía',

View File

@ -64,6 +64,10 @@ const translation = {
ukUA: 'Ucraniano', ukUA: 'Ucraniano',
viVN: 'Vietnamita', viVN: 'Vietnamita',
plPL: 'Polaco', plPL: 'Polaco',
roRO: 'Rumano',
hiIN: 'Hindi',
trTR: 'Turco',
faIR: 'Persa',
}, },
}, },
unit: { unit: {

View File

@ -283,11 +283,11 @@ const translation = {
typeSelect: 'انتخاب', typeSelect: 'انتخاب',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'کلید متغیر نمی‌تواند خالی باشد', canNoBeEmpty: '{{key}} مطلوب',
tooLong: 'کلید متغیر: {{key}} طولانی است. نمی‌تواند بیش از 30 کاراکتر باشد', tooLong: '{{key}} طولانی است. نمی‌تواند بیش از 30 کاراکتر باشد',
notValid: 'کلید متغیر: {{key}} نامعتبر است. فقط می‌تواند شامل حروف، اعداد و زیرخط باشد', notValid: '{{key}} نامعتبر است. فقط می‌تواند شامل حروف، اعداد و زیرخط باشد',
notStartWithNumber: 'کلید متغیر: {{key}} نمی‌تواند با عدد شروع شود', notStartWithNumber: '{{key}} نمی‌تواند با عدد شروع شود',
keyAlreadyExists: 'کلید متغیر: :{{key}} از قبل وجود دارد', keyAlreadyExists: '{{key}} از قبل وجود دارد',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'پرس و جو نمی‌تواند خالی باشد', promptNoBeEmpty: 'پرس و جو نمی‌تواند خالی باشد',

View File

@ -64,6 +64,10 @@ const translation = {
ukUA: 'اوکراینی', ukUA: 'اوکراینی',
viVN: 'ویتنامی', viVN: 'ویتنامی',
plPL: 'لهستانی', plPL: 'لهستانی',
roRO: 'رومانیایی',
hiIN: 'هندی',
trTR: 'ترکی',
faIR: 'فارسی',
}, },
}, },
unit: { unit: {

View File

@ -237,11 +237,11 @@ const translation = {
typeSelect: 'Sélectionner', typeSelect: 'Sélectionner',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'La clé variable ne peut pas être vide', canNoBeEmpty: '{{key}} est obligatoire',
tooLong: 'Variable key: {{key}} too length. Can not be longer then 30 characters', tooLong: '{{key}} too length. Can not be longer then 30 characters',
notValid: 'Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores', notValid: '{{key}} is invalid. Can only contain letters, numbers, and underscores',
notStartWithNumber: 'Variable key: {{key}} can not start with a number', notStartWithNumber: '{{key}} can not start with a number',
keyAlreadyExists: 'Variable key: :{{key}} already exists', keyAlreadyExists: '{{key}} already exists',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Le prompt ne peut pas être vide', promptNoBeEmpty: 'Le prompt ne peut pas être vide',

View File

@ -51,7 +51,7 @@ const translation = {
frFR: 'Français', frFR: 'Français',
esES: 'Espagnol', esES: 'Espagnol',
itIT: 'Italien', itIT: 'Italien',
thTH: 'Thaï.', thTH: 'Thaï',
idID: 'Indonésien', idID: 'Indonésien',
jaJP: 'Japonais', jaJP: 'Japonais',
koKR: 'Coréen', koKR: 'Coréen',
@ -60,6 +60,10 @@ const translation = {
ukUA: 'Ukrainien', ukUA: 'Ukrainien',
viVN: 'Vietnamien', viVN: 'Vietnamien',
plPL: 'Polonais', plPL: 'Polonais',
roRO: 'Roumain',
hiIN: 'Hindi',
trTR: 'Turc',
faIR: 'Persan',
}, },
}, },
unit: { unit: {

View File

@ -276,14 +276,14 @@ const translation = {
typeSelect: 'चुनें', typeSelect: 'चुनें',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'वेरिएबल कुंजी खाली नहीं हो सकती', canNoBeEmpty: '{{key}} आवश्यक है',
tooLong: tooLong:
'वेरिएबल कुंजी: {{key}} बहुत लंबी है। 30 वर्णों से अधिक नहीं हो सकती', '{{key}} बहुत लंबी है। 30 वर्णों से अधिक नहीं हो सकती',
notValid: notValid:
'वेरिएबल कुंजी: {{key}} अवैध है। केवल अक्षर, संख्याएं, और अंडरस्कोर शामिल हो सकते हैं', '{{key}} अवैध है। केवल अक्षर, संख्याएं, और अंडरस्कोर शामिल हो सकते हैं',
notStartWithNumber: notStartWithNumber:
'वेरिएबल कुंजी: {{key}} एक संख्या से प्रारंभ नहीं हो सकती', '{{key}} एक संख्या से प्रारंभ नहीं हो सकती',
keyAlreadyExists: 'वेरिएबल कुंजी: {{key}} पहले से मौजूद है', keyAlreadyExists: '{{key}} पहले से मौजूद है',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'प्रॉम्प्ट खाली नहीं हो सकता', promptNoBeEmpty: 'प्रॉम्प्ट खाली नहीं हो सकता',

View File

@ -64,6 +64,10 @@ const translation = {
ukUA: 'यूक्रेनी', ukUA: 'यूक्रेनी',
viVN: 'वियतनामी', viVN: 'वियतनामी',
plPL: 'पोलिश', plPL: 'पोलिश',
roRO: 'रोमानियाई',
hiIN: 'हिन्दी',
trTR: 'तुर्की',
faIR: 'फ़ारसी',
}, },
}, },
unit: { unit: {

View File

@ -278,14 +278,14 @@ const translation = {
typeSelect: 'Seleziona', typeSelect: 'Seleziona',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'La chiave della variabile non può essere vuota', canNoBeEmpty: '{{key}} è obbligatorio',
tooLong: tooLong:
'La chiave della variabile: {{key}} è troppo lunga. Non può essere più lunga di 30 caratteri', '{{key}} è troppo lunga. Non può essere più lunga di 30 caratteri',
notValid: notValid:
'La chiave della variabile: {{key}} non è valida. Può contenere solo lettere, numeri e underscore', '{{key}} non è valida. Può contenere solo lettere, numeri e underscore',
notStartWithNumber: notStartWithNumber:
'La chiave della variabile: {{key}} non può iniziare con un numero', '{{key}} non può iniziare con un numero',
keyAlreadyExists: 'La chiave della variabile: {{key}} esiste già', keyAlreadyExists: '{{key}} esiste già',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Il prompt non può essere vuoto', promptNoBeEmpty: 'Il prompt non può essere vuoto',

View File

@ -64,6 +64,10 @@ const translation = {
ukUA: 'Ucraino', ukUA: 'Ucraino',
viVN: 'Vietnamita', viVN: 'Vietnamita',
plPL: 'Polacco', plPL: 'Polacco',
roRO: 'Rumeno',
hiIN: 'Hindi',
trTR: 'Turco',
faIR: 'Persiano',
}, },
}, },
unit: { unit: {

View File

@ -284,11 +284,11 @@ const translation = {
typeSelect: '選択', typeSelect: '選択',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: '変数キーを空にすることはできません', canNoBeEmpty: '{{key}} は必須です',
tooLong: '変数キー: {{key}} が長すぎます。30文字を超えることはできません', tooLong: '{{key}} が長すぎます。30文字を超えることはできません',
notValid: '変数キー: {{key}} が無効です。文字、数字、アンダースコアのみを含めることができます', notValid: '{{key}} が無効です。文字、数字、アンダースコアのみを含めることができます',
notStartWithNumber: '変数キー: {{key}} は数字で始めることはできません', notStartWithNumber: '{{key}} は数字で始めることはできません',
keyAlreadyExists: '変数キー: {{key}} はすでに存在します', keyAlreadyExists: '{{key}} はすでに存在します',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'プロンプトを空にすることはできません', promptNoBeEmpty: 'プロンプトを空にすることはできません',

View File

@ -64,6 +64,10 @@ const translation = {
ukUA: 'ウクライナ語', ukUA: 'ウクライナ語',
viVN: 'ベトナム語', viVN: 'ベトナム語',
plPL: 'ポーランド語', plPL: 'ポーランド語',
roRO: 'ルーマニア語',
hiIN: 'ヒンディー語',
trTR: 'トルコ語',
faIR: 'ペルシア語',
}, },
}, },
unit: { unit: {

View File

@ -248,11 +248,11 @@ const translation = {
typeSelect: '선택', typeSelect: '선택',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: '변수 키를 비울 수 없습니다', canNoBeEmpty: '{{key}}가 필요합니다',
tooLong: '변수 키: {{key}}가 너무 깁니다. 30자를 넘을 수 없습니다', tooLong: '{{key}}가 너무 깁니다. 30자를 넘을 수 없습니다',
notValid: '변수 키: {{key}}가 유효하지 않습니다. 문자, 숫자, 밑줄만 포함할 수 있습니다', notValid: '{{key}}가 유효하지 않습니다. 문자, 숫자, 밑줄만 포함할 수 있습니다',
notStartWithNumber: '변수 키: {{key}}는 숫자로 시작할 수 없습니다', notStartWithNumber: '{{key}}는 숫자로 시작할 수 없습니다',
keyAlreadyExists: '변수 키: {{key}}는 이미 존재합니다', keyAlreadyExists: '{{key}}는 이미 존재합니다',
}, },
otherError: { otherError: {
promptNoBeEmpty: '프롬프트를 비울 수 없습니다', promptNoBeEmpty: '프롬프트를 비울 수 없습니다',

View File

@ -60,6 +60,10 @@ const translation = {
ukUA: '우크라이나어', ukUA: '우크라이나어',
viVN: '베트남어', viVN: '베트남어',
plPL: '폴란드어', plPL: '폴란드어',
roRO: '루마니아어',
hiIN: '힌디어',
trTR: '터키어',
faIR: '페르시아어',
}, },
}, },
unit: { unit: {

View File

@ -275,14 +275,14 @@ const translation = {
typeSelect: 'Wybierz', typeSelect: 'Wybierz',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'Klucz zmiennej nie może być pusty', canNoBeEmpty: '{{klucz}} jest wymagany',
tooLong: tooLong:
'Klucz zmiennej: {{key}} za długi. Nie może być dłuższy niż 30 znaków', '{{key}} za długi. Nie może być dłuższy niż 30 znaków',
notValid: notValid:
'Klucz zmiennej: {{key}} jest nieprawidłowy. Może zawierać tylko litery, cyfry i podkreślenia', '{{key}} jest nieprawidłowy. Może zawierać tylko litery, cyfry i podkreślenia',
notStartWithNumber: notStartWithNumber:
'Klucz zmiennej: {{key}} nie może zaczynać się od cyfry', '{{key}} nie może zaczynać się od cyfry',
keyAlreadyExists: 'Klucz zmiennej: :{{key}} już istnieje', keyAlreadyExists: '{{key}} już istnieje',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Monit nie może być pusty', promptNoBeEmpty: 'Monit nie może być pusty',

View File

@ -60,6 +60,10 @@ const translation = {
ukUA: 'Ukraiński', ukUA: 'Ukraiński',
viVN: 'Wietnamski', viVN: 'Wietnamski',
plPL: 'Polski', plPL: 'Polski',
roRO: 'Rumuński',
hiIN: 'Hindi',
trTR: 'Turecki',
faIR: 'Perski',
}, },
}, },
unit: { unit: {

View File

@ -254,11 +254,11 @@ const translation = {
typeSelect: 'Selecionar', typeSelect: 'Selecionar',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'A chave da variável não pode estar vazia', canNoBeEmpty: '{{key}} é obrigatório',
tooLong: 'A chave da variável: {{key}} é muito longa. Não pode ter mais de 30 caracteres', tooLong: '{{key}} é muito longa. Não pode ter mais de 30 caracteres',
notValid: 'A chave da variável: {{key}} é inválida. Pode conter apenas letras, números e sublinhados', notValid: '{{key}} é inválida. Pode conter apenas letras, números e sublinhados',
notStartWithNumber: 'A chave da variável: {{key}} não pode começar com um número', notStartWithNumber: '{{key}} não pode começar com um número',
keyAlreadyExists: 'A chave da variável: :{{key}} já existe', keyAlreadyExists: '{{key}} já existe',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'A solicitação não pode estar vazia', promptNoBeEmpty: 'A solicitação não pode estar vazia',

View File

@ -60,6 +60,10 @@ const translation = {
ukUA: 'Ucraniano', ukUA: 'Ucraniano',
viVN: 'Vietnamita', viVN: 'Vietnamita',
plPL: 'Polonês', plPL: 'Polonês',
roRO: 'Romeno',
hiIN: 'Hindi',
trTR: 'Turco',
faIR: 'Persa',
}, },
}, },
unit: { unit: {

View File

@ -254,11 +254,11 @@ const translation = {
typeSelect: 'Selectează', typeSelect: 'Selectează',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'Cheia variabilei nu poate fi goală', canNoBeEmpty: '{{key}} este necesară',
tooLong: 'Cheia variabilei: {{key}} este prea lungă. Nu poate fi mai lungă de 30 de caractere', tooLong: '{{key}} este prea lungă. Nu poate fi mai lungă de 30 de caractere',
notValid: 'Cheia variabilei: {{key}} este nevalidă. Poate conține doar litere, cifre și sublinieri', notValid: '{{key}} este nevalidă. Poate conține doar litere, cifre și sublinieri',
notStartWithNumber: 'Cheia variabilei: {{key}} nu poate începe cu un număr', notStartWithNumber: '{{key}} nu poate începe cu un număr',
keyAlreadyExists: 'Cheia variabilei: :{{key}} deja există', keyAlreadyExists: ':{{key}} deja există',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Promptul nu poate fi gol', promptNoBeEmpty: 'Promptul nu poate fi gol',

View File

@ -59,6 +59,10 @@ const translation = {
ruRU: 'Rusă', ruRU: 'Rusă',
ukUA: 'Ucraineană', ukUA: 'Ucraineană',
viVN: 'Vietnameză', viVN: 'Vietnameză',
roRO: 'Română',
hiIN: 'Hindi',
trTR: 'Turcă',
faIR: 'Persană',
}, },
}, },
unit: { unit: {

View File

@ -290,11 +290,11 @@ const translation = {
typeSelect: 'Seçim', typeSelect: 'Seçim',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'Değişken anahtarı boş olamaz', canNoBeEmpty: '{{key}} gereklidir',
tooLong: 'Değişken anahtarı: {{key}} çok uzun. 30 karakterden uzun olamaz', tooLong: '{{key}} çok uzun. 30 karakterden uzun olamaz',
notValid: 'Değişken anahtarı: {{key}} geçersizdir. Sadece harfler, rakamlar ve altçizgiler içerebilir', notValid: '{{key}} geçersizdir. Sadece harfler, rakamlar ve altçizgiler içerebilir',
notStartWithNumber: 'Değişken anahtarı: {{key}} bir rakamla başlamamalıdır', notStartWithNumber: '{{key}} bir rakamla başlamamalıdır',
keyAlreadyExists: 'Değişken anahtarı: {{key}} zaten mevcut', keyAlreadyExists: '{{key}} zaten mevcut',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Prompt boş olamaz', promptNoBeEmpty: 'Prompt boş olamaz',

View File

@ -55,7 +55,7 @@ const translation = {
frFR: 'French', frFR: 'French',
esES: 'Spanish', esES: 'Spanish',
itIT: 'Italian', itIT: 'Italian',
thTH: 'Thai.', thTH: 'Thai',
idID: 'Indonesian', idID: 'Indonesian',
jaJP: 'Japanese', jaJP: 'Japanese',
koKR: 'Korean', koKR: 'Korean',
@ -64,6 +64,10 @@ const translation = {
ukUA: 'Ukrainian', ukUA: 'Ukrainian',
viVN: 'Vietnamese', viVN: 'Vietnamese',
plPL: 'Polish', plPL: 'Polish',
roRO: 'Romence',
hiIN: 'Hintçe',
trTR: 'Türkçe',
faIR: 'Farsça',
}, },
}, },
unit: { unit: {

View File

@ -248,11 +248,11 @@ const translation = {
typeSelect: 'Вибрати', // Select typeSelect: 'Вибрати', // Select
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'Ключ змінної не може бути порожнім', // Variable key can not be empty canNoBeEmpty: 'Потрібен {{key}}', // Variable key can not be empty
tooLong: 'Ключ змінної: {{key}} занадто довгий. Не може бути більше 30 символів', // Variable key: {{key}} too length. Can not be longer then 30 characters tooLong: '{{key}} занадто довгий. Не може бути більше 30 символів', // Variable key: {{key}} too length. Can not be longer then 30 characters
notValid: 'Ключ змінної: {{key}} недійсний. Може містити лише літери, цифри та підкреслення', // Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores notValid: '{{key}} недійсний. Може містити лише літери, цифри та підкреслення', // Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores
notStartWithNumber: 'Ключ змінної: {{key}} не може починатися з цифри', // Variable key: {{key}} can not start with a number notStartWithNumber: '{{key}} не може починатися з цифри', // Variable key: {{key}} can not start with a number
keyAlreadyExists: 'Ключ змінної: :{{key}} вже існує', // Variable key: :{{key}} already exists keyAlreadyExists: ':{{key}} вже існує', // Variable key: :{{key}} already exists
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Команда не може бути порожньою', // Prompt can not be empty promptNoBeEmpty: 'Команда не може бути порожньою', // Prompt can not be empty

View File

@ -60,6 +60,10 @@ const translation = {
ukUA: 'Українська', ukUA: 'Українська',
viVN: 'В\'є тнамська', viVN: 'В\'є тнамська',
plPL: 'Польська', plPL: 'Польська',
roRO: 'Румунська',
hiIN: 'Хінді',
trTR: 'Турецька',
faIR: 'Перська',
}, },
}, },
unit: { unit: {

View File

@ -248,11 +248,11 @@ const translation = {
typeSelect: 'Lựa chọn', typeSelect: 'Lựa chọn',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: 'Khóa biến không thể trống', canNoBeEmpty: '{{key}} là bắt buộc',
tooLong: 'Khóa biến: {{key}} quá dài. Không thể dài hơn 30 ký tự', tooLong: '{{key}} quá dài. Không thể dài hơn 30 ký tự',
notValid: 'Khóa biến: {{key}} không hợp lệ. Chỉ có thể chứa chữ cái, số, và dấu gạch dưới', notValid: '{{key}} không hợp lệ. Chỉ có thể chứa chữ cái, số, và dấu gạch dưới',
notStartWithNumber: 'Khóa biến: {{key}} không thể bắt đầu bằng số', notStartWithNumber: '{{key}} không thể bắt đầu bằng số',
keyAlreadyExists: 'Khóa biến: {{key}} đã tồn tại', keyAlreadyExists: '{{key}} đã tồn tại',
}, },
otherError: { otherError: {
promptNoBeEmpty: 'Lời nhắc không thể trống', promptNoBeEmpty: 'Lời nhắc không thể trống',

View File

@ -45,6 +45,7 @@ const translation = {
voice: { voice: {
language: { language: {
zhHans: 'Tiếng Trung', zhHans: 'Tiếng Trung',
zhHant: 'Tiếng Trung phồn thể',
enUS: 'Tiếng Anh', enUS: 'Tiếng Anh',
deDE: 'Tiếng Đức', deDE: 'Tiếng Đức',
frFR: 'Tiếng Pháp', frFR: 'Tiếng Pháp',
@ -59,6 +60,10 @@ const translation = {
ukUA: 'Tiếng Ukraina', ukUA: 'Tiếng Ukraina',
viVN: 'Tiếng Việt', viVN: 'Tiếng Việt',
plPL: 'Tiếng Ba Lan', plPL: 'Tiếng Ba Lan',
roRO: 'Tiếng Rumani',
hiIN: 'Tiếng Hindi',
trTR: 'Tiếng Thổ Nhĩ Kỳ',
faIR: 'Tiếng Ba Tư',
}, },
}, },
unit: { unit: {

View File

@ -287,11 +287,11 @@ const translation = {
typeSelect: '下拉选项', typeSelect: '下拉选项',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: '变量不能为空', canNoBeEmpty: '{{key}}必填',
tooLong: '变量: {{key}} 长度太长。不能超过 30 个字符', tooLong: '{{key}} 长度太长。不能超过 30 个字符',
notValid: '变量: {{key}} 非法。只能包含英文字符,数字和下划线', notValid: '{{key}} 非法。只能包含英文字符,数字和下划线',
notStartWithNumber: '变量: {{key}} 不能以数字开头', notStartWithNumber: '{{key}} 不能以数字开头',
keyAlreadyExists: '变量:{{key}} 已存在', keyAlreadyExists: '{{key}} 已存在',
}, },
otherError: { otherError: {
promptNoBeEmpty: '提示词不能为空', promptNoBeEmpty: '提示词不能为空',
@ -320,7 +320,6 @@ const translation = {
'required': '必填', 'required': '必填',
'content': '内容', 'content': '内容',
'errorMsg': { 'errorMsg': {
varNameRequired: '变量名称必填',
labelNameRequired: '显示名称必填', labelNameRequired: '显示名称必填',
varNameCanBeRepeat: '变量名称不能重复', varNameCanBeRepeat: '变量名称不能重复',
atLeastOneOption: '至少需要一个选项', atLeastOneOption: '至少需要一个选项',

View File

@ -94,6 +94,7 @@ const translation = {
title: '追踪应用性能', title: '追踪应用性能',
description: '配置第三方 LLMOps 提供商并跟踪应用程序性能。', description: '配置第三方 LLMOps 提供商并跟踪应用程序性能。',
config: '配置', config: '配置',
view: '查看',
collapse: '折叠', collapse: '折叠',
expand: '展开', expand: '展开',
tracing: '追踪', tracing: '追踪',

View File

@ -64,6 +64,10 @@ const translation = {
ukUA: '乌克兰语', ukUA: '乌克兰语',
viVN: '越南语', viVN: '越南语',
plPL: '波兰语', plPL: '波兰语',
roRO: '罗马尼亚语',
hiIN: '印地语',
trTR: '土耳其语',
faIR: '波斯语',
}, },
}, },
unit: { unit: {

View File

@ -233,11 +233,11 @@ const translation = {
typeSelect: '下拉選項', typeSelect: '下拉選項',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: '變數不能為空', canNoBeEmpty: '{{key}} 是必要的',
tooLong: '變數: {{key}} 長度太長。不能超過 30 個字元', tooLong: '{{key}} 長度太長。不能超過 30 個字元',
notValid: '變數: {{key}} 非法。只能包含英文字元,數字和下劃線', notValid: '{{key}} 非法。只能包含英文字元,數字和下劃線',
notStartWithNumber: '變數: {{key}} 不能以數字開頭', notStartWithNumber: '{{key}} 不能以數字開頭',
keyAlreadyExists: '變數:{{key}} 已存在', keyAlreadyExists: '{{key}} 已存在',
}, },
otherError: { otherError: {
promptNoBeEmpty: '提示詞不能為空', promptNoBeEmpty: '提示詞不能為空',

View File

@ -90,6 +90,7 @@ const translation = {
title: '追蹤應用程式效能', title: '追蹤應用程式效能',
description: '配置第三方LLMOps提供商並追蹤應用程式效能。', description: '配置第三方LLMOps提供商並追蹤應用程式效能。',
config: '配置', config: '配置',
view: '查看',
collapse: '收起', collapse: '收起',
expand: '展開', expand: '展開',
tracing: '追蹤', tracing: '追蹤',

View File

@ -60,6 +60,10 @@ const translation = {
ukUA: '烏克蘭語', ukUA: '烏克蘭語',
viVN: '越南語', viVN: '越南語',
plPL: '波蘭語', plPL: '波蘭語',
roRO: '羅馬尼亞語',
hiIN: '印地語',
trTR: '土耳其語',
faIR: '波斯語',
}, },
}, },
unit: { unit: {

View File

@ -9373,4 +9373,4 @@ zustand@^4.4.1, zustand@^4.5.2:
zwitch@^2.0.0: zwitch@^2.0.0:
version "2.0.4" version "2.0.4"
resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz" resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz"
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==