Commit 9693d014 authored by Joel's avatar Joel

feat: add llm debug

parent 16abcf08
......@@ -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": ""
}
}`
'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'
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: '查询变量',
......
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