Commit 9693d014 authored by Joel's avatar Joel

feat: add llm debug

parent 16abcf08
...@@ -117,3 +117,23 @@ export const NODE_WIDTH = 220 ...@@ -117,3 +117,23 @@ export const NODE_WIDTH = 220
export const X_OFFSET = 64 export const X_OFFSET = 64
export const Y_OFFSET = 39 export const Y_OFFSET = 39
export const TREE_DEEPTH = 20 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": ""
}
}`
'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)
'use client' 'use client'
import type { FC } from 'react' 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 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 = { export type Props = {
className?: string
label?: string
inputs: InputVar[] 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 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 ( return (
<div> <FormItem
key={index}
payload={input}
value={values[input.variable]}
onChange={handleChange(input.variable)}
/>
)
})}
</div> </div>
) )
} }
......
...@@ -2,21 +2,24 @@ ...@@ -2,21 +2,24 @@
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' 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 Form from './form'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import { Loading02, XClose } from '@/app/components/base/icons/src/vender/line/general' 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' const i18nPrefix = 'workflow.singleRun'
type BeforeRunFormProps = Props & { type BeforeRunFormProps = {
nodeName: string nodeName: string
onHide: () => void onHide: () => void
onRun: () => void onRun: () => void
onStop: () => void onStop: () => void
runningStatus: string // todo: wait for enum runningStatus: string // todo: wait for enum
result?: JSX.Element result?: JSX.Element
forms: FormProps[]
} }
const BeforeRunForm: FC<BeforeRunFormProps> = ({ const BeforeRunForm: FC<BeforeRunFormProps> = ({
nodeName, nodeName,
...@@ -25,7 +28,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({ ...@@ -25,7 +28,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
onStop, onStop,
runningStatus, runningStatus,
result, result,
...formProps forms,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -35,8 +38,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({ ...@@ -35,8 +38,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
<div className='absolute inset-0 z-10 rounded-2xl pt-10' style={{ <div className='absolute inset-0 z-10 rounded-2xl pt-10' style={{
backgroundColor: 'rgba(16, 24, 40, 0.20)', backgroundColor: 'rgba(16, 24, 40, 0.20)',
}}> }}>
<div className='h-full rounded-2xl bg-white'> <div className='h-full rounded-2xl bg-white flex flex-col'>
<div className='flex justify-between items-center h-8 pl-4 pr-3 pt-3'> <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'> <div className='text-base font-semibold text-gray-900 truncate'>
{t(`${i18nPrefix}.testRun`)} {nodeName} {t(`${i18nPrefix}.testRun`)} {nodeName}
</div> </div>
...@@ -45,8 +48,18 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({ ...@@ -45,8 +48,18 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
</div> </div>
</div> </div>
<div className='px-4'> <div className='h-0 grow overflow-y-auto pb-4'>
<Form {...formProps} /> <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>
<div className='mt-4 flex justify-between space-x-2 px-4' > <div className='mt-4 flex justify-between space-x-2 px-4' >
...@@ -70,6 +83,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({ ...@@ -70,6 +83,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
</div> </div>
)} )}
</div> </div>
</div>
</div> </div>
) )
} }
......
...@@ -2,6 +2,7 @@ import { useState } from 'react' ...@@ -2,6 +2,7 @@ import { useState } from 'react'
import { useWorkflow } from '@/app/components/workflow/hooks' import { useWorkflow } from '@/app/components/workflow/hooks'
import type { CommonNodeType, InputVar, Variable } from '@/app/components/workflow/types' import type { CommonNodeType, InputVar, Variable } from '@/app/components/workflow/types'
import { InputVarType } from '@/app/components/workflow/types' import { InputVarType } from '@/app/components/workflow/types'
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
type Params<T> = { type Params<T> = {
id: string id: string
...@@ -23,17 +24,28 @@ const useOneStepRun = <T>({ id, data }: Params<T>) => { ...@@ -23,17 +24,28 @@ const useOneStepRun = <T>({ id, data }: Params<T>) => {
const [runningStatus, setRunningStatus] = useState('un started') 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[] => { const toVarInputs = (variables: Variable[]): InputVar[] => {
if (!variables) if (!variables)
return [] 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 { return {
label: item.variable, label: item.variable,
variable: 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 required: true, // TODO
options: [], // TODO options: ['a', 'b', 'c'], // TODO
} }
}) })
...@@ -46,6 +58,12 @@ const useOneStepRun = <T>({ id, data }: Params<T>) => { ...@@ -46,6 +58,12 @@ const useOneStepRun = <T>({ id, data }: Params<T>) => {
toVarInputs, toVarInputs,
runningStatus, runningStatus,
setRunningStatus, setRunningStatus,
inputVarValues,
setInputVarValues,
visionFiles,
setVisionFiles,
contexts,
setContexts,
} }
} }
......
...@@ -23,6 +23,14 @@ export const mockData: LLMNodeType = { ...@@ -23,6 +23,14 @@ export const mockData: LLMNodeType = {
variable: 'age', variable: 'age',
value_selector: ['bbb', 'b', 'c'], value_selector: ['bbb', 'b', 'c'],
}, },
{
variable: 'a1',
value_selector: ['aaa', 'name'],
},
{
variable: 'a2',
value_selector: ['aaa', 'name'],
},
], ],
prompt: [ prompt: [
{ {
......
...@@ -14,7 +14,7 @@ import Split from '@/app/components/workflow/nodes/_base/components/split' ...@@ -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 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 OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
import { Resolution } from '@/types/app' 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' import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
const i18nPrefix = 'workflow.nodes.llm' const i18nPrefix = 'workflow.nodes.llm'
...@@ -41,6 +41,12 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ ...@@ -41,6 +41,12 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
handleVisionResolutionChange, handleVisionResolutionChange,
isShowSingleRun, isShowSingleRun,
hideSingleRun, hideSingleRun,
inputVarValues,
setInputVarValues,
visionFiles,
setVisionFiles,
contexts,
setContexts,
runningStatus, runningStatus,
handleRun, handleRun,
handleStop, handleStop,
...@@ -50,6 +56,51 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ ...@@ -50,6 +56,51 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
const isChatApp = true // TODO: get from app context const isChatApp = true // TODO: get from app context
const model = inputs.model 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 ( return (
<div className='mt-2'> <div className='mt-2'>
<div className='px-4 pb-4 space-y-4'> <div className='px-4 pb-4 space-y-4'>
...@@ -159,7 +210,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ ...@@ -159,7 +210,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
<BeforeRunForm <BeforeRunForm
nodeName={inputs.title} nodeName={inputs.title}
onHide={hideSingleRun} onHide={hideSingleRun}
inputs={varInputs} forms={singleRunForms}
runningStatus={runningStatus} runningStatus={runningStatus}
onRun={handleRun} onRun={handleRun}
onStop={handleStop} onStop={handleStop}
......
...@@ -95,10 +95,19 @@ const useConfig = (id: string, payload: LLMNodeType) => { ...@@ -95,10 +95,19 @@ const useConfig = (id: string, payload: LLMNodeType) => {
toVarInputs, toVarInputs,
runningStatus, runningStatus,
setRunningStatus, setRunningStatus,
inputVarValues,
setInputVarValues,
visionFiles,
setVisionFiles,
contexts,
setContexts,
} = useOneStepRun<LLMNodeType>({ } = useOneStepRun<LLMNodeType>({
id, id,
data: inputs, data: inputs,
}) })
console.log(contexts)
const varInputs = toVarInputs(inputs.variables) const varInputs = toVarInputs(inputs.variables)
const handleRun = () => { const handleRun = () => {
setRunningStatus('running') setRunningStatus('running')
...@@ -123,6 +132,12 @@ const useConfig = (id: string, payload: LLMNodeType) => { ...@@ -123,6 +132,12 @@ const useConfig = (id: string, payload: LLMNodeType) => {
handleVisionResolutionChange, handleVisionResolutionChange,
isShowSingleRun, isShowSingleRun,
hideSingleRun, hideSingleRun,
inputVarValues,
setInputVarValues,
visionFiles,
setVisionFiles,
contexts,
setContexts,
varInputs, varInputs,
runningStatus, runningStatus,
handleRun, handleRun,
......
...@@ -70,6 +70,7 @@ export enum InputVarType { ...@@ -70,6 +70,7 @@ export enum InputVarType {
number = 'number', number = 'number',
url = 'url', url = 'url',
files = 'files', files = 'files',
contexts = 'contexts', // knowledge retrieval
} }
export type InputVar = { export type InputVar = {
......
...@@ -124,6 +124,7 @@ const translation = { ...@@ -124,6 +124,7 @@ const translation = {
roleDescription: 'TODO: Role Description', roleDescription: 'TODO: Role Description',
addMessage: 'Add Message', addMessage: 'Add Message',
vision: 'vision', vision: 'vision',
files: 'Files',
resolution: { resolution: {
name: 'Resolution', name: 'Resolution',
high: 'High', high: 'High',
...@@ -133,6 +134,9 @@ const translation = { ...@@ -133,6 +134,9 @@ const translation = {
output: 'Generate content', output: 'Generate content',
usage: 'Model Usage Information', usage: 'Model Usage Information',
}, },
singleRun: {
variable: 'Variable',
},
}, },
knowledgeRetrieval: { knowledgeRetrieval: {
queryVariable: 'Query Variable', queryVariable: 'Query Variable',
......
...@@ -124,6 +124,7 @@ const translation = { ...@@ -124,6 +124,7 @@ const translation = {
addMessage: '添加消息', addMessage: '添加消息',
roleDescription: 'TODO: Role Description', roleDescription: 'TODO: Role Description',
vision: '视觉', vision: '视觉',
files: '文件',
resolution: { resolution: {
name: '分辨率', name: '分辨率',
high: '高', high: '高',
...@@ -133,6 +134,9 @@ const translation = { ...@@ -133,6 +134,9 @@ const translation = {
output: '生成内容', output: '生成内容',
usage: '模型用量信息', usage: '模型用量信息',
}, },
singleRun: {
variable: '变量',
},
}, },
knowledgeRetrieval: { knowledgeRetrieval: {
queryVariable: '查询变量', queryVariable: '查询变量',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment