mirror of
https://github.com/langgenius/dify.git
synced 2025-12-24 00:07:43 +00:00
Compare commits
10 Commits
feat/queue
...
feat/big-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580201ed2c | ||
|
|
1b9c817dba | ||
|
|
0ca7c29c47 | ||
|
|
68ba41e57e | ||
|
|
d4370a8ca5 | ||
|
|
64897bc6fe | ||
|
|
559d014b29 | ||
|
|
3c4b374038 | ||
|
|
b4e76af4a7 | ||
|
|
2391e582f2 |
@@ -39,6 +39,7 @@ type Props = {
|
||||
tip?: React.JSX.Element
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
footer?: React.ReactNode
|
||||
}
|
||||
|
||||
const Base: FC<Props> = ({
|
||||
@@ -57,6 +58,7 @@ const Base: FC<Props> = ({
|
||||
showFileList,
|
||||
showCodeGenerator = false,
|
||||
tip,
|
||||
footer,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const {
|
||||
@@ -128,6 +130,7 @@ const Base: FC<Props> = ({
|
||||
{showFileList && fileList.length > 0 && (
|
||||
<FileListInLog fileList={fileList} />
|
||||
)}
|
||||
{footer}
|
||||
</div>
|
||||
</Wrap>
|
||||
)
|
||||
|
||||
@@ -39,6 +39,7 @@ export type Props = {
|
||||
showCodeGenerator?: boolean
|
||||
className?: string
|
||||
tip?: React.JSX.Element
|
||||
footer?: React.ReactNode
|
||||
}
|
||||
|
||||
export const languageMap = {
|
||||
@@ -67,6 +68,7 @@ const CodeEditor: FC<Props> = ({
|
||||
showCodeGenerator = false,
|
||||
className,
|
||||
tip,
|
||||
footer,
|
||||
}) => {
|
||||
const [isFocus, setIsFocus] = React.useState(false)
|
||||
const [isMounted, setIsMounted] = React.useState(false)
|
||||
@@ -191,6 +193,7 @@ const CodeEditor: FC<Props> = ({
|
||||
showFileList={showFileList}
|
||||
showCodeGenerator={showCodeGenerator}
|
||||
tip={tip}
|
||||
footer={footer}
|
||||
>
|
||||
{main}
|
||||
</Base>
|
||||
|
||||
@@ -15,6 +15,7 @@ type CodeEditorProps = {
|
||||
editorWrapperClassName?: string
|
||||
readOnly?: boolean
|
||||
hideTopMenu?: boolean
|
||||
topContent?: React.ReactNode
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const CodeEditor: FC<CodeEditorProps> = ({
|
||||
@@ -24,6 +25,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
editorWrapperClassName,
|
||||
readOnly = false,
|
||||
hideTopMenu = false,
|
||||
topContent,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -127,6 +129,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{topContent}
|
||||
<div className={classNames('relative overflow-hidden', editorWrapperClassName)}>
|
||||
<Editor
|
||||
defaultLanguage='json'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { type FC } from 'react'
|
||||
import CodeEditor from './code-editor'
|
||||
import cn from '@/utils/classnames'
|
||||
import LargeDataAlert from '@/app/components/workflow/variable-inspect/large-data-alert'
|
||||
|
||||
type SchemaEditorProps = {
|
||||
schema: string
|
||||
@@ -8,6 +9,7 @@ type SchemaEditorProps = {
|
||||
hideTopMenu?: boolean
|
||||
className?: string
|
||||
readonly?: boolean
|
||||
isTruncated?: boolean
|
||||
}
|
||||
|
||||
const SchemaEditor: FC<SchemaEditorProps> = ({
|
||||
@@ -16,6 +18,7 @@ const SchemaEditor: FC<SchemaEditorProps> = ({
|
||||
hideTopMenu,
|
||||
className,
|
||||
readonly = false,
|
||||
isTruncated,
|
||||
}) => {
|
||||
return (
|
||||
<CodeEditor
|
||||
@@ -25,6 +28,7 @@ const SchemaEditor: FC<SchemaEditorProps> = ({
|
||||
value={schema}
|
||||
onUpdate={onUpdate}
|
||||
hideTopMenu={hideTopMenu}
|
||||
topContent={isTruncated && <LargeDataAlert className='mx-1 mb-3 mt-[-4px]' />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -184,7 +184,12 @@ const WorkflowPreview = () => {
|
||||
{currentTab === 'DETAIL' && (
|
||||
<ResultPanel
|
||||
inputs={workflowRunningData?.result?.inputs}
|
||||
inputs_truncated={workflowRunningData?.result?.inputs_truncated}
|
||||
process_data={workflowRunningData?.result?.process_data}
|
||||
process_data_truncated={workflowRunningData?.result?.process_data_truncated}
|
||||
outputs={workflowRunningData?.result?.outputs}
|
||||
outputs_truncated={workflowRunningData?.result?.outputs_truncated}
|
||||
outputs_full_content={workflowRunningData?.result?.outputs_full_content}
|
||||
status={workflowRunningData?.result?.status || ''}
|
||||
error={workflowRunningData?.result?.error}
|
||||
elapsed_time={workflowRunningData?.result?.elapsed_time}
|
||||
|
||||
@@ -147,7 +147,10 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
{!loading && currentTab === 'DETAIL' && runDetail && (
|
||||
<ResultPanel
|
||||
inputs={runDetail.inputs}
|
||||
inputs_truncated={runDetail.inputs_truncated}
|
||||
outputs={runDetail.outputs}
|
||||
outputs_truncated={runDetail.outputs_truncated}
|
||||
outputs_full_content={runDetail.outputs_full_content}
|
||||
status={runDetail.status}
|
||||
error={runDetail.error}
|
||||
elapsed_time={runDetail.elapsed_time}
|
||||
|
||||
@@ -30,6 +30,7 @@ import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/err
|
||||
import { hasRetryNode } from '@/app/components/workflow/utils'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import LargeDataAlert from '../variable-inspect/large-data-alert'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@@ -231,6 +232,7 @@ const NodePanel: FC<Props> = ({
|
||||
language={CodeLanguage.json}
|
||||
value={nodeInfo.inputs}
|
||||
isJSONStringifyBeauty
|
||||
footer={nodeInfo.inputs_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -254,6 +256,7 @@ const NodePanel: FC<Props> = ({
|
||||
value={nodeInfo.outputs}
|
||||
isJSONStringifyBeauty
|
||||
tip={<ErrorHandleTip type={nodeInfo.execution_metadata?.error_strategy} />}
|
||||
footer={nodeInfo.outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={nodeInfo.outputs_full_content?.download_url} className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -16,12 +16,19 @@ import { IterationLogTrigger } from '@/app/components/workflow/run/iteration-log
|
||||
import { LoopLogTrigger } from '@/app/components/workflow/run/loop-log'
|
||||
import { RetryLogTrigger } from '@/app/components/workflow/run/retry-log'
|
||||
import { AgentLogTrigger } from '@/app/components/workflow/run/agent-log'
|
||||
import LargeDataAlert from '../variable-inspect/large-data-alert'
|
||||
|
||||
export type ResultPanelProps = {
|
||||
nodeInfo?: NodeTracing
|
||||
inputs?: string
|
||||
inputs_truncated?: boolean
|
||||
process_data?: string
|
||||
process_data_truncated?: boolean
|
||||
outputs?: string | Record<string, any>
|
||||
outputs_truncated?: boolean
|
||||
outputs_full_content?: {
|
||||
download_url: string
|
||||
}
|
||||
status: string
|
||||
error?: string
|
||||
elapsed_time?: number
|
||||
@@ -42,8 +49,12 @@ export type ResultPanelProps = {
|
||||
const ResultPanel: FC<ResultPanelProps> = ({
|
||||
nodeInfo,
|
||||
inputs,
|
||||
inputs_truncated,
|
||||
process_data,
|
||||
process_data_truncated,
|
||||
outputs,
|
||||
outputs_truncated,
|
||||
outputs_full_content,
|
||||
status,
|
||||
error,
|
||||
elapsed_time,
|
||||
@@ -118,6 +129,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
language={CodeLanguage.json}
|
||||
value={inputs}
|
||||
isJSONStringifyBeauty
|
||||
footer={inputs_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
{process_data && (
|
||||
<CodeEditor
|
||||
@@ -126,6 +138,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
language={CodeLanguage.json}
|
||||
value={process_data}
|
||||
isJSONStringifyBeauty
|
||||
footer={process_data_truncated && <LargeDataAlert textHasNoExport className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
)}
|
||||
{(outputs || status === 'running') && (
|
||||
@@ -136,6 +149,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
value={outputs}
|
||||
isJSONStringifyBeauty
|
||||
tip={<ErrorHandleTip type={execution_metadata?.error_strategy} />}
|
||||
footer={outputs_truncated && <LargeDataAlert textHasNoExport downloadUrl={outputs_full_content?.download_url} className='mx-1 mb-1 mt-2 h-7' />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -379,8 +379,14 @@ export type WorkflowRunningData = {
|
||||
result: {
|
||||
workflow_id?: string
|
||||
inputs?: string
|
||||
inputs_truncated: boolean
|
||||
process_data?: string
|
||||
process_data_truncated: boolean
|
||||
outputs?: string
|
||||
outputs_truncated: boolean
|
||||
outputs_full_content?: {
|
||||
download_url: string
|
||||
}
|
||||
status: string
|
||||
error?: string
|
||||
elapsed_time?: number
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client'
|
||||
import { RiInformation2Fill } from '@remixicon/react'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type Props = {
|
||||
textHasNoExport?: boolean
|
||||
downloadUrl?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const LargeDataAlert: FC<Props> = ({
|
||||
textHasNoExport,
|
||||
downloadUrl,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const text = textHasNoExport ? t('workflow.debug.variableInspect.largeDataNoExport') : t('workflow.debug.variableInspect.largeData')
|
||||
return (
|
||||
<div className={cn('flex h-8 items-center justify-between rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur px-2 shadow-xs', className)}>
|
||||
<div className='flex h-full w-0 grow items-center space-x-1'>
|
||||
<RiInformation2Fill className='size-4 shrink-0 text-text-accent' />
|
||||
<div className='system-xs-regular w-0 grow truncate text-text-primary'>{text}</div>
|
||||
</div>
|
||||
{downloadUrl && (
|
||||
<div className='system-xs-medium-uppercase ml-1 shrink-0 cursor-pointer text-text-accent'>{t('workflow.debug.variableInspect.export')}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(LargeDataAlert)
|
||||
@@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowGoBackLine,
|
||||
RiCloseLine,
|
||||
RiFileDownloadFill,
|
||||
RiMenuLine,
|
||||
RiSparklingFill,
|
||||
} from '@remixicon/react'
|
||||
@@ -52,6 +53,13 @@ const Right = ({
|
||||
const bottomPanelWidth = useStore(s => s.bottomPanelWidth)
|
||||
const setShowVariableInspectPanel = useStore(s => s.setShowVariableInspectPanel)
|
||||
const setCurrentFocusNodeId = useStore(s => s.setCurrentFocusNodeId)
|
||||
const isTruncated = currentNodeVar?.var.is_truncated
|
||||
const fullContent = currentNodeVar?.var.full_content
|
||||
// const isTruncated = true
|
||||
// const fullContent = {
|
||||
// size_bytes: 11289600,
|
||||
// download_url: 'https://upload.dify.ai/files/222bc6e7-40bd-4433-9ba8-4b9ecda88b14/file-preview?timestamp=1754976824&nonce=d970eb39b119f76ec94a9b026f2825b3&sign=ltJO4vS0jrwxuBl4GU74E1Sg_Tia2Y4g2LoBoPh3970=&as_attachment=true',
|
||||
// }
|
||||
|
||||
const {
|
||||
resetConversationVar,
|
||||
@@ -183,7 +191,16 @@ const Right = ({
|
||||
</>
|
||||
)}
|
||||
<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 text-text-tertiary'>{currentNodeVar.var.value_type}</div>
|
||||
<div className='system-xs-medium ml-1 shrink-0 space-x-2 text-text-tertiary'>
|
||||
<span>{currentNodeVar.var.value_type}</span>
|
||||
{isTruncated && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span>{((fullContent?.size_bytes || 0) / 1024 / 1024).toFixed(1)}MB</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -200,20 +217,32 @@ const Right = ({
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{currentNodeVar.var.edited && (
|
||||
{isTruncated && (
|
||||
<Tooltip popupContent={t('workflow.debug.variableInspect.exportToolTip')}>
|
||||
<ActionButton>
|
||||
<a
|
||||
href={fullContent?.download_url}
|
||||
target='_blank'
|
||||
>
|
||||
<RiFileDownloadFill className='size-4' />
|
||||
</a>
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{!isTruncated && currentNodeVar.var.edited && (
|
||||
<Badge>
|
||||
<span className='ml-[2.5px] mr-[4.5px] h-[3px] w-[3px] rounded bg-text-accent-secondary'></span>
|
||||
<span className='system-2xs-semibold-uupercase'>{t('workflow.debug.variableInspect.edited')}</span>
|
||||
</Badge>
|
||||
)}
|
||||
{currentNodeVar.var.edited && currentNodeVar.var.type !== VarInInspectType.conversation && (
|
||||
{!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type !== VarInInspectType.conversation && (
|
||||
<Tooltip popupContent={t('workflow.debug.variableInspect.reset')}>
|
||||
<ActionButton onClick={resetValue}>
|
||||
<RiArrowGoBackLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{currentNodeVar.var.edited && currentNodeVar.var.type === VarInInspectType.conversation && (
|
||||
{!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type === VarInInspectType.conversation && (
|
||||
<Tooltip popupContent={t('workflow.debug.variableInspect.resetConversationVar')}>
|
||||
<ActionButton onClick={handleClear}>
|
||||
<RiArrowGoBackLine className='h-4 w-4' />
|
||||
@@ -238,7 +267,7 @@ const Right = ({
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{currentNodeVar && !isValueFetching && <ValueContent currentVar={currentNodeVar.var} handleValueChange={handleValueChange} />}
|
||||
{currentNodeVar && !isValueFetching && <ValueContent currentVar={currentNodeVar.var} handleValueChange={handleValueChange} isTruncated={!!isTruncated} />}
|
||||
</div>
|
||||
{isShowPromptGenerator && (
|
||||
isCodeBlock
|
||||
|
||||
@@ -21,16 +21,19 @@ import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import type { VarInInspect } from '@/types/workflow'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
import cn from '@/utils/classnames'
|
||||
import LargeDataAlert from './large-data-alert'
|
||||
import BoolValue from '../panel/chat-variable-panel/components/bool-value'
|
||||
|
||||
type Props = {
|
||||
currentVar: VarInInspect
|
||||
handleValueChange: (varId: string, value: any) => void
|
||||
isTruncated: boolean
|
||||
}
|
||||
|
||||
const ValueContent = ({
|
||||
currentVar,
|
||||
handleValueChange,
|
||||
isTruncated,
|
||||
}: Props) => {
|
||||
const contentContainerRef = useRef<HTMLDivElement>(null)
|
||||
const errorMessageRef = useRef<HTMLDivElement>(null)
|
||||
@@ -78,6 +81,8 @@ const ValueContent = ({
|
||||
}, [currentVar.id, currentVar.value])
|
||||
|
||||
const handleTextChange = (value: string) => {
|
||||
if(isTruncated)
|
||||
return
|
||||
if (currentVar.value_type === 'string')
|
||||
setValue(value)
|
||||
|
||||
@@ -127,6 +132,8 @@ const ValueContent = ({
|
||||
}
|
||||
|
||||
const handleEditorChange = (value: string) => {
|
||||
if(isTruncated)
|
||||
return
|
||||
setJson(value)
|
||||
if (jsonValueValidate(value, currentVar.value_type)) {
|
||||
const parsed = JSON.parse(value)
|
||||
@@ -170,15 +177,18 @@ const ValueContent = ({
|
||||
ref={contentContainerRef}
|
||||
className='flex h-full flex-col'
|
||||
>
|
||||
<div className={cn('grow')} style={{ height: `${editorHeight}px` }}>
|
||||
<div className={cn('relative grow')} style={{ height: `${editorHeight}px` }}>
|
||||
{showTextEditor && (
|
||||
<>
|
||||
{isTruncated && <LargeDataAlert className='absolute left-3 right-3 top-1' />}
|
||||
<Textarea
|
||||
readOnly={textEditorDisabled}
|
||||
disabled={textEditorDisabled}
|
||||
className='h-full'
|
||||
disabled={textEditorDisabled || isTruncated}
|
||||
className={cn('h-full', isTruncated && 'pt-[48px]')}
|
||||
value={value as any}
|
||||
onChange={e => handleTextChange(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showBoolEditor && (
|
||||
<div className='w-[295px]'>
|
||||
@@ -211,11 +221,12 @@ const ValueContent = ({
|
||||
}
|
||||
{showJSONEditor && (
|
||||
<SchemaEditor
|
||||
readonly={JSONEditorDisabled}
|
||||
readonly={JSONEditorDisabled || isTruncated}
|
||||
className='overflow-y-auto'
|
||||
hideTopMenu
|
||||
schema={json}
|
||||
onUpdate={handleEditorChange}
|
||||
isTruncated={isTruncated}
|
||||
/>
|
||||
)}
|
||||
{showFileEditor && (
|
||||
|
||||
@@ -988,6 +988,10 @@ const translation = {
|
||||
envNode: 'Environment',
|
||||
chatNode: 'Conversation',
|
||||
systemNode: 'System',
|
||||
exportToolTip: 'Export Variable as File',
|
||||
largeData: 'Large data, read-only preview. Export to view all.',
|
||||
largeDataNoExport: 'Large data - partial preview only',
|
||||
export: 'export',
|
||||
},
|
||||
lastOutput: 'Last Output',
|
||||
relations: {
|
||||
|
||||
@@ -988,6 +988,10 @@ const translation = {
|
||||
envNode: '环境变量',
|
||||
chatNode: '会话变量',
|
||||
systemNode: '系统变量',
|
||||
exportToolTip: '导出变量为文件',
|
||||
largeData: '大数据 - 仅部分只读预览。请导出查看完整数据。',
|
||||
largeDataNoExport: '大数据 - 仅部分预览',
|
||||
export: '导出',
|
||||
},
|
||||
lastOutput: '上次输出',
|
||||
relations: {
|
||||
|
||||
@@ -285,8 +285,13 @@ export type WorkflowRunDetailResponse = {
|
||||
viewport?: Viewport
|
||||
}
|
||||
inputs: string
|
||||
inputs_truncated: boolean
|
||||
status: 'running' | 'succeeded' | 'failed' | 'stopped'
|
||||
outputs?: string
|
||||
outputs_truncated: boolean
|
||||
outputs_full_content?: {
|
||||
download_url: string
|
||||
}
|
||||
error?: string
|
||||
elapsed_time?: number
|
||||
total_tokens?: number
|
||||
|
||||
@@ -37,8 +37,14 @@ export type NodeTracing = {
|
||||
node_type: BlockEnum
|
||||
title: string
|
||||
inputs: any
|
||||
inputs_truncated: boolean
|
||||
process_data: any
|
||||
process_data_truncated: boolean
|
||||
outputs?: Record<string, any>
|
||||
outputs_truncated: boolean
|
||||
outputs_full_content?: {
|
||||
download_url: string
|
||||
}
|
||||
status: string
|
||||
parallel_run_id?: string
|
||||
error?: string
|
||||
@@ -378,6 +384,11 @@ export enum VarInInspectType {
|
||||
system = 'sys',
|
||||
}
|
||||
|
||||
export type FullContent = {
|
||||
size_bytes: number
|
||||
download_url: string
|
||||
}
|
||||
|
||||
export type VarInInspect = {
|
||||
id: string
|
||||
type: VarInInspectType
|
||||
@@ -388,6 +399,8 @@ export type VarInInspect = {
|
||||
value: any
|
||||
edited: boolean
|
||||
visible: boolean
|
||||
is_truncated: boolean
|
||||
full_content: FullContent
|
||||
}
|
||||
|
||||
export type NodeWithVar = {
|
||||
|
||||
Reference in New Issue
Block a user