| - name: Web style check | - name: Web style check | ||||
| if: steps.changed-files.outputs.any_changed == 'true' | if: steps.changed-files.outputs.any_changed == 'true' | ||||
| working-directory: ./web | working-directory: ./web | ||||
| run: pnpm run lint | |||||
| run: | | |||||
| pnpm run lint | |||||
| pnpm run eslint | |||||
| docker-compose-template: | docker-compose-template: | ||||
| name: Docker Compose Template | name: Docker Compose Template |
| && !trimmed.startsWith('//')) | && !trimmed.startsWith('//')) | ||||
| break | break | ||||
| } | } | ||||
| else { | |||||
| else { | |||||
| break | break | ||||
| } | } | ||||
| try { | try { | ||||
| validateDescriptionLength(invalidDescription) | validateDescriptionLength(invalidDescription) | ||||
| } | } | ||||
| catch (error) { | |||||
| catch (error) { | |||||
| expect((error as Error).message).toBe(expectedErrorMessage) | expect((error as Error).message).toBe(expectedErrorMessage) | ||||
| } | } | ||||
| }) | }) | ||||
| expect(() => validateDescriptionLength(testDescription)).not.toThrow() | expect(() => validateDescriptionLength(testDescription)).not.toThrow() | ||||
| expect(validateDescriptionLength(testDescription)).toBe(testDescription) | expect(validateDescriptionLength(testDescription)).toBe(testDescription) | ||||
| } | } | ||||
| else { | |||||
| else { | |||||
| expect(() => validateDescriptionLength(testDescription)).toThrow( | expect(() => validateDescriptionLength(testDescription)).toThrow( | ||||
| 'Description cannot exceed 400 characters.', | 'Description cannot exceed 400 characters.', | ||||
| ) | ) |
| const result = aValue.localeCompare(bValue) | const result = aValue.localeCompare(bValue) | ||||
| return order === 'asc' ? result : -result | return order === 'asc' ? result : -result | ||||
| } | } | ||||
| else { | |||||
| else { | |||||
| const result = aValue - bValue | const result = aValue - bValue | ||||
| return order === 'asc' ? result : -result | return order === 'asc' ? result : -result | ||||
| } | } |
| const _pluginId = (tool.uniqueIdentifier as any).split(':')[0] | const _pluginId = (tool.uniqueIdentifier as any).split(':')[0] | ||||
| }).toThrow() | }).toThrow() | ||||
| } | } | ||||
| else { | |||||
| else { | |||||
| // Valid tools should work fine | // Valid tools should work fine | ||||
| expect(() => { | expect(() => { | ||||
| const _pluginId = tool.uniqueIdentifier.split(':')[0] | const _pluginId = tool.uniqueIdentifier.split(':')[0] |
| if (hasStyleChange) | if (hasStyleChange) | ||||
| console.log('⚠️ Style changes detected - this causes visible flicker') | console.log('⚠️ Style changes detected - this causes visible flicker') | ||||
| else | |||||
| else | |||||
| console.log('✅ No style changes detected') | console.log('✅ No style changes detected') | ||||
| expect(timingData.length).toBeGreaterThan(1) | expect(timingData.length).toBeGreaterThan(1) |
| function setupEnvironment(value?: string) { | function setupEnvironment(value?: string) { | ||||
| if (value) | if (value) | ||||
| process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = value | process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = value | ||||
| else | |||||
| else | |||||
| delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT | delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT | ||||
| // Clear module cache to force re-evaluation | // Clear module cache to force re-evaluation | ||||
| function restoreEnvironment() { | function restoreEnvironment() { | ||||
| if (originalEnv) | if (originalEnv) | ||||
| process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = originalEnv | process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = originalEnv | ||||
| else | |||||
| else | |||||
| delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT | delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT | ||||
| jest.resetModules() | jest.resetModules() |
| console.log(` ${index + 1}. ${error.substring(0, 100)}...`) | console.log(` ${index + 1}. ${error.substring(0, 100)}...`) | ||||
| }) | }) | ||||
| } | } | ||||
| else { | |||||
| else { | |||||
| console.log('No inkscape errors found in this render') | console.log('No inkscape errors found in this render') | ||||
| } | } | ||||
| if (problematicKeys.length > 0) | if (problematicKeys.length > 0) | ||||
| console.log(`🚨 PROBLEM: Still found problematic attributes: ${problematicKeys.join(', ')}`) | console.log(`🚨 PROBLEM: Still found problematic attributes: ${problematicKeys.join(', ')}`) | ||||
| else | |||||
| else | |||||
| console.log('✅ No problematic attributes found after normalization') | console.log('✅ No problematic attributes found after normalization') | ||||
| }) | }) | ||||
| }) | }) |
| onClick={() => { | onClick={() => { | ||||
| if (hoverArea === 'right' && !onAvatarError) | if (hoverArea === 'right' && !onAvatarError) | ||||
| setIsShowDeleteConfirm(true) | setIsShowDeleteConfirm(true) | ||||
| else | |||||
| else | |||||
| setIsShowAvatarPicker(true) | setIsShowAvatarPicker(true) | ||||
| }} | }} | ||||
| onMouseMove={(e) => { | onMouseMove={(e) => { |
| </div>, | </div>, | ||||
| dataset: <AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' />, | dataset: <AppIcon innerIcon={DatasetSvg} className='!border-[0.5px] !border-indigo-100 !bg-indigo-25' />, | ||||
| webapp: <div className='rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-brand-blue-brand-500 p-1 shadow-md'> | webapp: <div className='rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-brand-blue-brand-500 p-1 shadow-md'> | ||||
| <WindowCursor className='h-4 w-4 text-text-primary-on-surface' /> | |||||
| </div>, | |||||
| <WindowCursor className='h-4 w-4 text-text-primary-on-surface' /> | |||||
| </div>, | |||||
| notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />, | notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />, | ||||
| } | } | ||||
| }, [appSidebarExpand, setAppSiderbarExpand]) | }, [appSidebarExpand, setAppSiderbarExpand]) | ||||
| if (inWorkflowCanvas && hideHeader) { | if (inWorkflowCanvas && hideHeader) { | ||||
| return ( | |||||
| return ( | |||||
| <div className='flex w-0 shrink-0'> | <div className='flex w-0 shrink-0'> | ||||
| <AppSidebarDropdown navigation={navigation} /> | <AppSidebarDropdown navigation={navigation} /> | ||||
| </div> | </div> | ||||
| ) | ) | ||||
| } | |||||
| } | |||||
| return ( | return ( | ||||
| <div | <div |
| })) | })) | ||||
| }) | }) | ||||
| describe('Issue #1: Toggle Button Position Movement - FIXED', () => { | |||||
| describe('Issue #1: Toggle Button Position Movement - FIXED', () => { | |||||
| it('should verify consistent padding prevents button position shift', () => { | it('should verify consistent padding prevents button position shift', () => { | ||||
| let expanded = false | let expanded = false | ||||
| const handleToggle = () => { | const handleToggle = () => { |
| setList(data as AnnotationItem[]) | setList(data as AnnotationItem[]) | ||||
| setTotal(total) | setTotal(total) | ||||
| } | } | ||||
| finally { | |||||
| finally { | |||||
| setIsLoading(false) | setIsLoading(false) | ||||
| } | } | ||||
| } | } |
| > | > | ||||
| <div className='flex items-center'> | <div className='flex items-center'> | ||||
| <InputVarTypeIcon type={selectedItem?.value as InputVarType} className='size-4 shrink-0 text-text-secondary' /> | <InputVarTypeIcon type={selectedItem?.value as InputVarType} className='size-4 shrink-0 text-text-secondary' /> | ||||
| <span | |||||
| className={` | |||||
| <span | |||||
| className={` | |||||
| ml-1.5 ${!selectedItem?.name && 'text-components-input-text-placeholder'} | ml-1.5 ${!selectedItem?.name && 'text-components-input-text-placeholder'} | ||||
| `} | `} | ||||
| > | |||||
| {selectedItem?.name} | |||||
| </span> | |||||
| > | |||||
| {selectedItem?.name} | |||||
| </span> | |||||
| </div> | </div> | ||||
| <div className='flex items-center space-x-1'> | <div className='flex items-center space-x-1'> | ||||
| <Badge uppercase={false}>{inputVarTypeToVarType(selectedItem?.value as InputVarType)}</Badge> | <Badge uppercase={false}>{inputVarTypeToVarType(selectedItem?.value as InputVarType)}</Badge> |
| ...datasetConfigs, | ...datasetConfigs, | ||||
| reranking_enable: enable, | reranking_enable: enable, | ||||
| }) | }) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [currentRerankModel, datasetConfigs, onChange]) | }, [currentRerankModel, datasetConfigs, onChange]) | ||||
| return ( | return ( |
| > | > | ||||
| <div> | <div> | ||||
| {type !== 'checkbox' && ( | {type !== 'checkbox' && ( | ||||
| <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> | |||||
| <div className='truncate'>{name || key}</div> | |||||
| {!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} | |||||
| </div> | |||||
| <div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'> | |||||
| <div className='truncate'>{name || key}</div> | |||||
| {!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>} | |||||
| </div> | |||||
| )} | )} | ||||
| <div className='grow'> | <div className='grow'> | ||||
| {type === 'string' && ( | {type === 'string' && ( |
| const newChatList: IChatItem[] = [] | const newChatList: IChatItem[] = [] | ||||
| try { | try { | ||||
| messages.forEach((item: ChatMessage) => { | messages.forEach((item: ChatMessage) => { | ||||
| const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || [] | |||||
| newChatList.push({ | |||||
| id: `question-${item.id}`, | |||||
| content: item.inputs.query || item.inputs.default_input || item.query, // text generation: item.inputs.query; chat: item.query | |||||
| isAnswer: false, | |||||
| message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))), | |||||
| parentMessageId: item.parent_message_id || undefined, | |||||
| }) | |||||
| const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || [] | |||||
| newChatList.push({ | |||||
| id: `question-${item.id}`, | |||||
| content: item.inputs.query || item.inputs.default_input || item.query, // text generation: item.inputs.query; chat: item.query | |||||
| isAnswer: false, | |||||
| message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))), | |||||
| parentMessageId: item.parent_message_id || undefined, | |||||
| }) | |||||
| const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] | |||||
| newChatList.push({ | |||||
| id: item.id, | |||||
| content: item.answer, | |||||
| agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files), | |||||
| feedback: item.feedbacks.find(item => item.from_source === 'user'), // user feedback | |||||
| adminFeedback: item.feedbacks.find(item => item.from_source === 'admin'), // admin feedback | |||||
| feedbackDisabled: false, | |||||
| isAnswer: true, | |||||
| message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))), | |||||
| log: [ | |||||
| ...item.message, | |||||
| ...(item.message[item.message.length - 1]?.role !== 'assistant' | |||||
| ? [ | |||||
| { | |||||
| role: 'assistant', | |||||
| text: item.answer, | |||||
| files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], | |||||
| }, | |||||
| ] | |||||
| : []), | |||||
| ] as IChatItem['log'], | |||||
| workflow_run_id: item.workflow_run_id, | |||||
| conversationId, | |||||
| input: { | |||||
| inputs: item.inputs, | |||||
| query: item.query, | |||||
| }, | |||||
| more: { | |||||
| time: dayjs.unix(item.created_at).tz(timezone).format(format), | |||||
| tokens: item.answer_tokens + item.message_tokens, | |||||
| latency: item.provider_response_latency.toFixed(2), | |||||
| }, | |||||
| citation: item.metadata?.retriever_resources, | |||||
| annotation: (() => { | |||||
| if (item.annotation_hit_history) { | |||||
| return { | |||||
| id: item.annotation_hit_history.annotation_id, | |||||
| authorName: item.annotation_hit_history.annotation_create_account?.name || 'N/A', | |||||
| created_at: item.annotation_hit_history.created_at, | |||||
| const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] | |||||
| newChatList.push({ | |||||
| id: item.id, | |||||
| content: item.answer, | |||||
| agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files), | |||||
| feedback: item.feedbacks.find(item => item.from_source === 'user'), // user feedback | |||||
| adminFeedback: item.feedbacks.find(item => item.from_source === 'admin'), // admin feedback | |||||
| feedbackDisabled: false, | |||||
| isAnswer: true, | |||||
| message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))), | |||||
| log: [ | |||||
| ...item.message, | |||||
| ...(item.message[item.message.length - 1]?.role !== 'assistant' | |||||
| ? [ | |||||
| { | |||||
| role: 'assistant', | |||||
| text: item.answer, | |||||
| files: item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], | |||||
| }, | |||||
| ] | |||||
| : []), | |||||
| ] as IChatItem['log'], | |||||
| workflow_run_id: item.workflow_run_id, | |||||
| conversationId, | |||||
| input: { | |||||
| inputs: item.inputs, | |||||
| query: item.query, | |||||
| }, | |||||
| more: { | |||||
| time: dayjs.unix(item.created_at).tz(timezone).format(format), | |||||
| tokens: item.answer_tokens + item.message_tokens, | |||||
| latency: item.provider_response_latency.toFixed(2), | |||||
| }, | |||||
| citation: item.metadata?.retriever_resources, | |||||
| annotation: (() => { | |||||
| if (item.annotation_hit_history) { | |||||
| return { | |||||
| id: item.annotation_hit_history.annotation_id, | |||||
| authorName: item.annotation_hit_history.annotation_create_account?.name || 'N/A', | |||||
| created_at: item.annotation_hit_history.created_at, | |||||
| } | |||||
| } | } | ||||
| } | |||||
| if (item.annotation) { | |||||
| return { | |||||
| id: item.annotation.id, | |||||
| authorName: item.annotation.account.name, | |||||
| logAnnotation: item.annotation, | |||||
| created_at: 0, | |||||
| if (item.annotation) { | |||||
| return { | |||||
| id: item.annotation.id, | |||||
| authorName: item.annotation.account.name, | |||||
| logAnnotation: item.annotation, | |||||
| created_at: 0, | |||||
| } | |||||
| } | } | ||||
| } | |||||
| return undefined | |||||
| })(), | |||||
| parentMessageId: `question-${item.id}`, | |||||
| return undefined | |||||
| })(), | |||||
| parentMessageId: `question-${item.id}`, | |||||
| }) | |||||
| }) | }) | ||||
| }) | |||||
| return newChatList | return newChatList | ||||
| } | } | ||||
| setThreadChatItems(getThreadMessages(tree, newAllChatItems.at(-1)?.id)) | setThreadChatItems(getThreadMessages(tree, newAllChatItems.at(-1)?.id)) | ||||
| } | } | ||||
| catch (error) { | |||||
| catch (error) { | |||||
| console.error(error) | console.error(error) | ||||
| setHasMore(false) | setHasMore(false) | ||||
| } | } | ||||
| if (outerDiv && outerDiv.scrollHeight > outerDiv.clientHeight) { | if (outerDiv && outerDiv.scrollHeight > outerDiv.clientHeight) { | ||||
| scrollContainer = outerDiv | scrollContainer = outerDiv | ||||
| } | } | ||||
| else if (scrollableDiv && scrollableDiv.scrollHeight > scrollableDiv.clientHeight) { | |||||
| else if (scrollableDiv && scrollableDiv.scrollHeight > scrollableDiv.clientHeight) { | |||||
| scrollContainer = scrollableDiv | scrollContainer = scrollableDiv | ||||
| } | } | ||||
| else if (chatContainer && chatContainer.scrollHeight > chatContainer.clientHeight) { | else if (chatContainer && chatContainer.scrollHeight > chatContainer.clientHeight) { |
| setAppDetail(res) | setAppDetail(res) | ||||
| setShowAccessControl(false) | setShowAccessControl(false) | ||||
| } | } | ||||
| catch (error) { | |||||
| catch (error) { | |||||
| console.error('Failed to fetch app detail:', error) | console.error('Failed to fetch app detail:', error) | ||||
| } | } | ||||
| }, [appDetail, setAppDetail]) | }, [appDetail, setAppDetail]) |
| `<script> | `<script> | ||||
| window.difyChatbotConfig = { | window.difyChatbotConfig = { | ||||
| token: '${token}'${isTestEnv | token: '${token}'${isTestEnv | ||||
| ? `, | |||||
| ? `, | |||||
| isDev: true` | isDev: true` | ||||
| : ''}${IS_CE_EDITION | |||||
| ? `, | |||||
| : ''}${IS_CE_EDITION | |||||
| ? `, | |||||
| baseUrl: '${url}${basePath}'` | baseUrl: '${url}${basePath}'` | ||||
| : ''}, | |||||
| : ''}, | |||||
| inputs: { | inputs: { | ||||
| // You can define the inputs from the Start node here | // You can define the inputs from the Start node here | ||||
| // key is the variable name | // key is the variable name |
| <Link href={docLink('/guides/application-publishing/launch-your-webapp-quickly/README', { | <Link href={docLink('/guides/application-publishing/launch-your-webapp-quickly/README', { | ||||
| 'zh-Hans': '/guides/application-publishing/launch-your-webapp-quickly/readme', | 'zh-Hans': '/guides/application-publishing/launch-your-webapp-quickly/readme', | ||||
| })} | })} | ||||
| target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> | |||||
| target='_blank' rel='noopener noreferrer' className='text-text-accent'>{t('common.operation.learnMore')}</Link> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {/* form body */} | {/* form body */} |
| const List = () => { | const List = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { systemFeatures } = useGlobalPublicStore() | |||||
| const { systemFeatures } = useGlobalPublicStore() | |||||
| const router = useRouter() | const router = useRouter() | ||||
| const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() | const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() | ||||
| const showTagManagementModal = useTagStore(s => s.showTagManagementModal) | const showTagManagementModal = useTagStore(s => s.showTagManagementModal) |
| {visibleInputsForms.map(form => ( | {visibleInputsForms.map(form => ( | ||||
| <div key={form.variable} className='space-y-1'> | <div key={form.variable} className='space-y-1'> | ||||
| {form.type !== InputVarType.checkbox && ( | {form.type !== InputVarType.checkbox && ( | ||||
| <div className='flex h-6 items-center gap-1'> | |||||
| <div className='system-md-semibold text-text-secondary'>{form.label}</div> | |||||
| {!form.required && ( | |||||
| <div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div> | |||||
| )} | |||||
| </div> | |||||
| <div className='flex h-6 items-center gap-1'> | |||||
| <div className='system-md-semibold text-text-secondary'>{form.label}</div> | |||||
| {!form.required && ( | |||||
| <div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div> | |||||
| )} | |||||
| </div> | |||||
| )} | )} | ||||
| {form.type === InputVarType.textInput && ( | {form.type === InputVarType.textInput && ( | ||||
| <Input | <Input |
| } | } | ||||
| const Checkbox = ({ | const Checkbox = ({ | ||||
| id, | |||||
| checked, | |||||
| onCheck, | |||||
| className, | |||||
| disabled, | |||||
| indeterminate, | |||||
| id, | |||||
| checked, | |||||
| onCheck, | |||||
| className, | |||||
| disabled, | |||||
| indeterminate, | |||||
| }: CheckboxProps) => { | }: CheckboxProps) => { | ||||
| const checkClassName = (checked || indeterminate) | const checkClassName = (checked || indeterminate) | ||||
| ? 'bg-components-checkbox-bg text-components-checkbox-icon hover:bg-components-checkbox-bg-hover' | ? 'bg-components-checkbox-bg text-components-checkbox-icon hover:bg-components-checkbox-bg-hover' |
| // Output format with time | // Output format with time | ||||
| return date.format('YYYY-MM-DDTHH:mm:ss.SSSZ') | return date.format('YYYY-MM-DDTHH:mm:ss.SSSZ') | ||||
| } | } | ||||
| else { | |||||
| else { | |||||
| // Date-only output format without timezone | // Date-only output format without timezone | ||||
| return date.format('YYYY-MM-DD') | return date.format('YYYY-MM-DD') | ||||
| } | } |
| }, | }, | ||||
| }) | }) | ||||
| const name = useStore(form.store, state => state.values.name) | |||||
| const name = useStore(form.store, state => state.values.name) | |||||
| return ( | return ( | ||||
| <form | <form |
| needCheckValidatedValues?: boolean | needCheckValidatedValues?: boolean | ||||
| } | } | ||||
| export type FormRefObject = { | export type FormRefObject = { | ||||
| getForm: () => AnyFormApi | |||||
| getFormValues: (obj: GetValuesOptions) => { | |||||
| values: Record<string, any> | |||||
| isCheckValidated: boolean | |||||
| } | |||||
| getForm: () => AnyFormApi | |||||
| getFormValues: (obj: GetValuesOptions) => { | |||||
| values: Record<string, any> | |||||
| isCheckValidated: boolean | |||||
| } | |||||
| } | } | ||||
| export type FormRef = ForwardedRef<FormRefObject> | export type FormRef = ForwardedRef<FormRefObject> |
| {isLoading && !svgString && ( | {isLoading && !svgString && ( | ||||
| <div className='px-[26px] py-4'> | <div className='px-[26px] py-4'> | ||||
| <LoadingAnim type='text'/> | <LoadingAnim type='text'/> | ||||
| <div className="mt-2 text-sm text-gray-500"> | |||||
| {t('common.wait_for_completion', 'Waiting for diagram code to complete...')} | |||||
| </div> | |||||
| <div className="mt-2 text-sm text-gray-500"> | |||||
| {t('common.wait_for_completion', 'Waiting for diagram code to complete...')} | |||||
| </div> | |||||
| </div> | </div> | ||||
| )} | )} | ||||
| const Icon = generatorType === GeneratorType.prompt ? MagicEdit : CodeAssistant | const Icon = generatorType === GeneratorType.prompt ? MagicEdit : CodeAssistant | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!editor.hasNodes([CurrentBlockNode])) | |||||
| throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') | |||||
| }, [editor]) | |||||
| if (!editor.hasNodes([CurrentBlockNode])) | |||||
| throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') | |||||
| }, [editor]) | |||||
| return ( | return ( | ||||
| <div | <div |
| const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_ERROR_MESSAGE_COMMAND) | const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_ERROR_MESSAGE_COMMAND) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!editor.hasNodes([ErrorMessageBlockNode])) | |||||
| throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') | |||||
| }, [editor]) | |||||
| if (!editor.hasNodes([ErrorMessageBlockNode])) | |||||
| throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') | |||||
| }, [editor]) | |||||
| return ( | return ( | ||||
| <div | <div |
| const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_LAST_RUN_COMMAND) | const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_LAST_RUN_COMMAND) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!editor.hasNodes([LastRunBlockNode])) | |||||
| throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') | |||||
| }, [editor]) | |||||
| if (!editor.hasNodes([LastRunBlockNode])) | |||||
| throw new Error('WorkflowVariableBlockPlugin: WorkflowVariableBlock not registered on editor') | |||||
| }, [editor]) | |||||
| return ( | return ( | ||||
| <div | <div |
| {renderTrigger && <ListboxButton className='w-full'>{renderTrigger(selectedItem)}</ListboxButton>} | {renderTrigger && <ListboxButton className='w-full'>{renderTrigger(selectedItem)}</ListboxButton>} | ||||
| {!renderTrigger && ( | {!renderTrigger && ( | ||||
| <ListboxButton onClick={() => { | <ListboxButton onClick={() => { | ||||
| onOpenChange?.(open) | |||||
| onOpenChange?.(open) | |||||
| }} className={classNames(`flex h-full w-full items-center rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 focus-visible:bg-state-base-hover-alt focus-visible:outline-none group-hover/simple-select:bg-state-base-hover-alt sm:text-sm sm:leading-6 ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}> | }} className={classNames(`flex h-full w-full items-center rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 focus-visible:bg-state-base-hover-alt focus-visible:outline-none group-hover/simple-select:bg-state-base-hover-alt sm:text-sm sm:leading-6 ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}> | ||||
| <span className={classNames('system-sm-regular block truncate text-left text-components-input-text-filled', !selectedItem?.name && 'text-components-input-text-placeholder')}>{selectedItem?.name ?? localPlaceholder}</span> | <span className={classNames('system-sm-regular block truncate text-left text-components-input-text-filled', !selectedItem?.name && 'text-components-input-text-placeholder')}>{selectedItem?.name ?? localPlaceholder}</span> | ||||
| <span className="absolute inset-y-0 right-0 flex items-center pr-2"> | <span className="absolute inset-y-0 right-0 flex items-center pr-2"> | ||||
| {isLoading ? <RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' /> | {isLoading ? <RiLoader4Line className='h-3.5 w-3.5 animate-spin text-text-secondary' /> | ||||
| : (selectedItem && !notClearable) | |||||
| ? ( | |||||
| <XMarkIcon | |||||
| onClick={(e) => { | |||||
| e.stopPropagation() | |||||
| setSelectedItem(null) | |||||
| onSelect({ name: '', value: '' }) | |||||
| }} | |||||
| className="h-4 w-4 cursor-pointer text-text-quaternary" | |||||
| aria-hidden="false" | |||||
| /> | |||||
| ) | |||||
| : ( | |||||
| open ? ( | |||||
| <ChevronUpIcon | |||||
| className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary" | |||||
| aria-hidden="true" | |||||
| /> | |||||
| ) : ( | |||||
| <ChevronDownIcon | |||||
| className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary" | |||||
| aria-hidden="true" | |||||
| : (selectedItem && !notClearable) | |||||
| ? ( | |||||
| <XMarkIcon | |||||
| onClick={(e) => { | |||||
| e.stopPropagation() | |||||
| setSelectedItem(null) | |||||
| onSelect({ name: '', value: '' }) | |||||
| }} | |||||
| className="h-4 w-4 cursor-pointer text-text-quaternary" | |||||
| aria-hidden="false" | |||||
| /> | /> | ||||
| ) | ) | ||||
| )} | |||||
| : ( | |||||
| open ? ( | |||||
| <ChevronUpIcon | |||||
| className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary" | |||||
| aria-hidden="true" | |||||
| /> | |||||
| ) : ( | |||||
| <ChevronDownIcon | |||||
| className="h-4 w-4 text-text-quaternary group-hover/simple-select:text-text-secondary" | |||||
| aria-hidden="true" | |||||
| /> | |||||
| ) | |||||
| )} | |||||
| </span> | </span> | ||||
| </ListboxButton> | </ListboxButton> | ||||
| )} | )} |
| const res = await fetchTagList(type) | const res = await fetchTagList(type) | ||||
| setTagList(res) | setTagList(res) | ||||
| } | } | ||||
| catch (error) { | |||||
| catch (error) { | |||||
| setTagList([]) | setTagList([]) | ||||
| } | } | ||||
| } | } |
| || (type === 'warning' && 'bg-toast-warning-bg') | || (type === 'warning' && 'bg-toast-warning-bg') | ||||
| || (type === 'error' && 'bg-toast-error-bg') | || (type === 'error' && 'bg-toast-error-bg') | ||||
| || (type === 'info' && 'bg-toast-info-bg') | || (type === 'info' && 'bg-toast-info-bg') | ||||
| }`} | |||||
| }`} | |||||
| /> | /> | ||||
| <div className={`flex ${size === 'md' ? 'gap-1' : 'gap-0.5'}`}> | <div className={`flex ${size === 'md' ? 'gap-1' : 'gap-0.5'}`}> | ||||
| <div className={`flex items-center justify-center ${size === 'md' ? 'p-0.5' : 'p-1'}`}> | <div className={`flex items-center justify-center ${size === 'md' ? 'p-0.5' : 'p-1'}`}> |
| ...value, | ...value, | ||||
| reranking_enable: enable, | reranking_enable: enable, | ||||
| }) | }) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [currentModel, onChange, value]) | }, [currentModel, onChange, value]) | ||||
| const rerankModel = useMemo(() => { | const rerankModel = useMemo(() => { |
| useEffect(() => { | useEffect(() => { | ||||
| if (controlFoldOptions) | if (controlFoldOptions) | ||||
| foldHide() | foldHide() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [controlFoldOptions]) | }, [controlFoldOptions]) | ||||
| return ( | return ( | ||||
| <div className={cn(className, !fold ? 'mb-0' : 'mb-3')}> | <div className={cn(className, !fold ? 'mb-0' : 'mb-3')}> |
| checkSetApiKey().then(() => { | checkSetApiKey().then(() => { | ||||
| setIsLoaded(true) | setIsLoaded(true) | ||||
| }) | }) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const handleOnConfig = useCallback(() => { | const handleOnConfig = useCallback(() => { | ||||
| setShowAccountSettingModal({ | setShowAccountSettingModal({ | ||||
| <span className={cn(s.jinaLogo, 'mr-2')}/> | <span className={cn(s.jinaLogo, 'mr-2')}/> | ||||
| <span>Jina Reader</span> | <span>Jina Reader</span> | ||||
| </button>} | </button>} | ||||
| {ENABLE_WEBSITE_FIRECRAWL && <button | |||||
| {ENABLE_WEBSITE_FIRECRAWL && <button | |||||
| className={cn('rounded-lg px-4 py-2', | className={cn('rounded-lg px-4 py-2', | ||||
| selectedProvider === DataSourceProvider.fireCrawl | selectedProvider === DataSourceProvider.fireCrawl | ||||
| ? 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary' | ? 'system-sm-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary' |
| useEffect(() => { | useEffect(() => { | ||||
| if (controlFoldOptions) | if (controlFoldOptions) | ||||
| foldHide() | foldHide() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [controlFoldOptions]) | }, [controlFoldOptions]) | ||||
| return ( | return ( | ||||
| <div className={cn(className, !fold ? 'mb-0' : 'mb-3')}> | <div className={cn(className, !fold ? 'mb-0' : 'mb-3')}> |
| const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => { | const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||
| const currentFile = e.target.files?.[0] | const currentFile = e.target.files?.[0] | ||||
| if (!isValid(currentFile)) | if (!isValid(currentFile)) | ||||
| return | |||||
| return | |||||
| initialUpload(currentFile) | initialUpload(currentFile) | ||||
| } | } |
| {t('dataset.connectDatasetIntro.content.end')} | {t('dataset.connectDatasetIntro.content.end')} | ||||
| </span> | </span> | ||||
| <a className='system-sm-regular self-stretch text-text-accent' | <a className='system-sm-regular self-stretch text-text-accent' | ||||
| href={docLink('/guides/knowledge-base/connect-external-knowledge-base')} | |||||
| target='_blank' | |||||
| rel="noopener noreferrer"> | |||||
| href={docLink('/guides/knowledge-base/connect-external-knowledge-base')} | |||||
| target='_blank' | |||||
| rel="noopener noreferrer"> | |||||
| {t('dataset.connectDatasetIntro.learnMore')} | {t('dataset.connectDatasetIntro.learnMore')} | ||||
| </a> | </a> | ||||
| </p> | </p> |
| <div> | <div> | ||||
| <div className='flex gap-x-1'> | <div className='flex gap-x-1'> | ||||
| <div className='w-4 shrink-0 text-[13px] font-medium leading-[20px] text-text-tertiary'>Q</div> | <div className='w-4 shrink-0 text-[13px] font-medium leading-[20px] text-text-tertiary'>Q</div> | ||||
| <div className={cn('body-md-regular text-text-secondary line-clamp-20')}> | |||||
| <div className={cn('body-md-regular line-clamp-20 text-text-secondary')}> | |||||
| {content} | {content} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className='flex gap-x-1'> | <div className='flex gap-x-1'> | ||||
| <div className='w-4 shrink-0 text-[13px] font-medium leading-[20px] text-text-tertiary'>A</div> | <div className='w-4 shrink-0 text-[13px] font-medium leading-[20px] text-text-tertiary'>A</div> | ||||
| <div className={cn('body-md-regular text-text-secondary line-clamp-20')}> | |||||
| <div className={cn('body-md-regular line-clamp-20 text-text-secondary')}> | |||||
| {answer} | {answer} | ||||
| </div> | </div> | ||||
| </div> | </div> |
| showEditModal() | showEditModal() | ||||
| localStorage.removeItem(isShowManageMetadataLocalStorageKey) | localStorage.removeItem(isShowManageMetadataLocalStorageKey) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const { data: datasetMetaData } = useDatasetMetaData(datasetId) | const { data: datasetMetaData } = useDatasetMetaData(datasetId) |
| try { | try { | ||||
| return await handler.search(args, locale) | return await handler.search(args, locale) | ||||
| } | } | ||||
| catch (error) { | |||||
| catch (error) { | |||||
| console.warn(`Command search failed for ${commandName}:`, error) | console.warn(`Command search failed for ${commandName}:`, error) | ||||
| return [] | return [] | ||||
| } | } | ||||
| try { | try { | ||||
| return await handler.search(args, locale) | return await handler.search(args, locale) | ||||
| } | } | ||||
| catch (error) { | |||||
| catch (error) { | |||||
| console.warn(`Command search failed for ${handler.name}:`, error) | console.warn(`Command search failed for ${handler.name}:`, error) | ||||
| return [] | return [] | ||||
| } | } |
| const results = await action.search(query, query, locale) | const results = await action.search(query, query, locale) | ||||
| return { success: true, data: results, actionType: action.key } | return { success: true, data: results, actionType: action.key } | ||||
| } | } | ||||
| catch (error) { | |||||
| catch (error) { | |||||
| console.warn(`Search failed for ${action.key}:`, error) | console.warn(`Search failed for ${action.key}:`, error) | ||||
| return { success: false, data: [], actionType: action.key, error } | return { success: false, data: [], actionType: action.key, error } | ||||
| } | } | ||||
| if (result.status === 'fulfilled' && result.value.success) { | if (result.status === 'fulfilled' && result.value.success) { | ||||
| allResults.push(...result.value.data) | allResults.push(...result.value.data) | ||||
| } | } | ||||
| else { | |||||
| else { | |||||
| const actionKey = globalSearchActions[index]?.key || 'unknown' | const actionKey = globalSearchActions[index]?.key || 'unknown' | ||||
| failedActions.push(actionKey) | failedActions.push(actionKey) | ||||
| } | } |
| acc[result.type].push(result) | acc[result.type].push(result) | ||||
| return acc | return acc | ||||
| }, {} as { [key: string]: SearchResult[] }), | }, {} as { [key: string]: SearchResult[] }), | ||||
| [searchResults]) | |||||
| [searchResults]) | |||||
| const emptyResult = useMemo(() => { | const emptyResult = useMemo(() => { | ||||
| if (searchResults.length || !searchQuery.trim() || isLoading || isCommandsMode) | if (searchResults.length || !searchQuery.trim() || isLoading || isCommandsMode) |
| useEffect(() => { | useEffect(() => { | ||||
| checkSetApiKey() | checkSetApiKey() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const [configTarget, setConfigTarget] = useState<DataSourceProvider | null>(null) | const [configTarget, setConfigTarget] = useState<DataSourceProvider | null>(null) |
| asChild | asChild | ||||
| popupContent={t('plugin.auth.credentialUnavailable')} | popupContent={t('plugin.auth.credentialUnavailable')} | ||||
| > | > | ||||
| {Item} | |||||
| </Tooltip> | |||||
| ) | |||||
| {Item} | |||||
| </Tooltip> | |||||
| ) | |||||
| } | } | ||||
| return Item | return Item | ||||
| }, [notAllowCustomCredential, t, customModel]) | }, [notAllowCustomCredential, t, customModel]) |
| onClick={() => handleEdit( | onClick={() => handleEdit( | ||||
| undefined, | undefined, | ||||
| currentCustomConfigurationModelFixedFields | currentCustomConfigurationModelFixedFields | ||||
| ? { | |||||
| model: currentCustomConfigurationModelFixedFields.__model_name, | |||||
| model_type: currentCustomConfigurationModelFixedFields.__model_type, | |||||
| } | |||||
| : undefined, | |||||
| ? { | |||||
| model: currentCustomConfigurationModelFixedFields.__model_name, | |||||
| model_type: currentCustomConfigurationModelFixedFields.__model_type, | |||||
| } | |||||
| : undefined, | |||||
| )} | )} | ||||
| className='system-xs-medium flex h-[30px] cursor-pointer items-center px-3 text-text-accent-light-mode-only' | className='system-xs-medium flex h-[30px] cursor-pointer items-center px-3 text-text-accent-light-mode-only' | ||||
| > | > |
| <div className='grow'> | <div className='grow'> | ||||
| <div className='text-sm text-text-secondary'>{ | <div className='text-sm text-text-secondary'>{ | ||||
| providerFormSchemaPredefined | providerFormSchemaPredefined | ||||
| ? t('common.modelProvider.auth.providerManaged') | |||||
| : t('common.modelProvider.auth.specifyModelCredential') | |||||
| ? t('common.modelProvider.auth.providerManaged') | |||||
| : t('common.modelProvider.auth.specifyModelCredential') | |||||
| }</div> | }</div> | ||||
| <div className='text-xs text-text-tertiary'>{ | <div className='text-xs text-text-tertiary'>{ | ||||
| providerFormSchemaPredefined | providerFormSchemaPredefined |
| onFetchedPayload(payload) | onFetchedPayload(payload) | ||||
| setPayload({ ...payload, from: dependency.type }) | setPayload({ ...payload, from: dependency.type }) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [data]) | }, [data]) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (error) | if (error) | ||||
| onFetchError() | onFetchError() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [error]) | }, [error]) | ||||
| if (!payload) return <Loading /> | if (!payload) return <Loading /> | ||||
| return ( | return ( |
| if (failedIndex.length > 0) | if (failedIndex.length > 0) | ||||
| setErrorIndexes([...errorIndexes, ...failedIndex]) | setErrorIndexes([...errorIndexes, ...failedIndex]) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [isFetchingMarketplaceDataById]) | }, [isFetchingMarketplaceDataById]) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (failedIndex.length > 0) | if (failedIndex.length > 0) | ||||
| setErrorIndexes([...errorIndexes, ...failedIndex]) | setErrorIndexes([...errorIndexes, ...failedIndex]) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [isFetchingDataByMeta]) | }, [isFetchingDataByMeta]) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| // get info all failed | // get info all failed | ||||
| if (infoByMetaError || infoByIdError) | if (infoByMetaError || infoByIdError) | ||||
| setErrorIndexes([...errorIndexes, ...marketPlaceInDSLIndex]) | setErrorIndexes([...errorIndexes, ...marketPlaceInDSLIndex]) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [infoByMetaError, infoByIdError]) | }, [infoByMetaError, infoByIdError]) | ||||
| const isLoadedAllData = (plugins.filter(p => !!p).length + errorIndexes.length) === allPlugins.length | const isLoadedAllData = (plugins.filter(p => !!p).length + errorIndexes.length) === allPlugins.length | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (isLoadedAllData && installedInfo) | if (isLoadedAllData && installedInfo) | ||||
| onLoadedAllPlugin(installedInfo!) | onLoadedAllPlugin(installedInfo!) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [isLoadedAllData, installedInfo]) | }, [isLoadedAllData, installedInfo]) | ||||
| const handleSelect = useCallback((index: number) => { | const handleSelect = useCallback((index: number) => { |
| useEffect(() => { | useEffect(() => { | ||||
| if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) | if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) | ||||
| onInstalled() | onInstalled() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [hasInstalled]) | }, [hasInstalled]) | ||||
| const handleInstall = async () => { | const handleInstall = async () => { |
| React.useEffect(() => { | React.useEffect(() => { | ||||
| handleUpload() | handleUpload() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| return ( | return ( | ||||
| <> | <> |
| }) | }) | ||||
| } | } | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [queryPlugins, queryMarketplaceCollectionsAndPlugins, isSuccess, exclude]) | }, [queryPlugins, queryMarketplaceCollectionsAndPlugins, isSuccess, exclude]) | ||||
| const handleQueryMarketplaceCollectionsAndPlugins = useCallback(() => { | const handleQueryMarketplaceCollectionsAndPlugins = useCallback(() => { |
| }, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction]) | }, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction]) | ||||
| const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) | const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) | ||||
| const handleRename = useCallback(async (payload: { | const handleRename = useCallback(async (payload: { | ||||
| credential_id: string | |||||
| name: string | |||||
| }) => { | |||||
| credential_id: string | |||||
| name: string | |||||
| }) => { | |||||
| if (doingActionRef.current) | if (doingActionRef.current) | ||||
| return | return | ||||
| try { | try { | ||||
| !notAllowCustomCredential && ( | !notAllowCustomCredential && ( | ||||
| <> | <> | ||||
| <div className='h-[1px] bg-divider-subtle'></div> | <div className='h-[1px] bg-divider-subtle'></div> | ||||
| <div className='p-2'> | |||||
| <Authorize | |||||
| pluginPayload={pluginPayload} | |||||
| theme='secondary' | |||||
| showDivider={false} | |||||
| canOAuth={canOAuth} | |||||
| canApiKey={canApiKey} | |||||
| disabled={disabled} | |||||
| onUpdate={onUpdate} | |||||
| /> | |||||
| </div> | |||||
| <div className='p-2'> | |||||
| <Authorize | |||||
| pluginPayload={pluginPayload} | |||||
| theme='secondary' | |||||
| showDivider={false} | |||||
| canOAuth={canOAuth} | |||||
| canApiKey={canApiKey} | |||||
| disabled={disabled} | |||||
| onUpdate={onUpdate} | |||||
| /> | |||||
| </div> | |||||
| </> | </> | ||||
| ) | ) | ||||
| } | } |
| }, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction]) | }, [setPluginDefaultCredential, onUpdate, notify, t, handleSetDoingAction]) | ||||
| const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) | const { mutateAsync: updatePluginCredential } = useUpdatePluginCredentialHook(pluginPayload) | ||||
| const handleRename = useCallback(async (payload: { | const handleRename = useCallback(async (payload: { | ||||
| credential_id: string | |||||
| name: string | |||||
| }) => { | |||||
| credential_id: string | |||||
| name: string | |||||
| }) => { | |||||
| if (doingActionRef.current) | if (doingActionRef.current) | ||||
| return | return | ||||
| try { | try { |
| try { | try { | ||||
| await setSize((size: number) => size + 1) | await setSize((size: number) => size + 1) | ||||
| } | } | ||||
| finally { | |||||
| finally { | |||||
| // Add a small delay to ensure state updates are complete | // Add a small delay to ensure state updates are complete | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| setIsLoadingMore(false) | setIsLoadingMore(false) |
| onUpdate, | onUpdate, | ||||
| }: Props) => { | }: Props) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { userProfile: { timezone } } = useAppContext() | |||||
| const { userProfile: { timezone } } = useAppContext() | |||||
| const { theme } = useTheme() | const { theme } = useTheme() | ||||
| const locale = useGetLanguage() | const locale = useGetLanguage() |
| const value = processedCredential[field.name] | const value = processedCredential[field.name] | ||||
| if (typeof value === 'string') | if (typeof value === 'string') | ||||
| processedCredential[field.name] = value === 'true' || value === '1' || value === 'True' | processedCredential[field.name] = value === 'true' || value === '1' || value === 'True' | ||||
| else if (typeof value === 'number') | |||||
| else if (typeof value === 'number') | |||||
| processedCredential[field.name] = value === 1 | processedCredential[field.name] = value === 1 | ||||
| else if (typeof value === 'boolean') | |||||
| else if (typeof value === 'boolean') | |||||
| processedCredential[field.name] = value | processedCredential[field.name] = value | ||||
| } | } | ||||
| }) | }) |
| canChooseMCPTool, | canChooseMCPTool, | ||||
| }: Props) => { | }: Props) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { data: mcpTools } = useAllMCPTools() | |||||
| const { data: mcpTools } = useAllMCPTools() | |||||
| const enabledCount = value.filter((item) => { | const enabledCount = value.filter((item) => { | ||||
| const isMCPTool = mcpTools?.find(tool => tool.id === item.provider_name) | const isMCPTool = mcpTools?.find(tool => tool.id === item.provider_name) | ||||
| if(isMCPTool) | if(isMCPTool) |
| {t('workflow.nodes.agent.clickToViewParameterSchema')} | {t('workflow.nodes.agent.clickToViewParameterSchema')} | ||||
| </div>} | </div>} | ||||
| asChild={false}> | asChild={false}> | ||||
| <div | |||||
| className='ml-0.5 cursor-pointer rounded-[4px] p-px text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary' | |||||
| onClick={() => showSchema(input_schema as SchemaRoot, label[language] || label.en_US)} | |||||
| > | |||||
| <RiBracesLine className='size-3.5'/> | |||||
| </div> | |||||
| <div | |||||
| className='ml-0.5 cursor-pointer rounded-[4px] p-px text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary' | |||||
| onClick={() => showSchema(input_schema as SchemaRoot, label[language] || label.en_US)} | |||||
| > | |||||
| <RiBracesLine className='size-3.5'/> | |||||
| </div> | |||||
| </Tooltip> | </Tooltip> | ||||
| )} | )} | ||||
| hideDeleteConfirm() | hideDeleteConfirm() | ||||
| onDelete() | onDelete() | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [installationId, onDelete]) | }, [installationId, onDelete]) | ||||
| return ( | return ( | ||||
| <div className='flex space-x-1'> | <div className='flex space-x-1'> |
| const renderTimePickerTrigger = useCallback(({ inputElem, onClick, isOpen }: TriggerParams) => { | const renderTimePickerTrigger = useCallback(({ inputElem, onClick, isOpen }: TriggerParams) => { | ||||
| return ( | return ( | ||||
| <div | |||||
| className='group float-right flex h-8 w-[160px] cursor-pointer items-center justify-between rounded-lg bg-components-input-bg-normal px-2 hover:bg-state-base-hover-alt' | |||||
| onClick={onClick} | |||||
| > | |||||
| <div className='flex w-0 grow items-center gap-x-1'> | |||||
| <RiTimeLine className={cn( | |||||
| 'h-4 w-4 shrink-0 text-text-tertiary', | |||||
| isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary', | |||||
| )} /> | |||||
| {inputElem} | |||||
| </div> | |||||
| <div className='system-sm-regular text-text-tertiary'>{convertTimezoneToOffsetStr(timezone)}</div> | |||||
| <div | |||||
| className='group float-right flex h-8 w-[160px] cursor-pointer items-center justify-between rounded-lg bg-components-input-bg-normal px-2 hover:bg-state-base-hover-alt' | |||||
| onClick={onClick} | |||||
| > | |||||
| <div className='flex w-0 grow items-center gap-x-1'> | |||||
| <RiTimeLine className={cn( | |||||
| 'h-4 w-4 shrink-0 text-text-tertiary', | |||||
| isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary', | |||||
| )} /> | |||||
| {inputElem} | |||||
| </div> | </div> | ||||
| <div className='system-sm-regular text-text-tertiary'>{convertTimezoneToOffsetStr(timezone)}</div> | |||||
| </div> | |||||
| ) | ) | ||||
| }, [timezone]) | }, [timezone]) | ||||
| } | } | ||||
| export const convertUTCDaySecondsToLocalSeconds = (utcDaySeconds: number, localTimezone: string): number => { | export const convertUTCDaySecondsToLocalSeconds = (utcDaySeconds: number, localTimezone: string): number => { | ||||
| const utcDayStart = dayjs().utc().startOf('day') | |||||
| const utcTargetTime = utcDayStart.add(utcDaySeconds, 'second') | |||||
| const localTargetTime = utcTargetTime.tz(localTimezone) | |||||
| const localDayStart = localTargetTime.startOf('day') | |||||
| const secondsInLocalDay = localTargetTime.diff(localDayStart, 'second') | |||||
| return secondsInLocalDay | |||||
| const utcDayStart = dayjs().utc().startOf('day') | |||||
| const utcTargetTime = utcDayStart.add(utcDaySeconds, 'second') | |||||
| const localTargetTime = utcTargetTime.tz(localTimezone) | |||||
| const localDayStart = localTargetTime.startOf('day') | |||||
| const secondsInLocalDay = localTargetTime.diff(localDayStart, 'second') | |||||
| return secondsInLocalDay | |||||
| } | } |
| <div className='flex flex-col items-start gap-2 self-stretch'> | <div className='flex flex-col items-start gap-2 self-stretch'> | ||||
| <div className='title-2xl-semi-bold text-text-primary'>{t(`${i18nPrefix}.title`)}</div> | <div className='title-2xl-semi-bold text-text-primary'>{t(`${i18nPrefix}.title`)}</div> | ||||
| <div className='system-md-regular text-text-secondary'> | <div className='system-md-regular text-text-secondary'> | ||||
| {t(`${i18nPrefix}.description`)} | |||||
| {t(`${i18nPrefix}.description`)} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className='mt-9 flex items-start justify-end space-x-2 self-stretch'> | <div className='mt-9 flex items-start justify-end space-x-2 self-stretch'> |
| onExcludeAndDowngrade={handleExcludeAndDownload} | onExcludeAndDowngrade={handleExcludeAndDownload} | ||||
| /> | /> | ||||
| )} | )} | ||||
| {!doShowDowngradeWarningModal && ( | |||||
| <> | |||||
| <div className='system-md-regular mb-2 mt-3 text-text-secondary'> | |||||
| {t(`${i18nPrefix}.description`)} | |||||
| </div> | |||||
| <div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'> | |||||
| <Card | |||||
| installed={uploadStep === UploadStep.installed} | |||||
| payload={pluginManifestToCardPluginProps({ | |||||
| ...originalPackageInfo.payload, | |||||
| icon: icon!, | |||||
| })} | |||||
| className='w-full' | |||||
| titleLeft={ | |||||
| <> | |||||
| <Badge className='mx-1' size="s" state={BadgeState.Warning}> | |||||
| {`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`} | |||||
| </Badge> | |||||
| </> | |||||
| } | |||||
| /> | |||||
| </div> | |||||
| <div className='flex items-center justify-end gap-2 self-stretch pt-5'> | |||||
| {uploadStep === UploadStep.notStarted && ( | |||||
| {!doShowDowngradeWarningModal && ( | |||||
| <> | |||||
| <div className='system-md-regular mb-2 mt-3 text-text-secondary'> | |||||
| {t(`${i18nPrefix}.description`)} | |||||
| </div> | |||||
| <div className='flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2'> | |||||
| <Card | |||||
| installed={uploadStep === UploadStep.installed} | |||||
| payload={pluginManifestToCardPluginProps({ | |||||
| ...originalPackageInfo.payload, | |||||
| icon: icon!, | |||||
| })} | |||||
| className='w-full' | |||||
| titleLeft={ | |||||
| <> | |||||
| <Badge className='mx-1' size="s" state={BadgeState.Warning}> | |||||
| {`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`} | |||||
| </Badge> | |||||
| </> | |||||
| } | |||||
| /> | |||||
| </div> | |||||
| <div className='flex items-center justify-end gap-2 self-stretch pt-5'> | |||||
| {uploadStep === UploadStep.notStarted && ( | |||||
| <Button | |||||
| onClick={handleCancel} | |||||
| > | |||||
| {t('common.operation.cancel')} | |||||
| </Button> | |||||
| )} | |||||
| <Button | <Button | ||||
| onClick={handleCancel} | |||||
| variant='primary' | |||||
| loading={uploadStep === UploadStep.upgrading} | |||||
| onClick={handleConfirm} | |||||
| disabled={uploadStep === UploadStep.upgrading} | |||||
| > | > | ||||
| {t('common.operation.cancel')} | |||||
| {configBtnText} | |||||
| </Button> | </Button> | ||||
| )} | |||||
| <Button | |||||
| variant='primary' | |||||
| loading={uploadStep === UploadStep.upgrading} | |||||
| onClick={handleConfirm} | |||||
| disabled={uploadStep === UploadStep.upgrading} | |||||
| > | |||||
| {configBtnText} | |||||
| </Button> | |||||
| </div> | |||||
| </> | |||||
| )} | |||||
| </div> | |||||
| </> | |||||
| )} | |||||
| </Modal> | </Modal> | ||||
| ) | ) |
| useEffect(() => { | useEffect(() => { | ||||
| if (isTriggerAuthorize) | if (isTriggerAuthorize) | ||||
| handleAuthorize() | handleAuthorize() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| if (!detail) | if (!detail) |
| > | > | ||||
| <div className="flex items-center justify-center gap-[1px]"> | <div className="flex items-center justify-center gap-[1px]"> | ||||
| <RiEditLine className="h-3.5 w-3.5" /> | |||||
| <div className="system-xs-medium px-[3px] text-text-tertiary">{serverPublished ? t('tools.mcp.server.edit') : t('tools.mcp.server.addDescription')}</div> | |||||
| </div> | |||||
| <RiEditLine className="h-3.5 w-3.5" /> | |||||
| <div className="system-xs-medium px-[3px] text-text-tertiary">{serverPublished ? t('tools.mcp.server.edit') : t('tools.mcp.server.addDescription')}</div> | |||||
| </div> | |||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| </div> | </div> |
| if (formSchema.type === 'boolean' && itemValue !== undefined && itemValue !== null && itemValue !== '') { | if (formSchema.type === 'boolean' && itemValue !== undefined && itemValue !== null && itemValue !== '') { | ||||
| if (typeof itemValue === 'string') | if (typeof itemValue === 'string') | ||||
| newValues[formSchema.variable] = itemValue === 'true' || itemValue === '1' || itemValue === 'True' | newValues[formSchema.variable] = itemValue === 'true' || itemValue === '1' || itemValue === 'True' | ||||
| else if (typeof itemValue === 'number') | |||||
| else if (typeof itemValue === 'number') | |||||
| newValues[formSchema.variable] = itemValue === 1 | newValues[formSchema.variable] = itemValue === 1 | ||||
| else if (typeof itemValue === 'boolean') | |||||
| else if (typeof itemValue === 'boolean') | |||||
| newValues[formSchema.variable] = itemValue | newValues[formSchema.variable] = itemValue | ||||
| } | } | ||||
| }) | }) | ||||
| } | } | ||||
| const getVarKindType = (type: FormTypeEnum) => { | const getVarKindType = (type: FormTypeEnum) => { | ||||
| if (type === FormTypeEnum.file || type === FormTypeEnum.files) | |||||
| return VarKindType.variable | |||||
| if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber) | |||||
| return VarKindType.constant | |||||
| if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput) | |||||
| return VarKindType.mixed | |||||
| } | |||||
| if (type === FormTypeEnum.file || type === FormTypeEnum.files) | |||||
| return VarKindType.variable | |||||
| if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber) | |||||
| return VarKindType.constant | |||||
| if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput) | |||||
| return VarKindType.mixed | |||||
| } | |||||
| export const generateAgentToolValue = (value: Record<string, any>, formSchemas: { variable: string; default?: any; type: string }[], isReasoning = false) => { | export const generateAgentToolValue = (value: Record<string, any>, formSchemas: { variable: string; default?: any; type: string }[], isReasoning = false) => { | ||||
| const newValues = {} as any | const newValues = {} as any |
| useEffect(() => { | useEffect(() => { | ||||
| handleGetInitialWorkflowData() | handleGetInitialWorkflowData() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const handleFetchPreloadData = useCallback(async () => { | const handleFetchPreloadData = useCallback(async () => { |
| category: PluginType.tool, | category: PluginType.tool, | ||||
| }) | }) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [searchText, tags, enable_marketplace]) | }, [searchText, tags, enable_marketplace]) | ||||
| const pluginRef = useRef<ListRef>(null) | const pluginRef = useRef<ListRef>(null) |
| downloadFile({ data: blob, fileName }) | downloadFile({ data: blob, fileName }) | ||||
| setNeedDownload(false) | setNeedDownload(false) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [blob]) | }, [blob]) | ||||
| return ( | return ( | ||||
| <PortalToFollowElem | <PortalToFollowElem |
| useEffect(() => { | useEffect(() => { | ||||
| handleScroll() | handleScroll() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [list]) | }, [list]) | ||||
| const handleHeadClick = () => { | const handleHeadClick = () => { |
| } | } | ||||
| if (!hasSearchText && !isFold) | if (!hasSearchText && !isFold) | ||||
| setFold(true) | setFold(true) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [hasSearchText]) | }, [hasSearchText]) | ||||
| const FoldIcon = isFold ? RiArrowRightSLine : RiArrowDownSLine | const FoldIcon = isFold ? RiArrowRightSLine : RiArrowDownSLine |
| }, []) | }, []) | ||||
| if (allDatasetIds.length === 0) return | if (allDatasetIds.length === 0) return | ||||
| updateDatasetsDetail(allDatasetIds) | updateDatasetsDetail(allDatasetIds) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| return ( | return ( |
| <RestoringTitle /> | <RestoringTitle /> | ||||
| </div> | </div> | ||||
| <div className=' flex items-center justify-end gap-x-2'> | <div className=' flex items-center justify-end gap-x-2'> | ||||
| <Button | |||||
| onClick={handleRestore} | |||||
| disabled={!currentVersion || currentVersion.version === WorkflowVersion.Draft} | |||||
| variant='primary' | |||||
| className={cn( | |||||
| theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', | |||||
| )} | |||||
| > | |||||
| {t('workflow.common.restore')} | |||||
| </Button> | |||||
| <Button | |||||
| onClick={handleCancelRestore} | |||||
| className={cn( | |||||
| 'text-components-button-secondary-accent-text', | |||||
| theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', | |||||
| )} | |||||
| > | |||||
| <div className='flex items-center gap-x-0.5'> | |||||
| <RiHistoryLine className='h-4 w-4' /> | |||||
| <span className='px-0.5'>{t('workflow.common.exitVersions')}</span> | |||||
| <Button | |||||
| onClick={handleRestore} | |||||
| disabled={!currentVersion || currentVersion.version === WorkflowVersion.Draft} | |||||
| variant='primary' | |||||
| className={cn( | |||||
| theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', | |||||
| )} | |||||
| > | |||||
| {t('workflow.common.restore')} | |||||
| </Button> | |||||
| <Button | |||||
| onClick={handleCancelRestore} | |||||
| className={cn( | |||||
| 'text-components-button-secondary-accent-text', | |||||
| theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', | |||||
| )} | |||||
| > | |||||
| <div className='flex items-center gap-x-0.5'> | |||||
| <RiHistoryLine className='h-4 w-4' /> | |||||
| <span className='px-0.5'>{t('workflow.common.exitVersions')}</span> | |||||
| </div> | </div> | ||||
| </Button> | </Button> | ||||
| </div> | </div> |
| e.preventDefault() | e.preventDefault() | ||||
| handleViewVersionHistory() | handleViewVersionHistory() | ||||
| }, | }, | ||||
| { exactMatch: true, useCapture: true }) | |||||
| { exactMatch: true, useCapture: true }) | |||||
| return <Tooltip | return <Tooltip | ||||
| popupContent={<PopupContent />} | popupContent={<PopupContent />} | ||||
| popupClassName='rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg | popupClassName='rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg | ||||
| shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px] p-1.5' | shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px] p-1.5' | ||||
| > | > | ||||
| <Button | |||||
| className={cn( | |||||
| 'p-2', | |||||
| theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', | |||||
| )} | |||||
| onClick={handleViewVersionHistory} | |||||
| > | |||||
| <RiHistoryLine className='h-4 w-4 text-components-button-secondary-text' /> | |||||
| </Button> | |||||
| <Button | |||||
| className={cn( | |||||
| 'p-2', | |||||
| theme === 'dark' && 'rounded-lg border border-black/5 bg-white/10 backdrop-blur-sm', | |||||
| )} | |||||
| onClick={handleViewVersionHistory} | |||||
| > | |||||
| <RiHistoryLine className='h-4 w-4 text-components-button-secondary-text' /> | |||||
| </Button> | |||||
| </Tooltip> | </Tooltip> | ||||
| } | } | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (storeRef.current && d3Selection && d3Zoom) | if (storeRef.current && d3Selection && d3Zoom) | ||||
| storeRef.current.getState().refreshAll(restProps) | storeRef.current.getState().refreshAll(restProps) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [d3Selection, d3Zoom]) | }, [d3Selection, d3Zoom]) | ||||
| if (!storeRef.current) | if (!storeRef.current) |
| if (!node) | if (!node) | ||||
| return undefined | return undefined | ||||
| const varId = node.vars.find((varItem) => { | const varId = node.vars.find((varItem) => { | ||||
| return varItem.selector[1] === varName | |||||
| })?.id | |||||
| return varId | |||||
| return varItem.selector[1] === varName | |||||
| })?.id | |||||
| return varId | |||||
| }, [getNodeInspectVars]) | }, [getNodeInspectVars]) | ||||
| const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => { | const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => { | ||||
| }, [getNodeInspectVars]) | }, [getNodeInspectVars]) | ||||
| const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => { | const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => { | ||||
| const isEnv = isENV([nodeId]) | |||||
| if (isEnv) // always have value | |||||
| return true | |||||
| const isSys = isSystemVar([nodeId]) | |||||
| if (isSys) | |||||
| return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name) | |||||
| const isChatVar = isConversationVar([nodeId]) | |||||
| if (isChatVar) | |||||
| return conversationVars.some(varItem => varItem.selector?.[1] === name) | |||||
| return getInspectVar(nodeId, name) !== undefined | |||||
| const isEnv = isENV([nodeId]) | |||||
| if (isEnv) // always have value | |||||
| return true | |||||
| const isSys = isSystemVar([nodeId]) | |||||
| if (isSys) | |||||
| return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name) | |||||
| const isChatVar = isConversationVar([nodeId]) | |||||
| if (isChatVar) | |||||
| return conversationVars.some(varItem => varItem.selector?.[1] === name) | |||||
| return getInspectVar(nodeId, name) !== undefined | |||||
| }, [getInspectVar]) | }, [getInspectVar]) | ||||
| const hasNodeInspectVars = useCallback((nodeId: string) => { | const hasNodeInspectVars = useCallback((nodeId: string) => { | ||||
| } = workflowStore.getState() | } = workflowStore.getState() | ||||
| const nodes = produce(nodesWithInspectVars, (draft) => { | const nodes = produce(nodesWithInspectVars, (draft) => { | ||||
| const nodeInfo = allNodes.find(node => node.id === nodeId) | const nodeInfo = allNodes.find(node => node.id === nodeId) | ||||
| if (nodeInfo) { | |||||
| const index = draft.findIndex(node => node.nodeId === nodeId) | |||||
| if (index === -1) { | |||||
| draft.unshift({ | |||||
| nodeId, | |||||
| nodeType: nodeInfo.data.type, | |||||
| title: nodeInfo.data.title, | |||||
| vars: payload, | |||||
| nodePayload: nodeInfo.data, | |||||
| }) | |||||
| } | |||||
| else { | |||||
| draft[index].vars = payload | |||||
| if (nodeInfo) { | |||||
| const index = draft.findIndex(node => node.nodeId === nodeId) | |||||
| if (index === -1) { | |||||
| draft.unshift({ | |||||
| nodeId, | |||||
| nodeType: nodeInfo.data.type, | |||||
| title: nodeInfo.data.title, | |||||
| vars: payload, | |||||
| nodePayload: nodeInfo.data, | |||||
| }) | |||||
| } | |||||
| else { | |||||
| draft[index].vars = payload | |||||
| // put the node to the topAdd commentMore actions | // put the node to the topAdd commentMore actions | ||||
| draft.unshift(draft.splice(index, 1)[0]) | |||||
| } | |||||
| draft.unshift(draft.splice(index, 1)[0]) | |||||
| } | } | ||||
| } | |||||
| }) | }) | ||||
| setNodesWithInspectVars(nodes) | setNodesWithInspectVars(nodes) | ||||
| handleCancelNodeSuccessStatus(nodeId) | handleCancelNodeSuccessStatus(nodeId) |
| hideChatVar = false, | hideChatVar = false, | ||||
| passedInAvailableNodes, | passedInAvailableNodes, | ||||
| }: Params = { | }: Params = { | ||||
| onlyLeafNodeVar: false, | |||||
| filterVar: () => true, | |||||
| }) => { | |||||
| onlyLeafNodeVar: false, | |||||
| filterVar: () => true, | |||||
| }) => { | |||||
| const { getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() | const { getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() | ||||
| const { getNodeAvailableVars } = useWorkflowVariables() | const { getNodeAvailableVars } = useWorkflowVariables() | ||||
| const isChatMode = useIsChatMode() | const isChatMode = useIsChatMode() |
| incomeEdges.forEach((edge) => { | incomeEdges.forEach((edge) => { | ||||
| const incomeNode = nodes.find(node => node.id === edge.source)! | const incomeNode = nodes.find(node => node.id === edge.source)! | ||||
| if (!incomeNode || !('data' in incomeNode)) | if (!incomeNode || !('data' in incomeNode)) | ||||
| return | |||||
| return | |||||
| if ( | if ( | ||||
| (!incomeNode.data._runningBranchId && edge.sourceHandle === 'source') | (!incomeNode.data._runningBranchId && edge.sourceHandle === 'source') |
| } | } | ||||
| export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => { | export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => { | ||||
| const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) | |||||
| const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) | |||||
| const { value, onChange, canChooseMCPTool } = props | const { value, onChange, canChooseMCPTool } = props | ||||
| const [open, setOpen] = useState(false) | const [open, setOpen] = useState(false) |
| 'zh-Hans': '/guides/workflow/node/agent#选择-agent-策略', | 'zh-Hans': '/guides/workflow/node/agent#选择-agent-策略', | ||||
| 'ja-JP': '/guides/workflow/node/agent#エージェント戦略の選択', | 'ja-JP': '/guides/workflow/node/agent#エージェント戦略の選択', | ||||
| })} | })} | ||||
| className='text-text-accent-secondary' target='_blank'> | |||||
| className='text-text-accent-secondary' target='_blank'> | |||||
| {t('workflow.nodes.agent.learnMore')} | {t('workflow.nodes.agent.learnMore')} | ||||
| </Link> | </Link> | ||||
| </div>} | </div>} |
| language={CodeLanguage.json} | language={CodeLanguage.json} | ||||
| onChange={onChange} | onChange={onChange} | ||||
| noWrapper | noWrapper | ||||
| className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1' | |||||
| placeholder={ | |||||
| <div className='whitespace-pre'>{payload.json_schema}</div> | |||||
| } | |||||
| className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1' | |||||
| placeholder={ | |||||
| <div className='whitespace-pre'>{payload.json_schema}</div> | |||||
| } | |||||
| /> | /> | ||||
| )} | )} | ||||
| {(type === InputVarType.singleFile) && ( | {(type === InputVarType.singleFile) && ( |
| useEffect(() => { | useEffect(() => { | ||||
| onFocusChange?.(isFocus) | onFocusChange?.(isFocus) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [isFocus]) | }, [isFocus]) | ||||
| return ( | return ( |
| <Tooltip | <Tooltip | ||||
| popupContent={ | popupContent={ | ||||
| <div className='w-[256px]'> | <div className='w-[256px]'> | ||||
| {t('plugin.detailPanel.toolSelector.unsupportedMCPTool')} | |||||
| {t('plugin.detailPanel.toolSelector.unsupportedMCPTool')} | |||||
| </div> | </div> | ||||
| } | } | ||||
| > | > |
| curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : [] | curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : [] | ||||
| if (isLast) | if (isLast) | ||||
| arrayType = curr?.type | |||||
| arrayType = curr?.type | |||||
| else if (curr?.type === VarType.object || curr?.type === VarType.file) | else if (curr?.type === VarType.object || curr?.type === VarType.file) | ||||
| curr = curr.children || [] | |||||
| curr = curr.children || [] | |||||
| } | } | ||||
| } | } | ||||
| const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) | const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) | ||||
| if (!isValid) { | if (!isValid) { | ||||
| setToastHandle(Toast.notify({ | setToastHandle(Toast.notify({ | ||||
| type: 'error', | |||||
| message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), | |||||
| })) | |||||
| return | |||||
| } | |||||
| type: 'error', | |||||
| message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), | |||||
| })) | |||||
| return | |||||
| } | |||||
| if (list.some(item => item.variable?.trim() === newKey.trim())) { | if (list.some(item => item.variable?.trim() === newKey.trim())) { | ||||
| console.log('new key', newKey.trim()) | console.log('new key', newKey.trim()) | ||||
| setToastHandle(Toast.notify({ | setToastHandle(Toast.notify({ |
| const data = await fetchDynamicOptions() | const data = await fetchDynamicOptions() | ||||
| setDynamicOptions(data?.options || []) | setDynamicOptions(data?.options || []) | ||||
| } | } | ||||
| finally { | |||||
| finally { | |||||
| setIsLoading(false) | setIsLoading(false) | ||||
| } | } | ||||
| } | } |
| if(logParams.showSpecialResultPanel) { | if(logParams.showSpecialResultPanel) { | ||||
| return ( | return ( | ||||
| <div className={cn( | |||||
| <div className={cn( | |||||
| 'relative mr-1 h-full', | 'relative mr-1 h-full', | ||||
| )}> | )}> | ||||
| <div | <div | ||||
| > | > | ||||
| { | { | ||||
| isSingleRunning ? <Stop className='h-4 w-4 text-text-tertiary' /> | isSingleRunning ? <Stop className='h-4 w-4 text-text-tertiary' /> | ||||
| : <RiPlayLargeLine className='h-4 w-4 text-text-tertiary' /> | |||||
| : <RiPlayLargeLine className='h-4 w-4 text-text-tertiary' /> | |||||
| } | } | ||||
| </div> | </div> | ||||
| </Tooltip> | </Tooltip> |
| }, [nodeId]) | }, [nodeId]) | ||||
| const handlePageVisibilityChange = useCallback(() => { | const handlePageVisibilityChange = useCallback(() => { | ||||
| if (document.visibilityState === 'hidden') | |||||
| setPageHasHide(true) | |||||
| else | |||||
| setPageShowed(true) | |||||
| }, []) | |||||
| if (document.visibilityState === 'hidden') | |||||
| setPageHasHide(true) | |||||
| else | |||||
| setPageShowed(true) | |||||
| }, []) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| document.addEventListener('visibilitychange', handlePageVisibilityChange) | document.addEventListener('visibilitychange', handlePageVisibilityChange) | ||||
| const [removedVar, setRemovedVar] = useState<ValueSelector>([]) | const [removedVar, setRemovedVar] = useState<ValueSelector>([]) | ||||
| const removeVarInNode = useCallback(() => { | const removeVarInNode = useCallback(() => { | ||||
| const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { | const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { | ||||
| return varItem.name === removedVar[1] | |||||
| })?.id | |||||
| return varItem.name === removedVar[1] | |||||
| })?.id | |||||
| if(varId) | if(varId) | ||||
| deleteInspectVar(id, varId) | deleteInspectVar(id, varId) | ||||
| removeUsedVarInNodes(removedVar) | removeUsedVarInNodes(removedVar) | ||||
| setInputs(newInputs) | setInputs(newInputs) | ||||
| onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index)) | onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index)) | ||||
| const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { | const varId = nodesWithInspectVars.find(node => node.nodeId === id)?.vars.find((varItem) => { | ||||
| return varItem.name === key | |||||
| })?.id | |||||
| return varItem.name === key | |||||
| })?.id | |||||
| if(varId) | if(varId) | ||||
| deleteInspectVar(id, varId) | deleteInspectVar(id, varId) | ||||
| }, [outputKeyOrders, isVarUsedInNodes, id, inputs, setInputs, onOutputKeyOrdersChange, nodesWithInspectVars, deleteInspectVar, showRemoveVarConfirm, varKey]) | }, [outputKeyOrders, isVarUsedInNodes, id, inputs, setInputs, onOutputKeyOrdersChange, nodesWithInspectVars, deleteInspectVar, showRemoveVarConfirm, varKey]) |
| const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) | const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) | ||||
| return <div className='my-2'> | return <div className='my-2'> | ||||
| <Field | <Field | ||||
| required | |||||
| title={t('workflow.nodes.agent.strategy.label')} | |||||
| className='px-4 py-2' | |||||
| tooltip={t('workflow.nodes.agent.strategy.tooltip')} > | |||||
| required | |||||
| title={t('workflow.nodes.agent.strategy.label')} | |||||
| className='px-4 py-2' | |||||
| tooltip={t('workflow.nodes.agent.strategy.tooltip')} > | |||||
| <AgentStrategy | <AgentStrategy | ||||
| strategy={inputs.agent_strategy_name ? { | strategy={inputs.agent_strategy_name ? { | ||||
| agent_strategy_provider_name: inputs.agent_strategy_provider_name!, | agent_strategy_provider_name: inputs.agent_strategy_provider_name!, |
| return formatTracing([runResult], t)[0] | return formatTracing([runResult], t)[0] | ||||
| }, [runResult, t]) | }, [runResult, t]) | ||||
| const getDependentVars = () => { | |||||
| const getDependentVars = () => { | |||||
| return varInputs.map((item) => { | return varInputs.map((item) => { | ||||
| // Guard against null/undefined variable to prevent app crash | // Guard against null/undefined variable to prevent app crash | ||||
| if (!item.variable || typeof item.variable !== 'string') | if (!item.variable || typeof item.variable !== 'string') |
| if (item.value === WriteMode.set || item.value === WriteMode.increment || item.value === WriteMode.decrement | if (item.value === WriteMode.set || item.value === WriteMode.increment || item.value === WriteMode.decrement | ||||
| || item.value === WriteMode.multiply || item.value === WriteMode.divide) { | || item.value === WriteMode.multiply || item.value === WriteMode.divide) { | ||||
| if(varType === VarType.boolean) | if(varType === VarType.boolean) | ||||
| draft[index].value = false | |||||
| draft[index].value = false | |||||
| draft[index].input_type = AssignerNodeInputType.constant | draft[index].input_type = AssignerNodeInputType.constant | ||||
| } | } | ||||
| else { | else { |
| const newValue = list.filter(item => item.key && item.value).map(item => `${item.key}:${item.value}`).join('\n') | const newValue = list.filter(item => item.key && item.value).map(item => `${item.key}:${item.value}`).join('\n') | ||||
| if (newValue !== value) | if (newValue !== value) | ||||
| onChange(newValue) | onChange(newValue) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [list, noFilter]) | }, [list, noFilter]) | ||||
| const addItem = useCallback(() => { | const addItem = useCallback(() => { | ||||
| setList([...list, { | setList([...list, { |
| setInputs(newInputs) | setInputs(newInputs) | ||||
| setIsDataReady(true) | setIsDataReady(true) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [defaultConfig]) | }, [defaultConfig]) | ||||
| const handleMethodChange = useCallback((method: Method) => { | const handleMethodChange = useCallback((method: Method) => { |
| handleRemoveCondition(condition.id) | handleRemoveCondition(condition.id) | ||||
| }) | }) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [metadataList, handleRemoveCondition, selectedDatasetsLoaded]) | }, [metadataList, handleRemoveCondition, selectedDatasetsLoaded]) | ||||
| return ( | return ( |
| } | } | ||||
| }) | }) | ||||
| setInputs(newInput) | setInputs(newInput) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [currentProvider?.provider, currentModel, currentRerankModel, rerankDefaultModel]) | }, [currentProvider?.provider, currentModel, currentRerankModel, rerankDefaultModel]) | ||||
| const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([]) | const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([]) | ||||
| const [rerankModelOpen, setRerankModelOpen] = useState(false) | const [rerankModelOpen, setRerankModelOpen] = useState(false) | ||||
| setInputs(newInputs) | setInputs(newInputs) | ||||
| setSelectedDatasetsLoaded(true) | setSelectedDatasetsLoaded(true) | ||||
| })() | })() | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| useEffect(() => { | useEffect(() => { | ||||
| setInputs(produce(inputs, (draft) => { | setInputs(produce(inputs, (draft) => { | ||||
| draft.query_variable_selector = query_variable_selector | draft.query_variable_selector = query_variable_selector | ||||
| })) | })) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const handleOnDatasetsChange = useCallback((newDatasets: DataSet[]) => { | const handleOnDatasetsChange = useCallback((newDatasets: DataSet[]) => { |
| const rect = importBtnRef.current.getBoundingClientRect() | const rect = importBtnRef.current.getBoundingClientRect() | ||||
| updateBtnWidth(rect.width) | updateBtnWidth(rect.width) | ||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, []) | }, []) | ||||
| const handleTrigger = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => { | const handleTrigger = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => { |