| const currentNodeOutputVars = toNodeOutputVars([currentNode], false, () => true, [], [], [], allPluginInfoList, schemaTypeDefinitions) | const currentNodeOutputVars = toNodeOutputVars([currentNode], false, () => true, [], [], [], allPluginInfoList, schemaTypeDefinitions) | ||||
| const vars = await fetchNodeInspectVars(flowType, flowId, nodeId) | const vars = await fetchNodeInspectVars(flowType, flowId, nodeId) | ||||
| const varsWithSchemaType = vars.map((varItem) => { | const varsWithSchemaType = vars.map((varItem) => { | ||||
| const schemaType = currentNodeOutputVars[0].vars.find(v => v.variable === varItem.name)?.schemaType || '' | |||||
| const schemaType = currentNodeOutputVars[0]?.vars.find(v => v.variable === varItem.name)?.schemaType || '' | |||||
| return { | return { | ||||
| ...varItem, | ...varItem, | ||||
| schemaType, | schemaType, |
| import React, { useMemo, useState } from 'react' | |||||
| import { useTranslation } from 'react-i18next' | |||||
| import { RiBracesLine, RiEyeLine } from '@remixicon/react' | |||||
| import Textarea from '@/app/components/base/textarea' | |||||
| import { Markdown } from '@/app/components/base/markdown' | |||||
| import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' | |||||
| import { SegmentedControl } from '@/app/components/base/segmented-control' | |||||
| import cn from '@/utils/classnames' | |||||
| import { ChunkCardList } from '@/app/components/rag-pipeline/components/chunk-card-list' | |||||
| import type { ChunkInfo } from '@/app/components/rag-pipeline/components/chunk-card-list/types' | |||||
| import type { ParentMode } from '@/models/datasets' | |||||
| import { ChunkingMode } from '@/models/datasets' | |||||
| import { PreviewType, ViewMode } from './types' | |||||
| import type { VarType } from '../types' | |||||
| type DisplayContentProps = { | |||||
| previewType: PreviewType | |||||
| varType: VarType | |||||
| schemaType?: string | |||||
| mdString?: string | |||||
| jsonString?: string | |||||
| readonly: boolean | |||||
| handleTextChange?: (value: string) => void | |||||
| handleEditorChange?: (value: string) => void | |||||
| } | |||||
| const DisplayContent = (props: DisplayContentProps) => { | |||||
| const { previewType, varType, schemaType, mdString, jsonString, readonly, handleTextChange, handleEditorChange } = props | |||||
| const [viewMode, setViewMode] = useState<ViewMode>(ViewMode.Code) | |||||
| const [isFocused, setIsFocused] = useState(false) | |||||
| const { t } = useTranslation() | |||||
| const chunkType = useMemo(() => { | |||||
| if (previewType !== PreviewType.Chunks || !schemaType) | |||||
| return undefined | |||||
| if (schemaType === 'general_structure') | |||||
| return ChunkingMode.text | |||||
| if (schemaType === 'parent_child_structure') | |||||
| return ChunkingMode.parentChild | |||||
| if (schemaType === 'qa_structure') | |||||
| return ChunkingMode.qa | |||||
| }, [previewType, schemaType]) | |||||
| const parentMode = useMemo(() => { | |||||
| if (previewType !== PreviewType.Chunks || !schemaType || !jsonString) | |||||
| return undefined | |||||
| if (schemaType === 'parent_child_structure') | |||||
| return JSON.parse(jsonString!)?.parent_mode as ParentMode | |||||
| return undefined | |||||
| }, [previewType, schemaType, jsonString]) | |||||
| return ( | |||||
| <div className={cn('flex h-full flex-col rounded-[10px] bg-components-input-bg-normal', isFocused && 'bg-components-input-bg-active outline outline-1 outline-components-input-border-active')}> | |||||
| <div className='flex shrink-0 items-center justify-end p-1'> | |||||
| {previewType === PreviewType.Markdown && ( | |||||
| <div className='system-xs-semibold-uppercase flex grow items-center px-2 py-0.5 text-text-secondary'> | |||||
| {previewType.toUpperCase()} | |||||
| </div> | |||||
| )} | |||||
| {previewType === PreviewType.Chunks && ( | |||||
| <div className='system-xs-semibold-uppercase flex grow items-center px-2 py-0.5 text-text-secondary'> | |||||
| {varType.toUpperCase()}{schemaType ? `(${schemaType})` : ''} | |||||
| </div> | |||||
| )} | |||||
| <SegmentedControl | |||||
| options={[ | |||||
| { value: ViewMode.Code, text: t('workflow.nodes.templateTransform.code'), Icon: RiBracesLine }, | |||||
| { value: ViewMode.Preview, text: t('workflow.common.preview'), Icon: RiEyeLine }, | |||||
| ]} | |||||
| value={viewMode} | |||||
| onChange={setViewMode} | |||||
| size='small' | |||||
| padding='with' | |||||
| activeClassName='!text-text-accent-light-mode-only' | |||||
| btnClassName='!pl-1.5 !pr-0.5 gap-[3px]' | |||||
| className='shrink-0' | |||||
| /> | |||||
| </div> | |||||
| <div className='flex flex-1 overflow-auto rounded-b-[10px] pl-3 pr-1'> | |||||
| {viewMode === ViewMode.Code && ( | |||||
| previewType === PreviewType.Markdown | |||||
| ? <Textarea | |||||
| readOnly={readonly} | |||||
| disabled={readonly} | |||||
| className='h-full border-none bg-transparent p-0 text-text-secondary hover:bg-transparent focus:bg-transparent focus:shadow-none' | |||||
| value={mdString as any} | |||||
| onChange={e => handleTextChange?.(e.target.value)} | |||||
| onFocus={() => setIsFocused(true)} | |||||
| onBlur={() => setIsFocused(false)} | |||||
| /> | |||||
| : <SchemaEditor | |||||
| readonly={readonly} | |||||
| className='overflow-y-auto bg-transparent' | |||||
| hideTopMenu | |||||
| schema={jsonString!} | |||||
| onUpdate={handleEditorChange!} | |||||
| onFocus={() => setIsFocused(true)} | |||||
| onBlur={() => setIsFocused(false)} | |||||
| /> | |||||
| )} | |||||
| {viewMode === ViewMode.Preview && ( | |||||
| previewType === PreviewType.Markdown | |||||
| ? <Markdown className='grow overflow-auto rounded-lg !bg-white px-4 py-3' content={(mdString ?? '') as string} /> | |||||
| : <ChunkCardList | |||||
| chunkType={chunkType!} | |||||
| parentMode={parentMode} | |||||
| chunkInfo={JSON.parse(jsonString!) as ChunkInfo} | |||||
| /> | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| export default React.memo(DisplayContent) |
| } as any) | } as any) | ||||
| handleHidePromptGenerator() | handleHidePromptGenerator() | ||||
| }, [setInputs, blockType, nodeId, node?.data, handleHidePromptGenerator]) | }, [setInputs, blockType, nodeId, node?.data, handleHidePromptGenerator]) | ||||
| const displaySchemaType = currentNodeVar?.var.schemaType ? (`(${currentNodeVar.var.schemaType})`) : '' | |||||
| return ( | return ( | ||||
| <div className={cn('flex h-full flex-col')}> | <div className={cn('flex h-full flex-col')}> | ||||
| {/* header */} | {/* header */} | ||||
| /> | /> | ||||
| ) | ) | ||||
| } | } | ||||
| {currentNodeVar.nodeType !== VarInInspectType.environment && currentNodeVar.nodeType !== VarInInspectType.conversation && currentNodeVar.nodeType !== VarInInspectType.system && ( | |||||
| <> | |||||
| <BlockIcon | |||||
| className='shrink-0' | |||||
| type={currentNodeVar.nodeType as BlockEnum} | |||||
| size='xs' | |||||
| toolIcon={toolIcon} | |||||
| /> | |||||
| <div className='system-sm-regular shrink-0 text-text-secondary'>{currentNodeVar.title}</div> | |||||
| <div className='system-sm-regular shrink-0 text-text-quaternary'>/</div> | |||||
| </> | |||||
| )} | |||||
| {currentNodeVar.nodeType !== VarInInspectType.environment | |||||
| && currentNodeVar.nodeType !== VarInInspectType.conversation | |||||
| && currentNodeVar.nodeType !== VarInInspectType.system | |||||
| && ( | |||||
| <> | |||||
| <BlockIcon | |||||
| className='shrink-0' | |||||
| type={currentNodeVar.nodeType as BlockEnum} | |||||
| size='xs' | |||||
| toolIcon={toolIcon} | |||||
| /> | |||||
| <div className='system-sm-regular shrink-0 text-text-secondary'>{currentNodeVar.title}</div> | |||||
| <div className='system-sm-regular shrink-0 text-text-quaternary'>/</div> | |||||
| </> | |||||
| )} | |||||
| <div title={currentNodeVar.var.name} className='system-sm-semibold truncate text-text-secondary'>{currentNodeVar.var.name}</div> | <div title={currentNodeVar.var.name} className='system-sm-semibold truncate text-text-secondary'>{currentNodeVar.var.name}</div> | ||||
| <div className='system-xs-medium ml-1 shrink-0 space-x-2 text-text-tertiary'> | <div className='system-xs-medium ml-1 shrink-0 space-x-2 text-text-tertiary'> | ||||
| <span>{`${currentNodeVar.var.value_type}${currentNodeVar.var.schemaType ? (`(${currentNodeVar.var.schemaType})`) : ''}`}</span> | |||||
| <span>{`${currentNodeVar.var.value_type}${displaySchemaType}`}</span> | |||||
| {isTruncated && ( | {isTruncated && ( | ||||
| <> | <> | ||||
| <span>·</span> | <span>·</span> | ||||
| <Loading /> | <Loading /> | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {currentNodeVar && !isValueFetching && <ValueContent currentVar={currentNodeVar.var} handleValueChange={handleValueChange} isTruncated={!!isTruncated} />} | |||||
| {currentNodeVar && !isValueFetching && ( | |||||
| <ValueContent | |||||
| currentVar={currentNodeVar.var} | |||||
| handleValueChange={handleValueChange} | |||||
| isTruncated={!!isTruncated} | |||||
| /> | |||||
| )} | |||||
| </div> | </div> | ||||
| {isShowPromptGenerator && ( | {isShowPromptGenerator && ( | ||||
| isCodeBlock | isCodeBlock |
| export const EVENT_WORKFLOW_STOP = 'WORKFLOW_STOP' | export const EVENT_WORKFLOW_STOP = 'WORKFLOW_STOP' | ||||
| export const CHUNK_SCHEMA_TYPES = ['general_structure', 'parent_child_structure', 'qa_structure'] | |||||
| export enum ViewMode { | |||||
| Code = 'code', | |||||
| Preview = 'preview', | |||||
| } | |||||
| export enum PreviewType { | |||||
| Markdown = 'markdown', | |||||
| Chunks = 'chunks', | |||||
| } |
| import { useEffect, useMemo, useRef, useState } from 'react' | import { useEffect, useMemo, useRef, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | |||||
| import { useDebounceFn } from 'ahooks' | import { useDebounceFn } from 'ahooks' | ||||
| import { RiBracesLine, RiEyeLine } from '@remixicon/react' | |||||
| import Textarea from '@/app/components/base/textarea' | import Textarea from '@/app/components/base/textarea' | ||||
| import { Markdown } from '@/app/components/base/markdown' | |||||
| import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' | import SchemaEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/schema-editor' | ||||
| import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' | import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader' | ||||
| import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message' | import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message' | ||||
| validateJSONSchema, | validateJSONSchema, | ||||
| } from '@/app/components/workflow/variable-inspect/utils' | } from '@/app/components/workflow/variable-inspect/utils' | ||||
| import { getProcessedFiles, getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' | import { getProcessedFiles, getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' | ||||
| import { SegmentedControl } from '@/app/components/base/segmented-control' | |||||
| import { JSON_SCHEMA_MAX_DEPTH } from '@/config' | import { JSON_SCHEMA_MAX_DEPTH } from '@/config' | ||||
| import { TransferMethod } from '@/types/app' | import { TransferMethod } from '@/types/app' | ||||
| import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' | import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' | ||||
| import LargeDataAlert from './large-data-alert' | import LargeDataAlert from './large-data-alert' | ||||
| import BoolValue from '../panel/chat-variable-panel/components/bool-value' | import BoolValue from '../panel/chat-variable-panel/components/bool-value' | ||||
| import { useStore } from '@/app/components/workflow/store' | import { useStore } from '@/app/components/workflow/store' | ||||
| import { ChunkCardList } from '@/app/components/rag-pipeline/components/chunk-card-list' | |||||
| import type { ChunkInfo } from '@/app/components/rag-pipeline/components/chunk-card-list/types' | |||||
| import { PreviewMode } from '../../base/features/types' | import { PreviewMode } from '../../base/features/types' | ||||
| import { ChunkingMode } from '@/models/datasets' | |||||
| enum ViewMode { | |||||
| Code = 'code', | |||||
| Preview = 'preview', | |||||
| } | |||||
| enum ContentType { | |||||
| Markdown = 'markdown', | |||||
| Chunks = 'chunks', | |||||
| } | |||||
| type DisplayContentProps = { | |||||
| type: ContentType | |||||
| mdString?: string | |||||
| jsonString?: string | |||||
| readonly: boolean | |||||
| handleTextChange?: (value: string) => void | |||||
| handleEditorChange?: (value: string) => void | |||||
| } | |||||
| const DisplayContent = (props: DisplayContentProps) => { | |||||
| const { type, mdString, jsonString, readonly, handleTextChange, handleEditorChange } = props | |||||
| const [viewMode, setViewMode] = useState<ViewMode>(ViewMode.Code) | |||||
| const [isFocused, setIsFocused] = useState(false) | |||||
| const { t } = useTranslation() | |||||
| return ( | |||||
| <div className={cn('flex h-full flex-col rounded-[10px] bg-components-input-bg-normal', isFocused && 'bg-components-input-bg-active outline outline-1 outline-components-input-border-active')}> | |||||
| <div className='flex shrink-0 items-center justify-between p-1'> | |||||
| <div className='system-xs-semibold-uppercase flex items-center px-2 py-0.5 text-text-secondary'> | |||||
| {type.toUpperCase()} | |||||
| </div> | |||||
| <SegmentedControl | |||||
| options={[ | |||||
| { value: ViewMode.Code, text: t('workflow.nodes.templateTransform.code'), Icon: RiBracesLine }, | |||||
| { value: ViewMode.Preview, text: t('workflow.common.preview'), Icon: RiEyeLine }, | |||||
| ]} | |||||
| value={viewMode} | |||||
| onChange={setViewMode} | |||||
| size='small' | |||||
| padding='with' | |||||
| activeClassName='!text-text-accent-light-mode-only' | |||||
| btnClassName='!pl-1.5 !pr-0.5 gap-[3px]' | |||||
| /> | |||||
| </div> | |||||
| <div className='flex flex-1 overflow-auto rounded-b-[10px] pl-3 pr-1'> | |||||
| {viewMode === ViewMode.Code && ( | |||||
| type === ContentType.Markdown | |||||
| ? <Textarea | |||||
| readOnly={readonly} | |||||
| disabled={readonly} | |||||
| className='h-full border-none bg-transparent p-0 text-text-secondary hover:bg-transparent focus:bg-transparent focus:shadow-none' | |||||
| value={mdString as any} | |||||
| onChange={e => handleTextChange?.(e.target.value)} | |||||
| onFocus={() => setIsFocused(true)} | |||||
| onBlur={() => setIsFocused(false)} | |||||
| /> | |||||
| : <SchemaEditor | |||||
| readonly={readonly} | |||||
| className='overflow-y-auto bg-transparent' | |||||
| hideTopMenu | |||||
| schema={jsonString!} | |||||
| onUpdate={handleEditorChange!} | |||||
| onFocus={() => setIsFocused(true)} | |||||
| onBlur={() => setIsFocused(false)} | |||||
| /> | |||||
| )} | |||||
| {viewMode === ViewMode.Preview && ( | |||||
| type === ContentType.Markdown | |||||
| ? <Markdown className='grow overflow-auto rounded-lg !bg-white px-4 py-3' content={(mdString ?? '') as string} /> | |||||
| : <ChunkCardList | |||||
| chunkType={ChunkingMode.text} // todo: delete mock data | |||||
| parentMode={'full-doc'} // todo: delete mock data | |||||
| chunkInfo={JSON.parse(jsonString!) as ChunkInfo} | |||||
| /> | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| import DisplayContent from './display-content' | |||||
| import { CHUNK_SCHEMA_TYPES, PreviewType } from './types' | |||||
| type Props = { | type Props = { | ||||
| currentVar: VarInInspect | currentVar: VarInInspect | ||||
| const fileUploadConfig = useStore(s => s.fileUploadConfig) | const fileUploadConfig = useStore(s => s.fileUploadConfig) | ||||
| const hasChunks = useMemo(() => { | const hasChunks = useMemo(() => { | ||||
| return currentVar.value_type === 'object' | |||||
| && currentVar.value | |||||
| && typeof currentVar.value === 'object' | |||||
| && ['parent_child_chunks', 'general_chunks', 'qa_chunks'].some(key => key in currentVar.value) | |||||
| }, [currentVar.value_type, currentVar.value]) | |||||
| if (!currentVar.schemaType) | |||||
| return false | |||||
| return CHUNK_SCHEMA_TYPES.includes(currentVar.schemaType) | |||||
| }, [currentVar.schemaType]) | |||||
| const formatFileValue = (value: VarInInspect) => { | const formatFileValue = (value: VarInInspect) => { | ||||
| if (value.value_type === 'file') | if (value.value_type === 'file') | ||||
| { | { | ||||
| currentVar.value_type === 'string' ? ( | currentVar.value_type === 'string' ? ( | ||||
| <DisplayContent | <DisplayContent | ||||
| type={ContentType.Markdown} | |||||
| previewType={PreviewType.Markdown} | |||||
| varType={currentVar.value_type} | |||||
| mdString={value as any} | mdString={value as any} | ||||
| readonly={textEditorDisabled} | readonly={textEditorDisabled} | ||||
| handleTextChange={handleTextChange} | handleTextChange={handleTextChange} | ||||
| /> | /> | ||||
| ) : <Textarea | |||||
| readOnly={textEditorDisabled} | |||||
| disabled={textEditorDisabled || isTruncated} | |||||
| className={cn('h-full', isTruncated && 'pt-[48px]')} | |||||
| value={value as any} | |||||
| onChange={e => handleTextChange(e.target.value)} | |||||
| /> | |||||
| ) : ( | |||||
| <Textarea | |||||
| readOnly={textEditorDisabled} | |||||
| disabled={textEditorDisabled || isTruncated} | |||||
| className={cn('h-full', isTruncated && 'pt-[48px]')} | |||||
| value={value as any} | |||||
| onChange={e => handleTextChange(e.target.value)} | |||||
| /> | |||||
| ) | |||||
| } | } | ||||
| </> | </> | ||||
| )} | )} | ||||
| } | } | ||||
| {showJSONEditor && ( | {showJSONEditor && ( | ||||
| hasChunks | hasChunks | ||||
| ? <DisplayContent | |||||
| type={ContentType.Chunks} | |||||
| jsonString={json ?? '{}'} | |||||
| readonly={JSONEditorDisabled} | |||||
| handleEditorChange={handleEditorChange} | |||||
| /> | |||||
| : <SchemaEditor | |||||
| readonly={JSONEditorDisabled || isTruncated} | |||||
| className='overflow-y-auto' | |||||
| hideTopMenu | |||||
| schema={json} | |||||
| onUpdate={handleEditorChange} | |||||
| isTruncated={isTruncated} | |||||
| /> | |||||
| ? ( | |||||
| <DisplayContent | |||||
| previewType={PreviewType.Chunks} | |||||
| varType={currentVar.value_type} | |||||
| schemaType={currentVar.schemaType ?? ''} | |||||
| jsonString={json ?? '{}'} | |||||
| readonly={JSONEditorDisabled} | |||||
| handleEditorChange={handleEditorChange} | |||||
| /> | |||||
| ) | |||||
| : ( | |||||
| <SchemaEditor | |||||
| readonly={JSONEditorDisabled || isTruncated} | |||||
| className='overflow-y-auto' | |||||
| hideTopMenu | |||||
| schema={json} | |||||
| onUpdate={handleEditorChange} | |||||
| isTruncated={isTruncated} | |||||
| /> | |||||
| ) | |||||
| )} | )} | ||||
| {showFileEditor && ( | {showFileEditor && ( | ||||
| <div className='max-w-[460px]'> | <div className='max-w-[460px]'> |