| @@ -241,7 +241,8 @@ const Prompt: FC<ISimplePromptInput> = ({ | |||
| selectable: !hasSetBlockStatus.query, | |||
| }} | |||
| onChange={(value) => { | |||
| handleChange?.(value, []) | |||
| if (handleChange) | |||
| handleChange(value, []) | |||
| }} | |||
| onBlur={() => { | |||
| handleChange(promptTemplate, getVars(promptTemplate)) | |||
| @@ -151,7 +151,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({ | |||
| return | |||
| } | |||
| if (localeData.variable && !/[a-zA-Z_][a-zA-Z0-9_]{0,29}/g.test(localeData.variable)) { | |||
| if (localeData.variable && !/[a-zA-Z_]\w{0,29}/g.test(localeData.variable)) { | |||
| notify({ type: 'error', message: t('appDebug.varKeyError.notValid', { key: t('appDebug.feature.tools.modal.variableName.title') }) }) | |||
| return | |||
| } | |||
| @@ -39,12 +39,12 @@ const OPTION_MAP = { | |||
| `<script> | |||
| window.difyChatbotConfig = { | |||
| token: '${token}'${isTestEnv | |||
| ? `, | |||
| ? `, | |||
| isDev: true` | |||
| : ''}${IS_CE_EDITION | |||
| ? `, | |||
| : ''}${IS_CE_EDITION | |||
| ? `, | |||
| baseUrl: '${url}'` | |||
| : ''}, | |||
| : ''}, | |||
| systemVariables: { | |||
| // user_id: 'YOU CAN DEFINE USER ID HERE', | |||
| }, | |||
| @@ -110,7 +110,7 @@ const Embedded = ({ siteInfo, isShow, onClose, appBaseUrl, accessToken, classNam | |||
| } | |||
| const navigateToChromeUrl = () => { | |||
| window.open('https://chrome.google.com/webstore/detail/dify-chatbot/ceehdapohffmjmkdcifjofadiaoeggaf', '_blank') | |||
| window.open('https://chrome.google.com/webstore/detail/dify-chatbot/ceehdapohffmjmkdcifjofadiaoeggaf', '_blank', 'noopener,noreferrer') | |||
| } | |||
| useEffect(() => { | |||
| @@ -97,7 +97,7 @@ const AudioBtn = ({ | |||
| </div> | |||
| ) | |||
| : ( | |||
| <div className={`flex h-full w-full items-center justify-center rounded-md ${!isAudition ? 'hover:bg-gray-50' : 'hover:bg-gray-50'}`}> | |||
| <div className={'flex h-full w-full items-center justify-center rounded-md hover:bg-gray-50'}> | |||
| <div className={`h-4 w-4 ${(audioState === 'playing') ? s.pauseIcon : s.playIcon}`}></div> | |||
| </div> | |||
| )} | |||
| @@ -196,7 +196,8 @@ const Chat: FC<ChatProps> = ({ | |||
| const chatContainer = chatContainerRef.current | |||
| if (chatContainer) { | |||
| const setUserScrolled = () => { | |||
| if (chatContainer) | |||
| // eslint-disable-next-line sonarjs/no-gratuitous-expressions | |||
| if (chatContainer) // its in event callback, chatContainer may be null | |||
| userScrolledRef.current = chatContainer.scrollHeight - chatContainer.scrollTop > chatContainer.clientHeight | |||
| } | |||
| chatContainer.addEventListener('scroll', setUserScrolled) | |||
| @@ -245,7 +245,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({ | |||
| > | |||
| <div className='flex items-center justify-between'> | |||
| <div className='title-2xl-semi-bold text-text-primary'>{t('appDebug.feature.moderation.modal.title')}</div> | |||
| <div className='cursor-pointer p-1' onClick={onCancel}><RiCloseLine className='h-4 w-4 text-text-tertiary'/></div> | |||
| <div className='cursor-pointer p-1' onClick={onCancel}><RiCloseLine className='h-4 w-4 text-text-tertiary' /></div> | |||
| </div> | |||
| <div className='py-2'> | |||
| <div className='text-sm font-medium leading-9 text-text-primary'> | |||
| @@ -348,14 +348,14 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({ | |||
| config={localeData.config?.inputs_config || { enabled: false, preset_response: '' }} | |||
| onConfigChange={config => handleDataContentChange('inputs_config', config)} | |||
| info={(localeData.type === 'api' && t('appDebug.feature.moderation.modal.content.fromApi')) || ''} | |||
| showPreset={!(localeData.type === 'api')} | |||
| showPreset={localeData.type !== 'api'} | |||
| /> | |||
| <ModerationContent | |||
| title={t('appDebug.feature.moderation.modal.content.output') || ''} | |||
| config={localeData.config?.outputs_config || { enabled: false, preset_response: '' }} | |||
| onConfigChange={config => handleDataContentChange('outputs_config', config)} | |||
| info={(localeData.type === 'api' && t('appDebug.feature.moderation.modal.content.fromApi')) || ''} | |||
| showPreset={!(localeData.type === 'api')} | |||
| showPreset={localeData.type !== 'api'} | |||
| /> | |||
| <div className='mb-8 mt-1 text-xs font-medium text-text-tertiary'>{t('appDebug.feature.moderation.modal.content.condition')}</div> | |||
| <div className='flex items-center justify-end'> | |||
| @@ -50,10 +50,7 @@ jest.mock('@/app/components/plugins/marketplace/utils', () => ({ | |||
| getMarketplacePluginsByCollectionId: jest.fn(), | |||
| })) | |||
| jest.mock('./provider-added-card', () => { | |||
| // eslint-disable-next-line no-labels, ts/no-unused-expressions | |||
| UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST: [] | |||
| }) | |||
| jest.mock('./provider-added-card', () => jest.fn()) | |||
| after(() => { | |||
| jest.resetModules() | |||
| @@ -37,7 +37,7 @@ const ParameterItem: FC<ParameterItemProps> = ({ | |||
| if (parameterRule.type === 'int' || parameterRule.type === 'float') | |||
| defaultValue = isNullOrUndefined(parameterRule.default) ? (parameterRule.min || 0) : parameterRule.default | |||
| else if (parameterRule.type === 'string' || parameterRule.type === 'text') | |||
| defaultValue = parameterRule.options?.length ? (parameterRule.default || '') : (parameterRule.default || '') | |||
| defaultValue = parameterRule.default || '' | |||
| else if (parameterRule.type === 'boolean') | |||
| defaultValue = !isNullOrUndefined(parameterRule.default) ? parameterRule.default : false | |||
| else if (parameterRule.type === 'tag') | |||
| @@ -132,8 +132,6 @@ const ParameterItem: FC<ParameterItemProps> = ({ | |||
| step = 1 | |||
| else if (parameterRule.max < 1000) | |||
| step = 10 | |||
| else if (parameterRule.max < 10000) | |||
| step = 100 | |||
| } | |||
| return ( | |||
| @@ -46,25 +46,23 @@ const CardWrapper = ({ | |||
| } | |||
| /> | |||
| { | |||
| showInstallButton && ( | |||
| <div className='absolute bottom-0 hidden w-full items-center space-x-2 rounded-b-xl bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent px-4 pb-4 pt-8 group-hover:flex'> | |||
| <div className='absolute bottom-0 hidden w-full items-center space-x-2 rounded-b-xl bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent px-4 pb-4 pt-8 group-hover:flex'> | |||
| <Button | |||
| variant='primary' | |||
| className='w-[calc(50%-4px)]' | |||
| onClick={showInstallFromMarketplace} | |||
| > | |||
| {t('plugin.detailPanel.operation.install')} | |||
| </Button> | |||
| <a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'> | |||
| <Button | |||
| variant='primary' | |||
| className='w-[calc(50%-4px)]' | |||
| onClick={showInstallFromMarketplace} | |||
| className='w-full gap-0.5' | |||
| > | |||
| {t('plugin.detailPanel.operation.install')} | |||
| {t('plugin.detailPanel.operation.detail')} | |||
| <RiArrowRightUpLine className='ml-1 h-4 w-4' /> | |||
| </Button> | |||
| <a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'> | |||
| <Button | |||
| className='w-full gap-0.5' | |||
| > | |||
| {t('plugin.detailPanel.operation.detail')} | |||
| <RiArrowRightUpLine className='ml-1 h-4 w-4' /> | |||
| </Button> | |||
| </a> | |||
| </div> | |||
| ) | |||
| </a> | |||
| </div> | |||
| } | |||
| { | |||
| isShowInstallFromMarketplace && ( | |||
| @@ -72,7 +72,7 @@ const Empty = () => { | |||
| <div className='flex w-full flex-col gap-y-1'> | |||
| {[ | |||
| ...( | |||
| (enable_marketplace && true) | |||
| (enable_marketplace) | |||
| ? [{ icon: MagicBox, text: t('plugin.list.source.marketplace'), action: 'marketplace' }] | |||
| : [] | |||
| ), | |||
| @@ -86,7 +86,7 @@ const InstallPluginDropdown = ({ | |||
| <div className='w-full'> | |||
| {[ | |||
| ...( | |||
| (enable_marketplace && true) | |||
| (enable_marketplace) | |||
| ? [{ icon: MagicBox, text: t('plugin.source.marketplace'), action: 'marketplace' }] | |||
| : [] | |||
| ), | |||
| @@ -1,7 +1,6 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import React, { useCallback, useEffect, useMemo, useState } from 'react' | |||
| import { RiInformation2Line } from '@remixicon/react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import Card from '@/app/components/plugins/card' | |||
| import Modal from '@/app/components/base/modal' | |||
| @@ -103,15 +102,7 @@ const UpdatePluginModal: FC<Props> = ({ | |||
| if (uploadStep === UploadStep.installed) | |||
| onSave() | |||
| }, [onSave, uploadStep, check, originalPackageInfo.id, handleRefetch, targetPackageInfo.id]) | |||
| const usedInAppInfo = useMemo(() => { | |||
| return ( | |||
| <div className='flex items-center justify-center gap-0.5 px-0.5'> | |||
| <div className='system-xs-medium text-text-warning'>{t(`${i18nPrefix}.usedInApps`, { num: 3 })}</div> | |||
| {/* show the used apps */} | |||
| <RiInformation2Line className='h-4 w-4 text-text-tertiary' /> | |||
| </div> | |||
| ) | |||
| }, [t]) | |||
| return ( | |||
| <Modal | |||
| isShow={true} | |||
| @@ -136,7 +127,6 @@ const UpdatePluginModal: FC<Props> = ({ | |||
| <Badge className='mx-1' size="s" state={BadgeState.Warning}> | |||
| {`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`} | |||
| </Badge> | |||
| {false && usedInAppInfo} | |||
| </> | |||
| } | |||
| /> | |||
| @@ -66,7 +66,7 @@ const WorkflowToolAsModal: FC<Props> = ({ | |||
| if (name === '') | |||
| return true | |||
| return /^[a-zA-Z0-9_]+$/.test(name) | |||
| return /^\w+$/.test(name) | |||
| } | |||
| const onConfirm = () => { | |||
| @@ -84,7 +84,7 @@ const CodeEditor: FC<Props> = ({ | |||
| const getUniqVarName = (varName: string) => { | |||
| if (varList.find(v => v.variable === varName)) { | |||
| const match = varName.match(/_([0-9]+)$/) | |||
| const match = varName.match(/_(\d+)$/) | |||
| const index = (() => { | |||
| if (match) | |||
| @@ -92,7 +92,7 @@ const CodeEditor: FC<Props> = ({ | |||
| return 1 | |||
| })() | |||
| return getUniqVarName(`${varName.replace(/_([0-9]+)$/, '')}_${index}`) | |||
| return getUniqVarName(`${varName.replace(/_(\d+)$/, '')}_${index}`) | |||
| } | |||
| return varName | |||
| } | |||
| @@ -278,6 +278,7 @@ const formatItem = ( | |||
| break | |||
| } | |||
| // eslint-disable-next-line sonarjs/no-duplicated-branches | |||
| case BlockEnum.VariableAggregator: { | |||
| const { | |||
| output_type, | |||
| @@ -466,7 +467,7 @@ const formatItem = ( | |||
| res.vars = res.vars.filter((v) => { | |||
| const isCurrentMatched = filterVar(v, (() => { | |||
| const variableArr = v.variable.split('.') | |||
| const [first, ..._other] = variableArr | |||
| const [first] = variableArr | |||
| if (first === 'sys' || first === 'env' || first === 'conversation') | |||
| return variableArr | |||
| @@ -611,6 +612,7 @@ const getLoopItemType = ({ | |||
| }: { | |||
| valueSelector: ValueSelector | |||
| beforeNodesOutputVars: NodeOutPutVar[] | |||
| // eslint-disable-next-line sonarjs/no-identical-functions | |||
| }): VarType => { | |||
| const outputVarNodeId = valueSelector[0] | |||
| const isSystem = isSystemVar(valueSelector) | |||
| @@ -1243,6 +1245,7 @@ export const updateNodeVars = (oldNode: Node, oldVarSelector: ValueSelector, new | |||
| } | |||
| break | |||
| } | |||
| // eslint-disable-next-line sonarjs/no-duplicated-branches | |||
| case BlockEnum.VariableAggregator: { | |||
| const payload = data as VariableAssignerNodeType | |||
| if (payload.variables) { | |||
| @@ -64,7 +64,7 @@ const Item: FC<ItemProps> = ({ | |||
| const isChatVar = itemData.variable.startsWith('conversation.') | |||
| const itemRef = useRef<HTMLDivElement>(null) | |||
| const [isItemHovering, setIsItemHovering] = useState(false) | |||
| const _ = useHover(itemRef, { | |||
| useHover(itemRef, { | |||
| onChange: (hovering) => { | |||
| if (hovering) { | |||
| setIsItemHovering(true) | |||
| @@ -185,7 +185,7 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({ | |||
| const currObjPath = objPath | |||
| const itemRef = useRef<HTMLDivElement>(null) | |||
| const [isItemHovering, setIsItemHovering] = useState(false) | |||
| const _ = useHover(itemRef, { | |||
| useHover(itemRef, { | |||
| onChange: (hovering) => { | |||
| if (hovering) { | |||
| setIsItemHovering(true) | |||
| @@ -97,7 +97,7 @@ const nodeDefault: NodeDefault<AgentNodeType> = { | |||
| } | |||
| // check form of tools | |||
| else { | |||
| let validState = { | |||
| const validState = { | |||
| isValid: true, | |||
| errorMessage: '', | |||
| } | |||
| @@ -108,13 +108,13 @@ const nodeDefault: NodeDefault<AgentNodeType> = { | |||
| schemas.forEach((schema: any) => { | |||
| if (schema?.required) { | |||
| if (schema.form === 'form' && !userSettings[schema.name]?.value) { | |||
| return validState = { | |||
| return { | |||
| isValid: false, | |||
| errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), | |||
| } | |||
| } | |||
| if (schema.form === 'llm' && reasoningConfig[schema.name]?.auto === 0 && !reasoningConfig[schema.name]?.value) { | |||
| return validState = { | |||
| return { | |||
| isValid: false, | |||
| errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), | |||
| } | |||
| @@ -102,6 +102,7 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => { | |||
| {t('workflow.nodes.agent.toolbox')} | |||
| </GroupLabel>}> | |||
| <div className='grid grid-cols-10 gap-0.5'> | |||
| {/* eslint-disable-next-line sonarjs/no-uniq-key */} | |||
| {tools.map(tool => <ToolIcon {...tool} key={Math.random()} />)} | |||
| </div> | |||
| </Group>} | |||
| @@ -16,16 +16,10 @@ const nodeDefault: NodeDefault<EndNodeType> = { | |||
| getAvailableNextNodes() { | |||
| return [] | |||
| }, | |||
| checkValid(payload: EndNodeType) { | |||
| let isValid = true | |||
| let errorMessages = '' | |||
| if (payload.type) { | |||
| isValid = true | |||
| errorMessages = '' | |||
| } | |||
| checkValid() { | |||
| return { | |||
| isValid, | |||
| errorMessage: errorMessages, | |||
| isValid: true, | |||
| errorMessage: '', | |||
| } | |||
| }, | |||
| } | |||
| @@ -62,5 +62,5 @@ export const comparisonOperatorNotRequireValue = (operator?: ComparisonOperator) | |||
| return [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull, ComparisonOperator.exists, ComparisonOperator.notExists].includes(operator) | |||
| } | |||
| export const VARIABLE_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}/gi | |||
| export const VARIABLE_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_]\w{0,29}){1,10}#)\}\}/gi | |||
| export const COMMON_VARIABLE_REGEX = /\{\{([a-zA-Z0-9_-]{1,50})\}\}/gi | |||
| @@ -36,7 +36,6 @@ export const getSelectedDatasetsMode = (datasets: DataSet[] = []) => { | |||
| allHighQualityFullTextSearch = false | |||
| allEconomic = false | |||
| mixtureHighQualityAndEconomic = false | |||
| inconsistentEmbeddingModel = false | |||
| allExternal = false | |||
| allInternal = false | |||
| mixtureInternalAndExternal = false | |||
| @@ -18,4 +18,5 @@ export function getSelectedNode( | |||
| return $isAtNodeEnd(anchor) ? anchorNode : focusNode | |||
| } | |||
| // eslint-disable-next-line sonarjs/empty-string-repetition | |||
| export const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/ | |||
| @@ -38,7 +38,7 @@ const ObjectValueItem: FC<Props> = ({ | |||
| const handleKeyChange = useCallback((index: number) => { | |||
| return (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| const newList = produce(list, (draft: any[]) => { | |||
| if (!/^[a-zA-Z0-9_]+$/.test(e.target.value)) | |||
| if (!/^\w+$/.test(e.target.value)) | |||
| return notify({ type: 'error', message: 'key is can only contain letters, numbers and underscores' }) | |||
| draft[index].key = e.target.value | |||
| }) | |||
| @@ -95,7 +95,7 @@ const VariableModal = ({ | |||
| type === 'number' && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg font-medium text-text-primary shadow-xs hover:border-components-option-card-option-selected-border', | |||
| )} onClick={() => { | |||
| setType('number') | |||
| if (!(/^[0-9]$/).test(value)) | |||
| if (!(/^\d$/).test(value)) | |||
| setValue('') | |||
| }}>Number</div> | |||
| <div className={cn( | |||
| @@ -1,37 +0,0 @@ | |||
| import graphToLogStruct from '../graph-to-log-struct' | |||
| describe('parallel', () => { | |||
| const list = graphToLogStruct('(parallel, parallelNode, nodeA, nodeB -> nodeC)') | |||
| const [parallelNode, ...parallelDetail] = list | |||
| const parallelI18n = 'PARALLEL' | |||
| // format will change the list... | |||
| // const result = format(cloneDeep(list) as any, () => parallelI18n) | |||
| test('parallel should put nodes in details', () => { | |||
| // expect(result as any).toEqual([ | |||
| // { | |||
| // ...parallelNode, | |||
| // parallelDetail: { | |||
| // isParallelStartNode: true, | |||
| // parallelTitle: `${parallelI18n}-1`, | |||
| // children: [ | |||
| // parallelNode, | |||
| // { | |||
| // ...parallelDetail[0], | |||
| // parallelDetail: { | |||
| // branchTitle: `${parallelI18n}-1-A`, | |||
| // }, | |||
| // }, | |||
| // { | |||
| // ...parallelDetail[1], | |||
| // parallelDetail: { | |||
| // branchTitle: `${parallelI18n}-1-B`, | |||
| // }, | |||
| // }, | |||
| // parallelDetail[2], | |||
| // ], | |||
| // }, | |||
| // }, | |||
| // ]) | |||
| }) | |||
| }) | |||
| @@ -148,6 +148,7 @@ const format = (list: NodeTracing[], t: any, isPrint?: boolean): NodeTracing[] = | |||
| return false | |||
| const isParallelStartNode = node.parallelDetail?.isParallelStartNode | |||
| // eslint-disable-next-line sonarjs/prefer-single-boolean-return | |||
| if (!isParallelStartNode) | |||
| return false | |||
| @@ -14,8 +14,5 @@ export const variableTransformer = (v: ValueSelector | string) => { | |||
| } | |||
| export const isExceptionVariable = (variable: string, nodeType?: BlockEnum) => { | |||
| if ((variable === 'error_message' || variable === 'error_type') && hasErrorHandleNode(nodeType)) | |||
| return true | |||
| return false | |||
| return (variable === 'error_message' || variable === 'error_type') && hasErrorHandleNode(nodeType) | |||
| } | |||
| @@ -265,7 +265,7 @@ Thought: {{agent_scratchpad}} | |||
| `, | |||
| } | |||
| export const VAR_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}/gi | |||
| export const VAR_REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_]\w{0,29}){1,10}#)\}\}/gi | |||
| export const resetReg = () => VAR_REGEX.lastIndex = 0 | |||
| @@ -7,6 +7,8 @@ import storybook from 'eslint-plugin-storybook' | |||
| // import { fixupConfigRules } from '@eslint/compat' | |||
| import tailwind from 'eslint-plugin-tailwindcss' | |||
| import reactHooks from 'eslint-plugin-react-hooks' | |||
| import sonar from 'eslint-plugin-sonarjs' | |||
| // import reactRefresh from 'eslint-plugin-react-refresh' | |||
| export default combine( | |||
| @@ -138,7 +140,48 @@ export default combine( | |||
| 'react-hooks': reactHooks, | |||
| }, | |||
| }, | |||
| // need futher research | |||
| // sonar | |||
| { | |||
| rules: { | |||
| ...sonar.configs.recommended.rules, | |||
| // code complexity | |||
| 'sonarjs/cognitive-complexity': 'off', | |||
| 'sonarjs/no-nested-functions': 'warn', | |||
| 'sonarjs/no-nested-conditional': 'warn', | |||
| 'sonarjs/nested-control-flow': 'warn', // 3 levels of nesting | |||
| 'sonarjs/no-small-switch': 'off', | |||
| 'sonarjs/no-nested-template-literals': 'warn', | |||
| 'sonarjs/redundant-type-aliases': 'off', | |||
| 'sonarjs/regex-complexity': 'warn', | |||
| // maintainability | |||
| 'sonarjs/no-ignored-exceptions': 'off', | |||
| 'sonarjs/no-commented-code': 'warn', | |||
| 'sonarjs/no-unused-vars': 'warn', | |||
| 'sonarjs/prefer-single-boolean-return': 'warn', | |||
| 'sonarjs/duplicates-in-character-class': 'off', | |||
| 'sonarjs/single-char-in-character-classes': 'off', | |||
| 'sonarjs/anchor-precedence': 'warn', | |||
| 'sonarjs/updated-loop-counter': 'off', | |||
| 'sonarjs/no-dead-store': 'warn', | |||
| 'sonarjs/no-duplicated-branches': 'warn', | |||
| 'sonarjs/max-lines': 'warn', // max 1000 lines | |||
| 'sonarjs/no-variable-usage-before-declaration': 'error', | |||
| // security | |||
| // eslint-disable-next-line sonarjs/no-hardcoded-passwords | |||
| 'sonarjs/no-hardcoded-passwords': 'off', // detect the wrong code that is not password. | |||
| 'sonarjs/no-hardcoded-secrets': 'off', | |||
| 'sonarjs/pseudo-random': 'off', | |||
| // performance | |||
| 'sonarjs/slow-regex': 'warn', | |||
| // others | |||
| 'sonarjs/todo-tag': 'warn', | |||
| 'sonarjs/table-header': 'off', | |||
| }, | |||
| plugins: { | |||
| sonarjs: sonar, | |||
| }, | |||
| }, | |||
| // need further research | |||
| { | |||
| rules: { | |||
| // not exist in old version | |||
| @@ -150,6 +193,7 @@ export default combine( | |||
| // useful, but big change | |||
| 'unicorn/prefer-number-properties': 'warn', | |||
| 'unicorn/no-new-array': 'warn', | |||
| 'style/indent': 'off', | |||
| }, | |||
| }, | |||
| // suppress error for `no-undef` rule | |||
| @@ -57,6 +57,7 @@ async function translateMissingKeyDeeply(sourceObj, targetObject, toLanguage) { | |||
| async function autoGenTrans(fileName, toGenLanguage) { | |||
| const fullKeyFilePath = path.join(__dirname, targetLanguage, `${fileName}.ts`) | |||
| const toGenLanguageFilePath = path.join(__dirname, toGenLanguage, `${fileName}.ts`) | |||
| // eslint-disable-next-line sonarjs/code-eval | |||
| const fullKeyContent = eval(transpile(fs.readFileSync(fullKeyFilePath, 'utf8'))) | |||
| // To keep object format and format it for magicast to work: const translation = { ... } => export default {...} | |||
| const readContent = await loadFile(toGenLanguageFilePath) | |||
| @@ -26,6 +26,7 @@ async function getKeysFromLanuage(language) { | |||
| ) // Convert to camel case | |||
| // console.log(camelCaseFileName) | |||
| const content = fs.readFileSync(filePath, 'utf8') | |||
| // eslint-disable-next-line sonarjs/code-eval | |||
| const translation = eval(transpile(content)) | |||
| // console.log(translation) | |||
| const keys = Object.keys(translation) | |||
| @@ -2,63 +2,6 @@ import type { LangFuseConfig, LangSmithConfig, OpikConfig, TracingProvider } fro | |||
| import type { App, AppMode, AppSSO, AppTemplate, SiteConfig } from '@/types/app' | |||
| import type { Dependency } from '@/app/components/plugins/types' | |||
| /* export type App = { | |||
| id: string | |||
| name: string | |||
| description: string | |||
| mode: AppMode | |||
| enable_site: boolean | |||
| enable_api: boolean | |||
| api_rpm: number | |||
| api_rph: number | |||
| is_demo: boolean | |||
| model_config: AppModelConfig | |||
| providers: Array<{ provider: string; token_is_set: boolean }> | |||
| site: SiteConfig | |||
| created_at: string | |||
| } | |||
| export type AppModelConfig = { | |||
| provider: string | |||
| model_id: string | |||
| configs: { | |||
| prompt_template: string | |||
| prompt_variables: Array<PromptVariable> | |||
| completion_params: CompletionParam | |||
| } | |||
| } | |||
| export type PromptVariable = { | |||
| key: string | |||
| name: string | |||
| description: string | |||
| type: string | number | |||
| default: string | |||
| options: string[] | |||
| } | |||
| export type CompletionParam = { | |||
| max_tokens: number | |||
| temperature: number | |||
| top_p: number | |||
| echo: boolean | |||
| stop: string[] | |||
| presence_penalty: number | |||
| frequency_penalty: number | |||
| } | |||
| export type SiteConfig = { | |||
| access_token: string | |||
| title: string | |||
| author: string | |||
| support_email: string | |||
| default_language: string | |||
| customize_domain: string | |||
| theme: string | |||
| customize_token_strategy: 'must' | 'allow' | 'not_allow' | |||
| prompt_public: boolean | |||
| } */ | |||
| export enum DSLImportMode { | |||
| YAML_CONTENT = 'yaml-content', | |||
| YAML_URL = 'yaml-url', | |||
| @@ -10,6 +10,7 @@ | |||
| "build": "next build", | |||
| "start": "cp -r .next/static .next/standalone/.next/static && cp -r public .next/standalone/public && cross-env PORT=$npm_config_port HOSTNAME=$npm_config_host node .next/standalone/server.js", | |||
| "lint": "pnpm eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache", | |||
| "lint-only-show-error": "pnpm eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --quiet", | |||
| "fix": "next lint --fix", | |||
| "eslint-fix": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix", | |||
| "eslint-fix-only-show-error": "eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache --fix --quiet", | |||
| @@ -181,6 +182,7 @@ | |||
| "eslint-config-next": "^15.0.0", | |||
| "eslint-plugin-react-hooks": "^5.1.0", | |||
| "eslint-plugin-react-refresh": "^0.4.19", | |||
| "eslint-plugin-sonarjs": "^3.0.2", | |||
| "eslint-plugin-storybook": "^0.11.2", | |||
| "eslint-plugin-tailwindcss": "^3.18.0", | |||
| "husky": "^9.1.6", | |||
| @@ -473,6 +473,9 @@ importers: | |||
| eslint-plugin-react-refresh: | |||
| specifier: ^0.4.19 | |||
| version: 0.4.19(eslint@9.24.0(jiti@1.21.7)) | |||
| eslint-plugin-sonarjs: | |||
| specifier: ^3.0.2 | |||
| version: 3.0.2(eslint@9.24.0(jiti@1.21.7)) | |||
| eslint-plugin-storybook: | |||
| specifier: ^0.11.2 | |||
| version: 0.11.6(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) | |||
| @@ -3736,6 +3739,10 @@ packages: | |||
| buffer@6.0.3: | |||
| resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} | |||
| builtin-modules@3.3.0: | |||
| resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} | |||
| engines: {node: '>=6'} | |||
| builtin-modules@5.0.0: | |||
| resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} | |||
| engines: {node: '>=18.20'} | |||
| @@ -3747,6 +3754,10 @@ packages: | |||
| resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} | |||
| engines: {node: '>=10.16.0'} | |||
| bytes@3.1.2: | |||
| resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} | |||
| engines: {node: '>= 0.8'} | |||
| cac@6.7.14: | |||
| resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} | |||
| engines: {node: '>=8'} | |||
| @@ -4864,6 +4875,11 @@ packages: | |||
| peerDependencies: | |||
| eslint: '>=8.44.0' | |||
| eslint-plugin-sonarjs@3.0.2: | |||
| resolution: {integrity: sha512-LxjbfwI7ypENeTmGyKmDyNux3COSkMi7H/6Cal5StSLQ6edf0naP45SZR43OclaNR7WfhVTZdhOn63q3/Y6puQ==} | |||
| peerDependencies: | |||
| eslint: ^8.0.0 || ^9.0.0 | |||
| eslint-plugin-storybook@0.11.6: | |||
| resolution: {integrity: sha512-3WodYD6Bs9ACqnB+TP2TuLh774c/nacAjxSKOP9bHJ2c8rf+nrhocxjjeAWNmO9IPkFIzTKlcl0vNXI2yYpVOw==} | |||
| engines: {node: '>= 18'} | |||
| @@ -5181,6 +5197,9 @@ packages: | |||
| resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} | |||
| engines: {node: '>= 0.4'} | |||
| functional-red-black-tree@1.0.1: | |||
| resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} | |||
| functions-have-names@1.2.3: | |||
| resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} | |||
| @@ -8115,6 +8134,11 @@ packages: | |||
| engines: {node: '>=4.2.0'} | |||
| hasBin: true | |||
| typescript@5.8.3: | |||
| resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} | |||
| engines: {node: '>=14.17'} | |||
| hasBin: true | |||
| ufo@1.6.1: | |||
| resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} | |||
| @@ -12474,6 +12498,8 @@ snapshots: | |||
| base64-js: 1.5.1 | |||
| ieee754: 1.2.1 | |||
| builtin-modules@3.3.0: {} | |||
| builtin-modules@5.0.0: {} | |||
| builtin-status-codes@3.0.0: {} | |||
| @@ -12482,6 +12508,8 @@ snapshots: | |||
| dependencies: | |||
| streamsearch: 1.1.0 | |||
| bytes@3.1.2: {} | |||
| cac@6.7.14: {} | |||
| cacheable-lookup@5.0.4: {} | |||
| @@ -13904,6 +13932,19 @@ snapshots: | |||
| regexp-ast-analysis: 0.7.1 | |||
| scslre: 0.3.0 | |||
| eslint-plugin-sonarjs@3.0.2(eslint@9.24.0(jiti@1.21.7)): | |||
| dependencies: | |||
| '@eslint-community/regexpp': 4.12.1 | |||
| builtin-modules: 3.3.0 | |||
| bytes: 3.1.2 | |||
| eslint: 9.24.0(jiti@1.21.7) | |||
| functional-red-black-tree: 1.0.1 | |||
| jsx-ast-utils: 3.3.5 | |||
| minimatch: 9.0.5 | |||
| scslre: 0.3.0 | |||
| semver: 7.7.1 | |||
| typescript: 5.8.3 | |||
| eslint-plugin-storybook@0.11.6(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5): | |||
| dependencies: | |||
| '@storybook/csf': 0.1.13 | |||
| @@ -14310,6 +14351,8 @@ snapshots: | |||
| hasown: 2.0.2 | |||
| is-callable: 1.2.7 | |||
| functional-red-black-tree@1.0.1: {} | |||
| functions-have-names@1.2.3: {} | |||
| gauge@3.0.2: | |||
| @@ -18063,6 +18106,8 @@ snapshots: | |||
| typescript@4.9.5: {} | |||
| typescript@5.8.3: {} | |||
| ufo@1.6.1: {} | |||
| uglify-js@3.19.3: {} | |||
| @@ -385,11 +385,11 @@ export const ssePost = ( | |||
| options.body = JSON.stringify(body) | |||
| const accessToken = getAccessToken(isPublicAPI) | |||
| ;(options.headers as Headers).set('Authorization', `Bearer ${accessToken}`) | |||
| ; (options.headers as Headers).set('Authorization', `Bearer ${accessToken}`) | |||
| globalThis.fetch(urlWithPrefix, options as RequestInit) | |||
| .then((res) => { | |||
| if (!/^(2|3)\d{2}$/.test(String(res.status))) { | |||
| if (!/^[23]\d{2}$/.test(String(res.status))) { | |||
| if (res.status === 401) { | |||
| refreshAccessTokenOrRelogin(TIME_OUT).then(() => { | |||
| ssePost(url, fetchOptions, otherOptions) | |||
| @@ -34,7 +34,7 @@ export type ResponseError = { | |||
| const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook => { | |||
| return async (_request, _options, response) => { | |||
| const clonedResponse = response.clone() | |||
| if (!/^(2|3)\d{2}$/.test(String(clonedResponse.status))) { | |||
| if (!/^([23])\d{2}$/.test(String(clonedResponse.status))) { | |||
| const bodyJson = clonedResponse.json() as Promise<ResponseError> | |||
| switch (clonedResponse.status) { | |||
| case 403: | |||
| @@ -109,10 +109,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | | |||
| export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[]) => { | |||
| const userInputs: UserInputFormItem[] = [] | |||
| promptVariables.filter(({ key, name }) => { | |||
| if (key && key.trim() && name && name.trim()) | |||
| return true | |||
| return false | |||
| return key && key.trim() && name && name.trim() | |||
| }).forEach((item: any) => { | |||
| if (item.type === 'string' || item.type === 'paragraph') { | |||
| userInputs.push({ | |||
| @@ -7,7 +7,7 @@ import { | |||
| } from '@/app/components/base/prompt-editor/constants' | |||
| import { InputVarType } from '@/app/components/workflow/types' | |||
| const otherAllowedRegex = /^[a-zA-Z0-9_]+$/ | |||
| const otherAllowedRegex = /^\w+$/ | |||
| export const getNewVar = (key: string, type: string) => { | |||
| const { ...rest } = VAR_ITEM_TEMPLATE | |||
| @@ -56,7 +56,7 @@ export const checkKey = (key: string, canBeEmpty?: boolean) => { | |||
| return 'tooLong' | |||
| if (otherAllowedRegex.test(key)) { | |||
| if (/[0-9]/.test(key[0])) | |||
| if (/\d/.test(key[0])) | |||
| return 'notStartWithNumber' | |||
| return true | |||
| @@ -82,7 +82,7 @@ export const checkKeys = (keys: string[], canBeEmpty?: boolean) => { | |||
| return { isValid, errorKey, errorMessageKey } | |||
| } | |||
| const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g | |||
| const varRegex = /\{\{([a-zA-Z_]\w*)\}\}/g | |||
| export const getVars = (value: string) => { | |||
| if (!value) | |||
| return [] | |||