mirror of https://github.com/langgenius/dify.git
feat: add llm debug
This commit is contained in:
parent
16abcf082c
commit
9693d014ba
|
|
@ -117,3 +117,23 @@ export const NODE_WIDTH = 220
|
|||
export const X_OFFSET = 64
|
||||
export const Y_OFFSET = 39
|
||||
export const TREE_DEEPTH = 20
|
||||
|
||||
export const RETRIEVAL_OUTPUT_STRUCT = `{
|
||||
"content": "",
|
||||
"title": "",
|
||||
"url": "",
|
||||
"icon": "",
|
||||
"metadata": {
|
||||
"dataset_id": "",
|
||||
"dataset_name": "",
|
||||
"document_id": [],
|
||||
"document_name": "",
|
||||
"document_data_source_type": "",
|
||||
"segment_id": "",
|
||||
"segment_position": "",
|
||||
"segment_word_count": "",
|
||||
"segment_hit_count": "",
|
||||
"segment_index_node_hash": "",
|
||||
"score": ""
|
||||
}
|
||||
}`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import produce from 'immer'
|
||||
import type { InputVar } from '../../../../types'
|
||||
import { InputVarType } from '../../../../types'
|
||||
import CodeEditor from '../editor/code-editor'
|
||||
import { CodeLanguage } from '../../../code/types'
|
||||
import Select from '@/app/components/base/select'
|
||||
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
|
||||
import { Resolution, TransferMethod } from '@/types/app'
|
||||
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
type Props = {
|
||||
payload: InputVar
|
||||
value: any
|
||||
onChange: (value: any) => void
|
||||
}
|
||||
|
||||
const FormItem: FC<Props> = ({
|
||||
payload,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const { type } = payload
|
||||
const handleContextItemChange = useCallback((index: number) => {
|
||||
return (newValue: any) => {
|
||||
const newValues = produce(value, (draft: any) => {
|
||||
draft[index] = newValue
|
||||
})
|
||||
onChange(newValues)
|
||||
}
|
||||
}, [value, onChange])
|
||||
|
||||
const handleContextItemRemove = useCallback((index: number) => {
|
||||
return () => {
|
||||
const newValues = produce(value, (draft: any) => {
|
||||
draft.splice(index, 1)
|
||||
})
|
||||
onChange(newValues)
|
||||
}
|
||||
}, [value, onChange])
|
||||
return (
|
||||
<div className='flex justify-between items-start'>
|
||||
{type !== InputVarType.contexts && <div className='shrink-0 w-[96px] pr-1 h-8 leading-8 text-[13px] font-medium text-gray-700 capitalize truncate'>{payload.label}</div>}
|
||||
<div className='w-0 grow'>
|
||||
{
|
||||
type === InputVarType.textInput && (
|
||||
<input
|
||||
className="w-full px-3 text-sm leading-8 text-gray-900 border-0 rounded-lg grow h-8 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
|
||||
type="text"
|
||||
value={value || ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
type === InputVarType.number && (
|
||||
<input
|
||||
className="w-full px-3 text-sm leading-8 text-gray-900 border-0 rounded-lg grow h-8 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
|
||||
type="number"
|
||||
value={value || ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
type === InputVarType.paragraph && (
|
||||
<textarea
|
||||
className="w-full px-3 text-sm leading-8 text-gray-900 border-0 rounded-lg grow h-[120px] bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
|
||||
value={value || ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
type === InputVarType.select && (
|
||||
<Select
|
||||
className="w-full"
|
||||
defaultValue={value || ''}
|
||||
items={payload.options?.map(option => ({ name: option, value: option })) || []}
|
||||
onSelect={i => onChange(i.value)}
|
||||
allowSearch={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
type === InputVarType.files && (
|
||||
<TextGenerationImageUploader
|
||||
settings={{
|
||||
enabled: true,
|
||||
number_limits: 3,
|
||||
detail: Resolution.high,
|
||||
transfer_methods: [TransferMethod.local_file, TransferMethod.remote_url],
|
||||
}}
|
||||
onFilesChange={files => onChange(files.filter(file => file.progress !== -1).map(fileItem => ({
|
||||
type: 'image',
|
||||
transfer_method: fileItem.type,
|
||||
url: fileItem.url,
|
||||
upload_file_id: fileItem.fileId,
|
||||
})))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
type === InputVarType.contexts && (
|
||||
<div className='space-y-2'>
|
||||
{(value || []).map((item: any, index: number) => (
|
||||
<CodeEditor
|
||||
key={index}
|
||||
value={item}
|
||||
title={<span>JSON</span>}
|
||||
headerRight={
|
||||
(value as any).length > 1
|
||||
? (<Trash03
|
||||
onClick={handleContextItemRemove(index)}
|
||||
className='mr-1 w-3.5 h-3.5 text-gray-500 cursor-pointer'
|
||||
/>)
|
||||
: undefined
|
||||
}
|
||||
language={CodeLanguage.json}
|
||||
onChange={handleContextItemChange(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(FormItem)
|
||||
|
|
@ -1,15 +1,65 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import produce from 'immer'
|
||||
import cn from 'classnames'
|
||||
import type { InputVar } from '../../../../types'
|
||||
import FormItem from './form-item'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
|
||||
|
||||
export type Props = {
|
||||
className?: string
|
||||
label?: string
|
||||
inputs: InputVar[]
|
||||
values: Record<string, string>
|
||||
onChange: (newValues: Record<string, any>) => void
|
||||
}
|
||||
|
||||
const Form: FC<Props> = () => {
|
||||
const Form: FC<Props> = ({
|
||||
className,
|
||||
label,
|
||||
inputs,
|
||||
values,
|
||||
onChange,
|
||||
}) => {
|
||||
const handleChange = useCallback((key: string) => {
|
||||
return (value: any) => {
|
||||
const newValues = produce(values, (draft) => {
|
||||
draft[key] = value
|
||||
})
|
||||
onChange(newValues)
|
||||
}
|
||||
}, [values, onChange])
|
||||
|
||||
const handleAddContext = useCallback(() => {
|
||||
const newValues = produce(values, (draft: any) => {
|
||||
const key = inputs[0].variable
|
||||
draft[key].push(RETRIEVAL_OUTPUT_STRUCT)
|
||||
})
|
||||
onChange(newValues)
|
||||
}, [values, onChange])
|
||||
return (
|
||||
<div>
|
||||
<div className={cn(className, 'space-y-2')}>
|
||||
{label && (
|
||||
<div className='mb-1 flex items-center justify-between'>
|
||||
<div className='flex items-center h-6 text-xs font-medium text-gray-500 uppercase'>{label}</div>
|
||||
{inputs[0]?.type === InputVarType.contexts && (
|
||||
<AddButton onClick={handleAddContext} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{inputs.map((input, index) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={index}
|
||||
payload={input}
|
||||
value={values[input.variable]}
|
||||
onChange={handleChange(input.variable)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,24 @@
|
|||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Props } from './form'
|
||||
import cn from 'classnames'
|
||||
import type { Props as FormProps } from './form'
|
||||
import Form from './form'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
import { Loading02, XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
|
||||
const i18nPrefix = 'workflow.singleRun'
|
||||
|
||||
type BeforeRunFormProps = Props & {
|
||||
type BeforeRunFormProps = {
|
||||
nodeName: string
|
||||
onHide: () => void
|
||||
onRun: () => void
|
||||
onStop: () => void
|
||||
runningStatus: string // todo: wait for enum
|
||||
result?: JSX.Element
|
||||
forms: FormProps[]
|
||||
}
|
||||
const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
nodeName,
|
||||
|
|
@ -25,7 +28,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
|||
onStop,
|
||||
runningStatus,
|
||||
result,
|
||||
...formProps
|
||||
forms,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
|
@ -35,8 +38,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
|||
<div className='absolute inset-0 z-10 rounded-2xl pt-10' style={{
|
||||
backgroundColor: 'rgba(16, 24, 40, 0.20)',
|
||||
}}>
|
||||
<div className='h-full rounded-2xl bg-white'>
|
||||
<div className='flex justify-between items-center h-8 pl-4 pr-3 pt-3'>
|
||||
<div className='h-full rounded-2xl bg-white flex flex-col'>
|
||||
<div className='shrink-0 flex justify-between items-center h-8 pl-4 pr-3 pt-3'>
|
||||
<div className='text-base font-semibold text-gray-900 truncate'>
|
||||
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
||||
</div>
|
||||
|
|
@ -45,30 +48,42 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className='px-4'>
|
||||
<Form {...formProps} />
|
||||
</div>
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<div className='mt-3 px-4 space-y-4'>
|
||||
{forms.map((form, index) => (
|
||||
<div key={index}>
|
||||
<Form
|
||||
key={index}
|
||||
className={cn(index < forms.length - 1 && 'mb-4')}
|
||||
{...form}
|
||||
/>
|
||||
{index < forms.length - 1 && <Split />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||
{isRunning && (
|
||||
<div
|
||||
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
||||
onClick={onStop}
|
||||
>
|
||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||
{isRunning && (
|
||||
<div
|
||||
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
||||
onClick={onStop}
|
||||
>
|
||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={isRunning} type='primary' className='w-0 grow !h-8 flex items-center space-x-2' onClick={onRun}>
|
||||
{isRunning && <Loading02 className='animate-spin w-4 h-4 text-white' />}
|
||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isFinished && (
|
||||
<div className='px-4'>
|
||||
{result}
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={isRunning} type='primary' className='w-0 grow !h-8 flex items-center space-x-2' onClick={onRun}>
|
||||
{isRunning && <Loading02 className='animate-spin w-4 h-4 text-white' />}
|
||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isFinished && (
|
||||
<div className='px-4'>
|
||||
{result}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useState } from 'react'
|
|||
import { useWorkflow } from '@/app/components/workflow/hooks'
|
||||
import type { CommonNodeType, InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
|
||||
|
||||
type Params<T> = {
|
||||
id: string
|
||||
|
|
@ -23,17 +24,28 @@ const useOneStepRun = <T>({ id, data }: Params<T>) => {
|
|||
|
||||
const [runningStatus, setRunningStatus] = useState('un started')
|
||||
|
||||
// TODO: test
|
||||
const [inputVarValues, setInputVarValues] = useState<Record<string, any>>({
|
||||
name: 'Joel',
|
||||
age: '18',
|
||||
})
|
||||
|
||||
const [visionFiles, setVisionFiles] = useState<any[]>([])
|
||||
|
||||
const [contexts, setContexts] = useState<string[]>([RETRIEVAL_OUTPUT_STRUCT])
|
||||
|
||||
const toVarInputs = (variables: Variable[]): InputVar[] => {
|
||||
if (!variables)
|
||||
return []
|
||||
|
||||
const varInputs = variables.map((item) => {
|
||||
const varInputs = variables.map((item, i) => {
|
||||
const allVarTypes = [InputVarType.textInput, InputVarType.paragraph, InputVarType.number, InputVarType.select, InputVarType.files]
|
||||
return {
|
||||
label: item.variable,
|
||||
variable: item.variable,
|
||||
type: InputVarType.textInput, // TODO: dynamic get var type
|
||||
type: allVarTypes[i % allVarTypes.length], // TODO: dynamic get var type
|
||||
required: true, // TODO
|
||||
options: [], // TODO
|
||||
options: ['a', 'b', 'c'], // TODO
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -46,6 +58,12 @@ const useOneStepRun = <T>({ id, data }: Params<T>) => {
|
|||
toVarInputs,
|
||||
runningStatus,
|
||||
setRunningStatus,
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
visionFiles,
|
||||
setVisionFiles,
|
||||
contexts,
|
||||
setContexts,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,14 @@ export const mockData: LLMNodeType = {
|
|||
variable: 'age',
|
||||
value_selector: ['bbb', 'b', 'c'],
|
||||
},
|
||||
{
|
||||
variable: 'a1',
|
||||
value_selector: ['aaa', 'name'],
|
||||
},
|
||||
{
|
||||
variable: 'a2',
|
||||
value_selector: ['aaa', 'name'],
|
||||
},
|
||||
],
|
||||
prompt: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import Split from '@/app/components/workflow/nodes/_base/components/split'
|
|||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import { Resolution } from '@/types/app'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.llm'
|
||||
|
|
@ -41,6 +41,12 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||
handleVisionResolutionChange,
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
visionFiles,
|
||||
setVisionFiles,
|
||||
contexts,
|
||||
setContexts,
|
||||
runningStatus,
|
||||
handleRun,
|
||||
handleStop,
|
||||
|
|
@ -50,6 +56,51 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||
const isChatApp = true // TODO: get from app context
|
||||
const model = inputs.model
|
||||
|
||||
const singleRunForms = (() => {
|
||||
const forms = [
|
||||
{
|
||||
label: t(`${i18nPrefix}.singleRun.variable`)!,
|
||||
inputs: varInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
|
||||
if (inputs.context?.variable_selector && inputs.context?.variable_selector.length > 0) {
|
||||
forms.push(
|
||||
{
|
||||
label: t(`${i18nPrefix}.context`)!,
|
||||
inputs: [{
|
||||
label: '',
|
||||
variable: 'contexts',
|
||||
type: InputVarType.contexts,
|
||||
required: false,
|
||||
}],
|
||||
values: { contexts },
|
||||
onChange: keyValue => setContexts((keyValue as any).contexts),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (isShowVisionConfig) {
|
||||
forms.push(
|
||||
{
|
||||
label: t(`${i18nPrefix}.vision`)!,
|
||||
inputs: [{
|
||||
label: t(`${i18nPrefix}.files`)!,
|
||||
variable: 'visionFiles',
|
||||
type: InputVarType.files,
|
||||
required: false,
|
||||
}],
|
||||
values: { visionFiles },
|
||||
onChange: keyValue => setVisionFiles((keyValue as any).visionFiles),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return forms
|
||||
})()
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
|
|
@ -159,7 +210,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
onHide={hideSingleRun}
|
||||
inputs={varInputs}
|
||||
forms={singleRunForms}
|
||||
runningStatus={runningStatus}
|
||||
onRun={handleRun}
|
||||
onStop={handleStop}
|
||||
|
|
|
|||
|
|
@ -95,10 +95,19 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
|||
toVarInputs,
|
||||
runningStatus,
|
||||
setRunningStatus,
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
visionFiles,
|
||||
setVisionFiles,
|
||||
contexts,
|
||||
setContexts,
|
||||
} = useOneStepRun<LLMNodeType>({
|
||||
id,
|
||||
data: inputs,
|
||||
})
|
||||
|
||||
console.log(contexts)
|
||||
|
||||
const varInputs = toVarInputs(inputs.variables)
|
||||
const handleRun = () => {
|
||||
setRunningStatus('running')
|
||||
|
|
@ -123,6 +132,12 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
|||
handleVisionResolutionChange,
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
visionFiles,
|
||||
setVisionFiles,
|
||||
contexts,
|
||||
setContexts,
|
||||
varInputs,
|
||||
runningStatus,
|
||||
handleRun,
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export enum InputVarType {
|
|||
number = 'number',
|
||||
url = 'url',
|
||||
files = 'files',
|
||||
contexts = 'contexts', // knowledge retrieval
|
||||
}
|
||||
|
||||
export type InputVar = {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ const translation = {
|
|||
roleDescription: 'TODO: Role Description',
|
||||
addMessage: 'Add Message',
|
||||
vision: 'vision',
|
||||
files: 'Files',
|
||||
resolution: {
|
||||
name: 'Resolution',
|
||||
high: 'High',
|
||||
|
|
@ -133,6 +134,9 @@ const translation = {
|
|||
output: 'Generate content',
|
||||
usage: 'Model Usage Information',
|
||||
},
|
||||
singleRun: {
|
||||
variable: 'Variable',
|
||||
},
|
||||
},
|
||||
knowledgeRetrieval: {
|
||||
queryVariable: 'Query Variable',
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ const translation = {
|
|||
addMessage: '添加消息',
|
||||
roleDescription: 'TODO: Role Description',
|
||||
vision: '视觉',
|
||||
files: '文件',
|
||||
resolution: {
|
||||
name: '分辨率',
|
||||
high: '高',
|
||||
|
|
@ -133,6 +134,9 @@ const translation = {
|
|||
output: '生成内容',
|
||||
usage: '模型用量信息',
|
||||
},
|
||||
singleRun: {
|
||||
variable: '变量',
|
||||
},
|
||||
},
|
||||
knowledgeRetrieval: {
|
||||
queryVariable: '查询变量',
|
||||
|
|
|
|||
Loading…
Reference in New Issue