mirror of
https://github.com/langgenius/dify.git
synced 2026-01-05 22:15:52 +00:00
feat(workflow): add DisplayContent component for improved variable inspection and integrate schema type handling
This commit is contained in:
@@ -128,7 +128,7 @@ export const useInspectVarsCrudCommon = ({
|
||||
const currentNodeOutputVars = toNodeOutputVars([currentNode], false, () => true, [], [], [], allPluginInfoList, schemaTypeDefinitions)
|
||||
const vars = await fetchNodeInspectVars(flowType, flowId, nodeId)
|
||||
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 {
|
||||
...varItem,
|
||||
schemaType,
|
||||
|
||||
115
web/app/components/workflow/variable-inspect/display-content.tsx
Normal file
115
web/app/components/workflow/variable-inspect/display-content.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
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)
|
||||
@@ -161,6 +161,9 @@ const Right = ({
|
||||
} as any)
|
||||
handleHidePromptGenerator()
|
||||
}, [setInputs, blockType, nodeId, node?.data, handleHidePromptGenerator])
|
||||
|
||||
const displaySchemaType = currentNodeVar?.var.schemaType ? (`(${currentNodeVar.var.schemaType})`) : ''
|
||||
|
||||
return (
|
||||
<div className={cn('flex h-full flex-col')}>
|
||||
{/* header */}
|
||||
@@ -181,21 +184,24 @@ const Right = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{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 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 && (
|
||||
<>
|
||||
<span>·</span>
|
||||
@@ -270,7 +276,13 @@ const Right = ({
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{currentNodeVar && !isValueFetching && <ValueContent currentVar={currentNodeVar.var} handleValueChange={handleValueChange} isTruncated={!!isTruncated} />}
|
||||
{currentNodeVar && !isValueFetching && (
|
||||
<ValueContent
|
||||
currentVar={currentNodeVar.var}
|
||||
handleValueChange={handleValueChange}
|
||||
isTruncated={!!isTruncated}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isShowPromptGenerator && (
|
||||
isCodeBlock
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
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',
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
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 { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
|
||||
import ErrorMessage from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/error-message'
|
||||
@@ -16,7 +13,6 @@ import {
|
||||
validateJSONSchema,
|
||||
} from '@/app/components/workflow/variable-inspect/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 { TransferMethod } from '@/types/app'
|
||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
@@ -27,90 +23,9 @@ import cn from '@/utils/classnames'
|
||||
import LargeDataAlert from './large-data-alert'
|
||||
import BoolValue from '../panel/chat-variable-panel/components/bool-value'
|
||||
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 { 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 = {
|
||||
currentVar: VarInInspect
|
||||
@@ -137,11 +52,10 @@ const ValueContent = ({
|
||||
const fileUploadConfig = useStore(s => s.fileUploadConfig)
|
||||
|
||||
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) => {
|
||||
if (value.value_type === 'file')
|
||||
@@ -279,18 +193,21 @@ const ValueContent = ({
|
||||
{
|
||||
currentVar.value_type === 'string' ? (
|
||||
<DisplayContent
|
||||
type={ContentType.Markdown}
|
||||
previewType={PreviewType.Markdown}
|
||||
varType={currentVar.value_type}
|
||||
mdString={value as any}
|
||||
readonly={textEditorDisabled}
|
||||
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)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
@@ -325,20 +242,26 @@ const ValueContent = ({
|
||||
}
|
||||
{showJSONEditor && (
|
||||
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 && (
|
||||
<div className='max-w-[460px]'>
|
||||
|
||||
Reference in New Issue
Block a user