feat: support binding context var (#1227)

Co-authored-by: Joel <iamjoel007@gmail.com>
This commit is contained in:
Garfield Dai
2023-09-27 14:53:22 +08:00
committed by GitHub
parent 59236b789f
commit 18c710c906
44 changed files with 711 additions and 77 deletions

View File

@@ -0,0 +1,39 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import type { Props } from './var-picker'
import VarPicker from './var-picker'
import { BracketsX } from '@/app/components/base/icons/src/vender/line/development'
import Tooltip from '@/app/components/base/tooltip'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
const ContextVar: FC<Props> = (props) => {
const { t } = useTranslation()
const { value, options } = props
const currItem = options.find(item => item.value === value)
const notSetVar = !currItem
return (
<div className={cn(notSetVar ? 'rounded-bl-xl rounded-br-xl bg-[#FEF0C7] border-[#FEF0C7]' : 'border-gray-200', 'flex justify-between items-center h-12 px-3 border-t ')}>
<div className='flex items-center space-x-1 shrink-0'>
<div className='p-1'>
<BracketsX className='w-4 h-4 text-primary-500'/>
</div>
<div className='mr-1 text-sm font-medium text-gray-800'>{t('appDebug.feature.dataSet.queryVariable.title')}</div>
<Tooltip
htmlContent={<div className='w-[180px]'>
{t('appDebug.feature.dataSet.queryVariable.tip')}
</div>}
selector='context-var-tooltip'
>
<HelpCircle className='w-3.5 h-3.5 text-gray-400'/>
</Tooltip>
</div>
<VarPicker {...props} />
</div>
)
}
export default React.memo(ContextVar)

View File

@@ -0,0 +1,3 @@
.trigger:hover .dropdownIcon {
color: #98A2B3;
}

View File

@@ -0,0 +1,99 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ChevronDownIcon } from '@heroicons/react/24/outline'
import cn from 'classnames'
import s from './style.module.css'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type { IInputTypeIconProps } from '@/app/components/app/configuration/config-var/input-type-icon'
import IconTypeIcon from '@/app/components/app/configuration/config-var/input-type-icon'
type Option = { name: string; value: string; type: string }
export type Props = {
value: string | undefined
options: Option[]
onChange: (value: string) => void
}
const VarItem: FC<{ item: Option }> = ({ item }) => (
<div className='flex items-center h-[18px] px-1 bg-[#EFF8FF] rounded space-x-1'>
<IconTypeIcon type={item.type as IInputTypeIconProps['type']} className='text-[#1570EF]' />
<div className='flex text-xs font-medium text-[#1570EF]'>
<span className='opacity-60'>{'{{'}</span>
<span className='max-w-[150px] truncate'>{item.value}</span>
<span className='opacity-60'>{'}}'}</span>
</div>
</div>
)
const VarPicker: FC<Props> = ({
value,
options,
onChange,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const currItem = options.find(item => item.value === value)
const notSetVar = !currItem
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
offset={{
mainAxis: 8,
}}
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<div className={cn(
s.trigger,
notSetVar ? 'bg-[#FFFCF5] border-[#FEDF89] text-[#DC6803]' : ' hover:bg-gray-50 border-gray-200 text-primary-600',
open ? 'bg-gray-50' : 'bg-white',
`
flex items-center h-8 justify-center px-2 space-x-1 rounded-lg border shadow-xs cursor-pointer
text-[13px] font-medium
`)}>
<div>
{value
? (
<VarItem item={currItem as Option} />
)
: (<div>
{t('appDebug.feature.dataSet.queryVariable.choosePlaceholder')}
</div>)}
</div>
<ChevronDownIcon className={cn(s.dropdownIcon, open && 'rotate-180 text-[#98A2B3]', 'w-3.5 h-3.5')} />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
{options.length > 0
? (<div className='w-[240px] max-h-[50vh] overflow-y-auto p-1 border bg-white border-gray-200 rounded-lg shadow-lg'>
{options.map(({ name, value, type }, index) => (
<div
key={index}
className='px-3 py-1 flex rounded-lg hover:bg-gray-50 cursor-pointer'
onClick={() => {
onChange(value)
setOpen(false)
}}
>
<VarItem item={{ name, value, type }} />
</div>
))}
</div>)
: (
<div className='w-[240px] p-6 bg-white border border-gray-200 rounded-lg shadow-lg'>
<div className='mb-1 text-sm font-medium text-gray-700'>{t('appDebug.feature.dataSet.queryVariable.noVar')}</div>
<div className='text-xs leading-normal text-gray-500'>{t('appDebug.feature.dataSet.queryVariable.noVarTip')}</div>
</div>
)}
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default React.memo(VarPicker)

View File

@@ -10,8 +10,10 @@ import FeaturePanel from '../base/feature-panel'
import OperationBtn from '../base/operation-btn'
import CardItem from './card-item'
import SelectDataSet from './select-dataset'
import ContextVar from './context-var'
import ConfigContext from '@/context/debug-configuration'
import type { DataSet } from '@/models/datasets'
import { AppType } from '@/types/app'
const Icon = (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -23,9 +25,12 @@ const Icon = (
const DatasetConfig: FC = () => {
const { t } = useTranslation()
const {
mode,
dataSets: dataSet,
setDataSets: setDataSet,
setFormattingChanged,
modelConfig,
setModelConfig,
} = useContext(ConfigContext)
const selectedIds = dataSet.map(item => item.id)
@@ -60,6 +65,25 @@ const DatasetConfig: FC = () => {
setFormattingChanged(true)
}
const promptVariables = modelConfig.configs.prompt_variables
const promptVariablesToSelect = promptVariables.map(item => ({
name: item.name,
type: item.type,
value: item.key,
}))
const selectedContextVar = promptVariables?.find(item => item.is_context_var)
const handleSelectContextVar = (selectedValue: string) => {
const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_variables = modelConfig.configs.prompt_variables.map((item) => {
return ({
...item,
is_context_var: item.key === selectedValue,
})
})
})
setModelConfig(newModelConfig)
}
return (
<FeaturePanel
className='mt-3'
@@ -67,10 +91,11 @@ const DatasetConfig: FC = () => {
title={t('appDebug.feature.dataSet.title')}
headerRight={<OperationBtn type="add" onClick={showSelectDataSet} />}
hasHeaderBottomBorder={!hasData}
noBodySpacing
>
{hasData
? (
<div className='flex flex-wrap justify-between'>
<div className='flex flex-wrap mt-1 px-3 justify-between'>
{dataSet.map(item => (
<CardItem
className="mb-2"
@@ -82,9 +107,19 @@ const DatasetConfig: FC = () => {
</div>
)
: (
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.feature.dataSet.noData')}</div>
<div className='mt-1 px-3 pb-3'>
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.feature.dataSet.noData')}</div>
</div>
)}
{mode === AppType.completion && dataSet.length > 0 && (
<ContextVar
value={selectedContextVar?.key}
options={promptVariablesToSelect}
onChange={handleSelectContextVar}
/>
)}
{isShowSelectDataSet && (
<SelectDataSet
isShow={isShowSelectDataSet}