mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-20 12:39:14 +08:00
feat: llm support vision
This commit is contained in:
parent
251ab5418f
commit
0455e4e1a5
@ -3,31 +3,52 @@ import type { FC } from 'react'
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
|
import VarReferencePicker from './variable/var-reference-picker'
|
||||||
import ResolutionPicker from '@/app/components/workflow/nodes/llm/components/resolution-picker'
|
import ResolutionPicker from '@/app/components/workflow/nodes/llm/components/resolution-picker'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import Switch from '@/app/components/base/switch'
|
import Switch from '@/app/components/base/switch'
|
||||||
import type { VisionSetting } from '@/app/components/workflow/types'
|
import { type ValueSelector, type Var, VarType, type VisionSetting } from '@/app/components/workflow/types'
|
||||||
import type { Resolution } from '@/types/app'
|
import { Resolution } from '@/types/app'
|
||||||
|
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||||
const i18nPrefix = 'workflow.nodes.llm'
|
const i18nPrefix = 'workflow.nodes.llm'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
isVisionModel: boolean
|
||||||
|
readOnly: boolean
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
onEnabledChange: (enabled: boolean) => void
|
onEnabledChange: (enabled: boolean) => void
|
||||||
config: VisionSetting
|
nodeId: string
|
||||||
|
config?: VisionSetting
|
||||||
onConfigChange: (config: VisionSetting) => void
|
onConfigChange: (config: VisionSetting) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfigVision: FC<Props> = ({
|
const ConfigVision: FC<Props> = ({
|
||||||
|
isVisionModel,
|
||||||
|
readOnly,
|
||||||
enabled,
|
enabled,
|
||||||
onEnabledChange,
|
onEnabledChange,
|
||||||
config,
|
nodeId,
|
||||||
|
config = {
|
||||||
|
detail: Resolution.high,
|
||||||
|
valueSelector: [],
|
||||||
|
},
|
||||||
onConfigChange,
|
onConfigChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const filterVar = useCallback((payload: Var) => {
|
||||||
|
return [VarType.file, VarType.arrayFile].includes(payload.type)
|
||||||
|
}, [])
|
||||||
const handleVisionResolutionChange = useCallback((resolution: Resolution) => {
|
const handleVisionResolutionChange = useCallback((resolution: Resolution) => {
|
||||||
const newConfig = produce(config, (draft) => {
|
const newConfig = produce(config, (draft) => {
|
||||||
draft.resolution = resolution
|
draft.detail = resolution
|
||||||
|
})
|
||||||
|
onConfigChange(newConfig)
|
||||||
|
}, [config, onConfigChange])
|
||||||
|
|
||||||
|
const handleVarSelectorChange = useCallback((valueSelector: ValueSelector | string) => {
|
||||||
|
const newConfig = produce(config, (draft) => {
|
||||||
|
draft.valueSelector = valueSelector as ValueSelector
|
||||||
})
|
})
|
||||||
onConfigChange(newConfig)
|
onConfigChange(newConfig)
|
||||||
}, [config, onConfigChange])
|
}, [config, onConfigChange])
|
||||||
@ -37,15 +58,29 @@ const ConfigVision: FC<Props> = ({
|
|||||||
title={t(`${i18nPrefix}.vision`)}
|
title={t(`${i18nPrefix}.vision`)}
|
||||||
tooltip={t('appDebug.vision.description')!}
|
tooltip={t('appDebug.vision.description')!}
|
||||||
operations={
|
operations={
|
||||||
<Switch size='md' defaultValue={enabled} onChange={onEnabledChange} />
|
// disabled={isVisionModel}
|
||||||
|
<TooltipPlus hideArrow popupContent={t('appDebug.vision.onlySupportVisionModelTip')!} >
|
||||||
|
<Switch disabled={readOnly || !isVisionModel} size='md' defaultValue={!isVisionModel ? false : enabled} onChange={onEnabledChange} />
|
||||||
|
</TooltipPlus>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{enabled
|
{(enabled || !isVisionModel)
|
||||||
? (
|
? (
|
||||||
<ResolutionPicker
|
<div>
|
||||||
value={config.resolution}
|
<VarReferencePicker
|
||||||
onChange={handleVisionResolutionChange}
|
className='mb-4'
|
||||||
/>
|
filterVar={filterVar}
|
||||||
|
nodeId={nodeId}
|
||||||
|
value={config.valueSelector || []}
|
||||||
|
onChange={handleVarSelectorChange}
|
||||||
|
readonly={readOnly}
|
||||||
|
/>
|
||||||
|
<ResolutionPicker
|
||||||
|
value={config.detail}
|
||||||
|
onChange={handleVisionResolutionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ const TRIGGER_DEFAULT_WIDTH = 227
|
|||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
nodeId: string
|
nodeId: string
|
||||||
isShowNodeName: boolean
|
isShowNodeName?: boolean
|
||||||
readonly: boolean
|
readonly: boolean
|
||||||
value: ValueSelector | string
|
value: ValueSelector | string
|
||||||
onChange: (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => void
|
onChange: (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => void
|
||||||
@ -56,7 +56,7 @@ const VarReferencePicker: FC<Props> = ({
|
|||||||
nodeId,
|
nodeId,
|
||||||
readonly,
|
readonly,
|
||||||
className,
|
className,
|
||||||
isShowNodeName,
|
isShowNodeName = true,
|
||||||
value,
|
value,
|
||||||
onOpen = () => { },
|
onOpen = () => { },
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { RiQuestionLine } from '@remixicon/react'
|
import { RiQuestionLine } from '@remixicon/react'
|
||||||
import MemoryConfig from '../_base/components/memory-config'
|
import MemoryConfig from '../_base/components/memory-config'
|
||||||
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
||||||
|
import ConfigVision from '../_base/components/config-vision'
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import ResolutionPicker from './components/resolution-picker'
|
|
||||||
import type { LLMNodeType } from './types'
|
import type { LLMNodeType } from './types'
|
||||||
import ConfigPrompt from './components/config-prompt'
|
import ConfigPrompt from './components/config-prompt'
|
||||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||||
@ -14,14 +14,13 @@ import Field from '@/app/components/workflow/nodes/_base/components/field'
|
|||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
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 { InputVarType, 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'
|
||||||
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
|
||||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||||
import Switch from '@/app/components/base/switch'
|
|
||||||
const i18nPrefix = 'workflow.nodes.llm'
|
const i18nPrefix = 'workflow.nodes.llm'
|
||||||
|
|
||||||
const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||||
@ -37,7 +36,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
isChatMode,
|
isChatMode,
|
||||||
isCompletionModel,
|
isCompletionModel,
|
||||||
shouldShowContextTip,
|
shouldShowContextTip,
|
||||||
isShowVisionConfig,
|
isVisionModel,
|
||||||
handleModelChanged,
|
handleModelChanged,
|
||||||
hasSetBlockStatus,
|
hasSetBlockStatus,
|
||||||
handleCompletionParamsChange,
|
handleCompletionParamsChange,
|
||||||
@ -103,7 +102,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isShowVisionConfig) {
|
if (isVisionModel) {
|
||||||
forms.push(
|
forms.push(
|
||||||
{
|
{
|
||||||
label: t(`${i18nPrefix}.vision`)!,
|
label: t(`${i18nPrefix}.vision`)!,
|
||||||
@ -259,28 +258,15 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Vision: GPT4-vision and so on */}
|
{/* Vision: GPT4-vision and so on */}
|
||||||
{isShowVisionConfig && (
|
<ConfigVision
|
||||||
<>
|
nodeId={id}
|
||||||
<Split />
|
readOnly={readOnly}
|
||||||
<Field
|
isVisionModel={isVisionModel}
|
||||||
title={t(`${i18nPrefix}.vision`)}
|
enabled={inputs.vision.enabled}
|
||||||
tooltip={t('appDebug.vision.description')!}
|
onEnabledChange={handleVisionResolutionEnabledChange}
|
||||||
operations={
|
config={inputs.vision.configs}
|
||||||
<Switch size='md' defaultValue={inputs.vision.enabled} onChange={handleVisionResolutionEnabledChange} />
|
onConfigChange={handleVisionResolutionChange}
|
||||||
}
|
/>
|
||||||
>
|
|
||||||
{inputs.vision.enabled
|
|
||||||
? (
|
|
||||||
<ResolutionPicker
|
|
||||||
value={inputs.vision.configs?.detail || Resolution.high}
|
|
||||||
onChange={handleVisionResolutionChange}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
|
|
||||||
</Field>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Split />
|
<Split />
|
||||||
<div className='px-4 pt-4 pb-2'>
|
<div className='px-4 pt-4 pb-2'>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type { Resolution } from '@/types/app'
|
import type { CommonNodeType, Memory, ModelConfig, PromptItem, ValueSelector, Variable, VisionSetting } from '@/app/components/workflow/types'
|
||||||
import type { CommonNodeType, Memory, ModelConfig, PromptItem, ValueSelector, Variable } from '@/app/components/workflow/types'
|
|
||||||
|
|
||||||
export type LLMNodeType = CommonNodeType & {
|
export type LLMNodeType = CommonNodeType & {
|
||||||
model: ModelConfig
|
model: ModelConfig
|
||||||
@ -14,8 +13,6 @@ export type LLMNodeType = CommonNodeType & {
|
|||||||
}
|
}
|
||||||
vision: {
|
vision: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
configs?: {
|
configs?: VisionSetting
|
||||||
detail: Resolution
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { EditionType, VarType } from '../../types'
|
import { EditionType, VarType } from '../../types'
|
||||||
import type { Memory, PromptItem, ValueSelector, Var, Variable } from '../../types'
|
import type { Memory, PromptItem, ValueSelector, Var, Variable, VisionSetting } from '../../types'
|
||||||
import { useStore } from '../../store'
|
import { useStore } from '../../store'
|
||||||
import {
|
import {
|
||||||
useIsChatMode,
|
useIsChatMode,
|
||||||
@ -147,13 +147,13 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
|||||||
model: model.name,
|
model: model.name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
|
const isVisionModel = !!currModel?.features?.includes(ModelFeatureEnum.vision)
|
||||||
// change to vision model to set vision enabled, else disabled
|
// change to vision model to set vision enabled, else disabled
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!modelChanged)
|
if (!modelChanged)
|
||||||
return
|
return
|
||||||
setModelChanged(false)
|
setModelChanged(false)
|
||||||
if (!isShowVisionConfig) {
|
if (!isVisionModel) {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
draft.vision = {
|
draft.vision = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@ -169,6 +169,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
configs: {
|
configs: {
|
||||||
detail: Resolution.high,
|
detail: Resolution.high,
|
||||||
|
valueSelector: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,7 +177,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
|||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isShowVisionConfig, modelChanged])
|
}, [isVisionModel, modelChanged])
|
||||||
|
|
||||||
// variables
|
// variables
|
||||||
const isShowVars = (() => {
|
const isShowVars = (() => {
|
||||||
@ -298,31 +299,18 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
|||||||
if (!draft.vision) {
|
if (!draft.vision) {
|
||||||
draft.vision = {
|
draft.vision = {
|
||||||
enabled,
|
enabled,
|
||||||
configs: {
|
|
||||||
detail: Resolution.high,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
draft.vision.enabled = enabled
|
draft.vision.enabled = enabled
|
||||||
if (!draft.vision.configs) {
|
|
||||||
draft.vision.configs = {
|
|
||||||
detail: Resolution.high,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
}, [inputs, setInputs])
|
||||||
|
|
||||||
const handleVisionResolutionChange = useCallback((newResolution: Resolution) => {
|
const handleVisionResolutionChange = useCallback((config: VisionSetting) => {
|
||||||
const newInputs = produce(inputs, (draft) => {
|
const newInputs = produce(inputs, (draft) => {
|
||||||
if (!draft.vision.configs) {
|
draft.vision.configs = config
|
||||||
draft.vision.configs = {
|
|
||||||
detail: Resolution.high,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
draft.vision.configs.detail = newResolution
|
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
}, [inputs, setInputs])
|
||||||
@ -425,7 +413,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
|||||||
isCompletionModel,
|
isCompletionModel,
|
||||||
hasSetBlockStatus,
|
hasSetBlockStatus,
|
||||||
shouldShowContextTip,
|
shouldShowContextTip,
|
||||||
isShowVisionConfig,
|
isVisionModel,
|
||||||
handleModelChanged,
|
handleModelChanged,
|
||||||
handleCompletionParamsChange,
|
handleCompletionParamsChange,
|
||||||
isShowVars,
|
isShowVars,
|
||||||
|
@ -347,5 +347,5 @@ export type UploadFileSetting = {
|
|||||||
|
|
||||||
export type VisionSetting = {
|
export type VisionSetting = {
|
||||||
valueSelector: ValueSelector
|
valueSelector: ValueSelector
|
||||||
resolution: Resolution
|
detail: Resolution
|
||||||
}
|
}
|
||||||
|
@ -356,6 +356,7 @@ const translation = {
|
|||||||
vision: {
|
vision: {
|
||||||
name: 'Vision',
|
name: 'Vision',
|
||||||
description: 'Enable Vision will allows the model to take in images and answer questions about them. ',
|
description: 'Enable Vision will allows the model to take in images and answer questions about them. ',
|
||||||
|
onlySupportVisionModelTip: 'Only supports vision models',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
visionSettings: {
|
visionSettings: {
|
||||||
title: 'Vision Settings',
|
title: 'Vision Settings',
|
||||||
|
@ -351,6 +351,7 @@ const translation = {
|
|||||||
vision: {
|
vision: {
|
||||||
name: '视觉',
|
name: '视觉',
|
||||||
description: '开启视觉功能将允许模型输入图片,并根据图像内容的理解回答用户问题',
|
description: '开启视觉功能将允许模型输入图片,并根据图像内容的理解回答用户问题',
|
||||||
|
onlySupportVisionModelTip: '只有视觉模型配置视觉功能',
|
||||||
settings: '设置',
|
settings: '设置',
|
||||||
visionSettings: {
|
visionSettings: {
|
||||||
title: '视觉设置',
|
title: '视觉设置',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user