From 2bc0632d0d1882b3893a6efedf76ee51f5c3765b Mon Sep 17 00:00:00 2001 From: -LAN- Date: Tue, 23 Jul 2024 17:59:32 +0800 Subject: [PATCH 01/24] fix(segments): Support NoneType. (#6581) --- api/core/app/segments/__init__.py | 7 +++++-- api/core/app/segments/factory.py | 3 +++ api/core/app/segments/segments.py | 17 +++++++++++++++++ api/core/app/segments/types.py | 3 ++- api/core/app/segments/variables.py | 5 +++++ api/tests/unit_tests/app/test_variables.py | 14 +++++++++++++- 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/api/core/app/segments/__init__.py b/api/core/app/segments/__init__.py index e5cecd35fd..0179d28887 100644 --- a/api/core/app/segments/__init__.py +++ b/api/core/app/segments/__init__.py @@ -1,11 +1,12 @@ from .segment_group import SegmentGroup -from .segments import Segment +from .segments import NoneSegment, Segment from .types import SegmentType from .variables import ( ArrayVariable, FileVariable, FloatVariable, IntegerVariable, + NoneVariable, ObjectVariable, SecretVariable, StringVariable, @@ -23,5 +24,7 @@ __all__ = [ 'Variable', 'SegmentType', 'SegmentGroup', - 'Segment' + 'Segment', + 'NoneSegment', + 'NoneVariable', ] diff --git a/api/core/app/segments/factory.py b/api/core/app/segments/factory.py index 4f0b361d95..187042ec03 100644 --- a/api/core/app/segments/factory.py +++ b/api/core/app/segments/factory.py @@ -10,6 +10,7 @@ from .variables import ( FileVariable, FloatVariable, IntegerVariable, + NoneVariable, ObjectVariable, SecretVariable, StringVariable, @@ -39,6 +40,8 @@ def build_variable_from_mapping(m: Mapping[str, Any], /) -> Variable: def build_anonymous_variable(value: Any, /) -> Variable: + if value is None: + return NoneVariable(name='anonymous') if isinstance(value, str): return StringVariable(name='anonymous', value=value) if isinstance(value, int): diff --git a/api/core/app/segments/segments.py b/api/core/app/segments/segments.py index b7ca250ff2..e6bf6cc3a3 100644 --- a/api/core/app/segments/segments.py +++ b/api/core/app/segments/segments.py @@ -43,6 +43,23 @@ class Segment(BaseModel): return self.value +class NoneSegment(Segment): + value_type: SegmentType = SegmentType.NONE + value: None = None + + @property + def text(self) -> str: + return 'null' + + @property + def log(self) -> str: + return 'null' + + @property + def markdown(self) -> str: + return 'null' + + class StringSegment(Segment): value_type: SegmentType = SegmentType.STRING value: str diff --git a/api/core/app/segments/types.py b/api/core/app/segments/types.py index 517f210533..346b659601 100644 --- a/api/core/app/segments/types.py +++ b/api/core/app/segments/types.py @@ -14,4 +14,5 @@ class SegmentType(str, Enum): ARRAY_STRING = 'array[string]' ARRAY_NUMBER = 'array[number]' ARRAY_OBJECT = 'array[object]' - ARRAY_FILE = 'array[file]' \ No newline at end of file + ARRAY_FILE = 'array[file]' + NONE = 'none' \ No newline at end of file diff --git a/api/core/app/segments/variables.py b/api/core/app/segments/variables.py index e600b442d6..02c06074ab 100644 --- a/api/core/app/segments/variables.py +++ b/api/core/app/segments/variables.py @@ -81,3 +81,8 @@ class SecretVariable(StringVariable): @property def log(self) -> str: return encrypter.obfuscated_token(self.value) + + +class NoneVariable(Variable): + value_type: SegmentType = SegmentType.NONE + value: None = None \ No newline at end of file diff --git a/api/tests/unit_tests/app/test_variables.py b/api/tests/unit_tests/app/test_variables.py index 05b080bdcf..40872c8d53 100644 --- a/api/tests/unit_tests/app/test_variables.py +++ b/api/tests/unit_tests/app/test_variables.py @@ -2,14 +2,16 @@ import pytest from pydantic import ValidationError from core.app.segments import ( + ArrayVariable, FloatVariable, IntegerVariable, + NoneVariable, + ObjectVariable, SecretVariable, SegmentType, StringVariable, factory, ) -from core.app.segments.variables import ArrayVariable, ObjectVariable def test_string_variable(): @@ -134,3 +136,13 @@ def test_variable_to_object(): assert var.to_object() == 3.14 var = SecretVariable(name='secret', value='secret_value') assert var.to_object() == 'secret_value' + + +def test_build_a_object_variable_with_none_value(): + var = factory.build_anonymous_variable( + { + 'key1': None, + } + ) + assert isinstance(var, ObjectVariable) + assert isinstance(var.value['key1'], NoneVariable) From 0f6a064c08fbb3979201af8fb5932c7d191cda96 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 23 Jul 2024 19:51:38 +0800 Subject: [PATCH 02/24] chore: enchance auto generate prompt (#6564) --- .../config-prompt/advanced-prompt-input.tsx | 3 + .../app/configuration/config-prompt/index.tsx | 14 + .../config-prompt/simple-prompt-input.tsx | 76 +++-- .../config/automatic/automatic-btn.tsx | 14 +- .../config/automatic/get-automatic-res.tsx | 275 +++++++++++------- .../config/automatic/style.module.css | 7 + .../icons/assets/vender/other/generator.svg | 4 + .../icons/src/vender/other/Generator.json | 37 +++ .../base/icons/src/vender/other/Generator.tsx | 16 + .../base/icons/src/vender/other/index.ts | 1 + .../solid/general/QuestionTriangle.json | 2 +- .../nodes/_base/components/prompt/editor.tsx | 12 + .../llm/components/config-prompt-item.tsx | 19 +- .../nodes/llm/components/config-prompt.tsx | 20 +- .../llm/components/prompt-generator-btn.tsx | 48 +++ .../components/workflow/nodes/llm/panel.tsx | 1 + web/i18n/de-DE/app-debug.ts | 19 +- web/i18n/en-US/app-debug.ts | 64 +++- web/i18n/fr-FR/app-debug.ts | 19 +- web/i18n/ja-JP/app-debug.ts | 17 -- web/i18n/zh-Hans/app-debug.ts | 61 +++- web/i18n/zh-Hant/app-debug.ts | 19 +- web/service/debug.ts | 1 + web/types/app.ts | 109 +++---- 24 files changed, 565 insertions(+), 293 deletions(-) create mode 100644 web/app/components/app/configuration/config/automatic/style.module.css create mode 100644 web/app/components/base/icons/assets/vender/other/generator.svg create mode 100644 web/app/components/base/icons/src/vender/other/Generator.json create mode 100644 web/app/components/base/icons/src/vender/other/Generator.tsx create mode 100644 web/app/components/base/icons/src/vender/other/index.ts create mode 100644 web/app/components/workflow/nodes/llm/components/prompt-generator-btn.tsx diff --git a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx index 641cdd7e23..fb3ceadc0d 100644 --- a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx @@ -43,6 +43,7 @@ type Props = { promptVariables: PromptVariable[] isContextMissing: boolean onHideContextMissingTip: () => void + noResize?: boolean } const AdvancedPromptInput: FC = ({ @@ -56,6 +57,7 @@ const AdvancedPromptInput: FC = ({ promptVariables, isContextMissing, onHideContextMissingTip, + noResize, }) => { const { t } = useTranslation() const { eventEmitter } = useEventEmitterContextContext() @@ -207,6 +209,7 @@ const AdvancedPromptInput: FC = ({
{value.length}
)} + hideResize={noResize} > void } @@ -26,7 +30,11 @@ const Prompt: FC = ({ mode, promptTemplate, promptVariables, + noTitle, + gradientBorder, readonly = false, + editorHeight, + noResize, onChange, }) => { const { t } = useTranslation() @@ -99,6 +107,10 @@ const Prompt: FC = ({ promptVariables={promptVariables} readonly={readonly} onChange={onChange} + noTitle={noTitle} + gradientBorder={gradientBorder} + editorHeight={editorHeight} + noResize={noResize} /> ) } @@ -121,6 +133,7 @@ const Prompt: FC = ({ promptVariables={promptVariables} isContextMissing={isContextMissing && !isHideContextMissTip} onHideContextMissingTip={() => setIsHideContextMissTip(true)} + noResize={noResize} /> )) ) @@ -136,6 +149,7 @@ const Prompt: FC = ({ promptVariables={promptVariables} isContextMissing={isContextMissing && !isHideContextMissTip} onHideContextMissingTip={() => setIsHideContextMissTip(true)} + noResize={noResize} /> ) } diff --git a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx index a15f538227..2811c42402 100644 --- a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx @@ -14,6 +14,7 @@ import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap' import cn from '@/utils/classnames' import { type PromptVariable } from '@/models/debug' import Tooltip from '@/app/components/base/tooltip' +import type { CompletionParams } from '@/types/app' import { AppType } from '@/types/app' import { getNewVar, getVars } from '@/utils/var' import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn' @@ -28,6 +29,7 @@ import { useEventEmitterContextContext } from '@/context/event-emitter' import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var' import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block' import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '@/app/components/base/prompt-editor/plugins/update-block' +import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' export type ISimplePromptInput = { mode: AppType @@ -35,6 +37,10 @@ export type ISimplePromptInput = { promptVariables: PromptVariable[] readonly?: boolean onChange?: (promp: string, promptVariables: PromptVariable[]) => void + noTitle?: boolean + gradientBorder?: boolean + editorHeight?: number + noResize?: boolean } const Prompt: FC = ({ @@ -43,11 +49,19 @@ const Prompt: FC = ({ promptVariables, readonly = false, onChange, + noTitle, + gradientBorder, + editorHeight: initEditorHeight, + noResize, }) => { const { t } = useTranslation() + const media = useBreakpoints() + const isMobile = media === MediaType.mobile + const { eventEmitter } = useEventEmitterContextContext() const { modelConfig, + completionParams, dataSets, setModelConfig, setPrevPromptConfig, @@ -116,6 +130,11 @@ const Prompt: FC = ({ const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) const handleAutomaticRes = (res: AutomaticRes) => { + // put eventEmitter in first place to prevent overwrite the configs.prompt_variables.But another problem is that prompt won't hight the prompt_variables. + eventEmitter?.emit({ + type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER, + payload: res.prompt, + } as any) const newModelConfig = produce(modelConfig, (draft) => { draft.configs.prompt_template = res.prompt draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true })) @@ -125,41 +144,41 @@ const Prompt: FC = ({ if (mode !== AppType.completion) setIntroduction(res.opening_statement) showAutomaticFalse() - eventEmitter?.emit({ - type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER, - payload: res.prompt, - } as any) } - const minHeight = 228 + const minHeight = initEditorHeight || 228 const [editorHeight, setEditorHeight] = useState(minHeight) return ( -
+
-
-
-
{mode !== AppType.completion ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}
- {!readonly && ( - - {t('appDebug.promptTip')} -
} - selector='config-prompt-tooltip'> - - - )} + {!noTitle && ( +
+
+
{mode !== AppType.completion ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}
+ {!readonly && ( + + {t('appDebug.promptTip')} +
} + selector='config-prompt-tooltip'> + + + )} +
+
+ {!isAgent && !readonly && !isMobile && ( + + )} +
-
- {!isAgent && !readonly && ( - - )} -
-
+ )} +
{promptTemplate.length}
@@ -216,6 +235,7 @@ const Prompt: FC = ({ onBlur={() => { handleChange(promptTemplate, getVars(promptTemplate)) }} + editable={!readonly} />
@@ -232,6 +252,14 @@ const Prompt: FC = ({ {showAutomatic && ( void } - -const leftIcon = ( - - - - - - -) const AutomaticBtn: FC = ({ onClick, }) => { const { t } = useTranslation() return ( -
- {leftIcon} + {t('appDebug.operation.automatic')}
) diff --git a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx index fa58253cac..1939dd3ad7 100644 --- a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx +++ b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx @@ -1,70 +1,126 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' +import { + RiDatabase2Line, + RiFileExcel2Line, + RiGitCommitLine, + RiNewspaperLine, + RiPresentationLine, + RiRoadMapLine, + RiTerminalBoxLine, + RiTranslate, + RiUser2Line, +} from '@remixicon/react' +import cn from 'classnames' +import s from './style.module.css' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Toast from '@/app/components/base/toast' import { generateRule } from '@/service/debug' import ConfigPrompt from '@/app/components/app/configuration/config-prompt' +import type { Model } from '@/types/app' import { AppType } from '@/types/app' import ConfigVar from '@/app/components/app/configuration/config-var' import OpeningStatement from '@/app/components/app/configuration/features/chat-group/opening-statement' import GroupName from '@/app/components/app/configuration/base/group-name' import Loading from '@/app/components/base/loading' import Confirm from '@/app/components/base/confirm' + // type import type { AutomaticRes } from '@/service/debug' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' - -const noDataIcon = ( - - - -) +import { Generator } from '@/app/components/base/icons/src/vender/other' export type IGetAutomaticResProps = { mode: AppType + model: Model isShow: boolean onClose: () => void onFinished: (res: AutomaticRes) => void + isInLLMNode?: boolean } -const genIcon = ( - - - - - -) +const TryLabel: FC<{ + Icon: any + text: string + onClick: () => void +}> = ({ Icon, text, onClick }) => { + return ( +
+ +
{text}
+
+ ) +} const GetAutomaticRes: FC = ({ mode, + model, isShow, onClose, - // appId, + isInLLMNode, onFinished, }) => { const { t } = useTranslation() - const media = useBreakpoints() - const isMobile = media === MediaType.mobile + const tryList = [ + { + icon: RiTerminalBoxLine, + key: 'pythonDebugger', + }, + { + icon: RiTranslate, + key: 'translation', + }, + { + icon: RiPresentationLine, + key: 'meetingTakeaways', + }, + { + icon: RiNewspaperLine, + key: 'writingsPolisher', + }, + { + icon: RiUser2Line, + key: 'professionalAnalyst', + }, + { + icon: RiFileExcel2Line, + key: 'excelFormulaExpert', + }, + { + icon: RiRoadMapLine, + key: 'travelPlanning', + }, + { + icon: RiDatabase2Line, + key: 'SQLSorcerer', + }, + { + icon: RiGitCommitLine, + key: 'GitGud', + }, + ] - const [audiences, setAudiences] = React.useState('') - const [hopingToSolve, setHopingToSolve] = React.useState('') - const isValid = () => { - if (audiences.trim() === '') { - Toast.notify({ - type: 'error', - message: t('appDebug.automatic.audiencesRequired'), - }) - return false + const [instruction, setInstruction] = React.useState('') + const handleChooseTemplate = useCallback((key: string) => { + return () => { + const template = t(`appDebug.generate.template.${key}.instruction`) + setInstruction(template) } - if (hopingToSolve.trim() === '') { + }, [t]) + const isValid = () => { + if (instruction.trim() === '') { Toast.notify({ type: 'error', - message: t('appDebug.automatic.problemRequired'), + message: t('common.errorMsg.fieldRequired', { + field: t('appDebug.generate.instruction'), + }), }) return false } @@ -76,14 +132,17 @@ const GetAutomaticRes: FC = ({ const renderLoading = (
-
{t('appDebug.automatic.loading')}
+
{t('appDebug.generate.loading')}
) const renderNoData = (
- {noDataIcon} -
{t('appDebug.automatic.noData')}
+ +
+
{t('appDebug.generate.noDataLine1')}
+
{t('appDebug.generate.noDataLine2')}
+
) @@ -94,11 +153,18 @@ const GetAutomaticRes: FC = ({ return setLoadingTrue() try { - const res = await generateRule({ - audiences, - hoping_to_solve: hopingToSolve, + const { error, ...res } = await generateRule({ + instruction, + model_config: model, + no_variable: !!isInLLMNode, }) setRes(res) + if (error) { + Toast.notify({ + type: 'error', + message: error, + }) + } } finally { setLoadingFalse() @@ -107,24 +173,7 @@ const GetAutomaticRes: FC = ({ const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false) - const isShowAutoPromptInput = () => { - if (isMobile) { - // hide prompt panel on mobile if it is loading or has had result - if (isLoading || res) - return false - return true - } - - // always display prompt panel on desktop mode - return true - } - const isShowAutoPromptResPlaceholder = () => { - if (isMobile) { - // hide placeholder panel on mobile - return false - } - return !isLoading && !res } @@ -132,75 +181,97 @@ const GetAutomaticRes: FC = ({ -
- {isShowAutoPromptInput() &&
-
-
{t('appDebug.automatic.title')}
-
{t('appDebug.automatic.description')}
+
+
+
+
{t('appDebug.generate.title')}
+
{t('appDebug.generate.description')}
+
+
+
+
{t('appDebug.generate.tryIt')}
+
+
+
+ {tryList.map(item => ( + + ))} +
{/* inputs */} -
-
-
{t('appDebug.automatic.intendedAudience')}
- setAudiences(e.target.value)} /> -
-
-
{t('appDebug.automatic.solveProblem')}
-