diff --git a/web/app/components/app/chat/index.tsx b/web/app/components/app/chat/index.tsx index 8ac9e98378..67a2586585 100644 --- a/web/app/components/app/chat/index.tsx +++ b/web/app/components/app/chat/index.tsx @@ -53,6 +53,7 @@ export type IChatProps = { isShowConfigElem?: boolean dataSets?: DataSet[] isShowCitationHitInfo?: boolean + isShowPromptLog?: boolean } const Chat: FC = ({ @@ -81,6 +82,7 @@ const Chat: FC = ({ isShowConfigElem, dataSets, isShowCitationHitInfo, + isShowPromptLog, }) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) @@ -186,7 +188,18 @@ const Chat: FC = ({ isShowCitationHitInfo={isShowCitationHitInfo} /> } - return + return ( + + ) })} { diff --git a/web/app/components/app/chat/log/index.tsx b/web/app/components/app/chat/log/index.tsx new file mode 100644 index 0000000000..0d7e216ee2 --- /dev/null +++ b/web/app/components/app/chat/log/index.tsx @@ -0,0 +1,70 @@ +import type { Dispatch, FC, ReactNode, RefObject, SetStateAction } from 'react' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { File02 } from '@/app/components/base/icons/src/vender/line/files' +import PromptLogModal from '@/app/components/base/prompt-log-modal' +import Tooltip from '@/app/components/base/tooltip' + +export type LogData = { + role: string + text: string +} + +type LogProps = { + containerRef: RefObject + log: LogData[] + children?: (v: Dispatch>) => ReactNode +} +const Log: FC = ({ + containerRef, + children, + log, +}) => { + const { t } = useTranslation() + const [showModal, setShowModal] = useState(false) + const [width, setWidth] = useState(0) + + const adjustModalWidth = () => { + if (containerRef.current) + setWidth(document.body.clientWidth - (containerRef.current?.clientWidth + 56 + 16)) + } + + useEffect(() => { + adjustModalWidth() + }, []) + + return ( + <> + { + children + ? children(setShowModal) + : ( + + + + ) + } + { + showModal && ( + setShowModal(false)} + /> + ) + } + + ) +} + +export default Log diff --git a/web/app/components/app/chat/question/index.tsx b/web/app/components/app/chat/question/index.tsx index cabb5f995d..bca9cf8776 100644 --- a/web/app/components/app/chat/question/index.tsx +++ b/web/app/components/app/chat/question/index.tsx @@ -1,22 +1,34 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import React, { useRef } from 'react' import { useContext } from 'use-context-selector' import s from '../style.module.css' import type { IChatItem } from '../type' +import Log from '../log' import MoreInfo from '../more-info' import AppContext from '@/context/app-context' import { Markdown } from '@/app/components/base/markdown' -type IQuestionProps = Pick +type IQuestionProps = Pick & { + isShowPromptLog?: boolean + item: IChatItem + isResponsing?: boolean +} -const Question: FC = ({ id, content, more, useCurrentUserAvatar }) => { +const Question: FC = ({ id, content, more, useCurrentUserAvatar, isShowPromptLog, item, isResponsing }) => { const { userProfile } = useContext(AppContext) const userName = userProfile?.name + const ref = useRef(null) + return ( -
+
-
+
+ { + isShowPromptLog && !isResponsing && ( + + ) + }
diff --git a/web/app/components/app/chat/type.ts b/web/app/components/app/chat/type.ts index 6df5ce5da2..65dadc865a 100644 --- a/web/app/components/app/chat/type.ts +++ b/web/app/components/app/chat/type.ts @@ -66,6 +66,7 @@ export type IChatItem = { annotation?: Annotation useCurrentUserAvatar?: boolean isOpeningStatement?: boolean + log?: { role: string; text: string }[] } export type MessageEnd = { diff --git a/web/app/components/app/configuration/base/feature-panel/index.tsx b/web/app/components/app/configuration/base/feature-panel/index.tsx index 9169293b9d..5bc3734f60 100644 --- a/web/app/components/app/configuration/base/feature-panel/index.tsx +++ b/web/app/components/app/configuration/base/feature-panel/index.tsx @@ -37,7 +37,7 @@ const FeaturePanel: FC = ({
- {headerIcon &&
{headerIcon}
} + {headerIcon &&
{headerIcon}
}
{title}
diff --git a/web/app/components/app/configuration/config-model/index.tsx b/web/app/components/app/configuration/config-model/index.tsx index 8d03c28deb..61f7169925 100644 --- a/web/app/components/app/configuration/config-model/index.tsx +++ b/web/app/components/app/configuration/config-model/index.tsx @@ -4,11 +4,13 @@ import React, { useEffect, useState } from 'react' import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useBoolean, useClickAway, useGetState } from 'ahooks' -import { Cog8ToothIcon, InformationCircleIcon } from '@heroicons/react/24/outline' +import { InformationCircleIcon } from '@heroicons/react/24/outline' import produce from 'immer' import ParamItem from './param-item' import ModelIcon from './model-icon' import ModelName from './model-name' +import ModelModeTypeLabel from './model-mode-type-label' +import { SlidersH } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import Radio from '@/app/components/base/radio' import Panel from '@/app/components/base/panel' import type { CompletionParams } from '@/models/debug' @@ -25,21 +27,23 @@ import Loading from '@/app/components/base/loading' import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector' import { ModelType, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations' import { useProviderContext } from '@/context/provider-context' - +import type { ModelModeType } from '@/types/app' export type IConfigModelProps = { + isAdvancedMode: boolean mode: string modelId: string provider: ProviderEnum - setModelId: (id: string, provider: ProviderEnum) => void + setModel: (model: { id: string; provider: ProviderEnum; mode: ModelModeType }) => void completionParams: CompletionParams onCompletionParamsChange: (newParams: CompletionParams) => void disabled: boolean } const ConfigModel: FC = ({ + isAdvancedMode, modelId, provider, - setModelId, + setModel, completionParams, onCompletionParamsChange, disabled, @@ -56,6 +60,8 @@ const ConfigModel: FC = ({ const hasEnableParams = currParams && Object.keys(currParams).some(key => currParams[key].enabled) const allSupportParams = ['temperature', 'top_p', 'presence_penalty', 'frequency_penalty', 'max_tokens'] const currSupportParams = currParams ? allSupportParams.filter(key => currParams[key].enabled) : allSupportParams + if (isAdvancedMode) + currSupportParams.push('stop') useEffect(() => { (async () => { @@ -115,11 +121,15 @@ const ConfigModel: FC = ({ return adjustedValue } - const handleSelectModel = (id: string, nextProvider = ProviderEnum.openai) => { + const handleSelectModel = ({ id, provider: nextProvider, mode }: { id: string; provider: ProviderEnum; mode: ModelModeType }) => { return async () => { const prevParamsRule = getAllParams()[provider]?.[modelId] - setModelId(id, nextProvider) + setModel({ + id, + provider: nextProvider || ProviderEnum.openai, + mode, + }) await ensureModelParamLoaded(nextProvider, id) @@ -211,16 +221,26 @@ const ConfigModel: FC = ({ setToneId(matchToneId(completionParams)) }, [completionParams]) - const handleParamChange = (key: string, value: number) => { - const currParamsRule = getAllParams()[provider]?.[modelId] - let notOutRangeValue = parseFloat((value || 0).toFixed(2)) - notOutRangeValue = Math.max(currParamsRule[key].min, notOutRangeValue) - notOutRangeValue = Math.min(currParamsRule[key].max, notOutRangeValue) + const handleParamChange = (key: string, value: number | string[]) => { + if (value === undefined) + return - onCompletionParamsChange({ - ...completionParams, - [key]: notOutRangeValue, - }) + if (key === 'stop') { + onCompletionParamsChange({ + ...completionParams, + [key]: value as string[], + }) + } + else { + const currParamsRule = getAllParams()[provider]?.[modelId] + let notOutRangeValue = parseFloat((value as number).toFixed(2)) + notOutRangeValue = Math.max(currParamsRule[key].min, notOutRangeValue) + notOutRangeValue = Math.min(currParamsRule[key].max, notOutRangeValue) + onCompletionParamsChange({ + ...completionParams, + [key]: notOutRangeValue, + }) + } } const ableStyle = 'bg-indigo-25 border-[#2A87F5] cursor-pointer' const diabledStyle = 'bg-[#FFFCF5] border-[#F79009]' @@ -228,7 +248,7 @@ const ConfigModel: FC = ({ const getToneIcon = (toneId: number) => { const className = 'w-[14px] h-[14px]' const res = ({ - 1: , + 1: , 2: , 3: , 4: , @@ -249,17 +269,19 @@ const ConfigModel: FC = ({ return (
!disabled && toogleShowConfig()} >
- {disabled ? : } + {isAdvancedMode && } + {disabled ? : }
{isShowConfig && ( = ({
{t('appDebug.modelConfig.model')}
= ({ }} modelType={ModelType.textGeneration} onChange={(model) => { - handleSelectModel(model.model_name, model.model_provider.provider_name as ProviderEnum)() + handleSelectModel({ + id: model.model_name, + provider: model.model_provider.provider_name as ProviderEnum, + mode: model.model_mode, + })() }} />
@@ -343,20 +371,21 @@ const ConfigModel: FC = ({ {/* Params */}
- {allParams[provider]?.[modelId] + {(allParams[provider]?.[modelId]) ? ( currSupportParams.map(key => ()) ) : ( - + )}
diff --git a/web/app/components/app/configuration/config-model/model-mode-type-label.tsx b/web/app/components/app/configuration/config-model/model-mode-type-label.tsx new file mode 100644 index 0000000000..efc3af1e23 --- /dev/null +++ b/web/app/components/app/configuration/config-model/model-mode-type-label.tsx @@ -0,0 +1,29 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import type { ModelModeType } from '@/types/app' + +type Props = { + className?: string + type: ModelModeType + isHighlight?: boolean +} + +const ModelModeTypeLabel: FC = ({ + className, + type, + isHighlight, +}) => { + const { t } = useTranslation() + + return ( +
+ {t(`appDebug.modelConfig.modeType.${type}`)} +
+ ) +} +export default React.memo(ModelModeTypeLabel) diff --git a/web/app/components/app/configuration/config-model/param-item.tsx b/web/app/components/app/configuration/config-model/param-item.tsx index df0f7ff9d5..c94244bbde 100644 --- a/web/app/components/app/configuration/config-model/param-item.tsx +++ b/web/app/components/app/configuration/config-model/param-item.tsx @@ -1,8 +1,10 @@ 'use client' import type { FC } from 'react' import React, { useEffect } from 'react' +import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' import Slider from '@/app/components/base/slider' +import TagInput from '@/app/components/base/tag-input' export const getFitPrecisionValue = (num: number, precision: number | null) => { if (!precision || !(`${num}`).includes('.')) @@ -19,16 +21,19 @@ export type IParamIteProps = { id: string name: string tip: string - value: number + value: number | string[] step?: number min?: number max: number precision: number | null - onChange: (key: string, value: number) => void + onChange: (key: string, value: number | string[]) => void + inputType?: 'inputTag' | 'slider' } const TIMES_TEMPLATE = '1000000000000' -const ParamItem: FC = ({ id, name, tip, step = 0.1, min = 0, max, precision, value, onChange }) => { +const ParamItem: FC = ({ id, name, tip, step = 0.1, min = 0, max, precision, value, inputType, onChange }) => { + const { t } = useTranslation() + const getToIntTimes = (num: number) => { if (precision) return parseInt(TIMES_TEMPLATE.slice(0, precision + 1), 10) @@ -45,30 +50,44 @@ const ParamItem: FC = ({ id, name, tip, step = 0.1, min = 0, max }, [value, precision]) return (
-
- {name} - {/* Give tooltip different tip to avoiding hide bug */} - {tip}
} position='top' selector={`param-name-tooltip-${id}`}> - - - - +
+
+ {name} + {/* Give tooltip different tip to avoiding hide bug */} + {tip}
} position='top' selector={`param-name-tooltip-${id}`}> + + + + +
+ {inputType === 'inputTag' &&
{t('common.model.params.stop_sequencesPlaceholder')}
}
-
- { - onChange(id, value / times) - }} /> -
- { - let value = getFitPrecisionValue(isNaN(parseFloat(e.target.value)) ? min : parseFloat(e.target.value), precision) - if (value < min) - value = min + {inputType === 'inputTag' + ? onChange(id, newSequences)} + customizedConfirmKey='Tab' + /> + : ( + <> +
+ { + onChange(id, value / times) + }} /> +
+ { + let value = getFitPrecisionValue(isNaN(parseFloat(e.target.value)) ? min : parseFloat(e.target.value), precision) + if (value < min) + value = min - if (value > max) - value = max - onChange(id, value) - }} /> + if (value > max) + value = max + onChange(id, value) + }} /> + + ) + }
) 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 new file mode 100644 index 0000000000..972cbf62fa --- /dev/null +++ b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx @@ -0,0 +1,185 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import copy from 'copy-to-clipboard' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import { useBoolean } from 'ahooks' +import produce from 'immer' +import s from './style.module.css' +import MessageTypeSelector from './message-type-selector' +import ConfirmAddVar from './confirm-add-var' +import type { PromptRole, PromptVariable } from '@/models/debug' +import { HelpCircle, Trash03 } from '@/app/components/base/icons/src/vender/line/general' +import { Clipboard, ClipboardCheck } from '@/app/components/base/icons/src/vender/line/files' +import Tooltip from '@/app/components/base/tooltip' +import PromptEditor from '@/app/components/base/prompt-editor' +import ConfigContext from '@/context/debug-configuration' +import { getNewVar, getVars } from '@/utils/var' +import { AppType } from '@/types/app' + +type Props = { + type: PromptRole + isChatMode: boolean + value: string + onTypeChange: (value: PromptRole) => void + onChange: (value: string) => void + canDelete: boolean + onDelete: () => void + promptVariables: PromptVariable[] +} + +const AdvancedPromptInput: FC = ({ + type, + isChatMode, + value, + onChange, + onTypeChange, + canDelete, + onDelete, + promptVariables, +}) => { + const { t } = useTranslation() + + const { + mode, + hasSetBlockStatus, + modelConfig, + setModelConfig, + conversationHistoriesRole, + showHistoryModal, + dataSets, + showSelectDataSet, + } = useContext(ConfigContext) + const isChatApp = mode === AppType.chat + const [isCopied, setIsCopied] = React.useState(false) + + const promptVariablesObj = (() => { + const obj: Record = {} + promptVariables.forEach((item) => { + obj[item.key] = true + }) + return obj + })() + const [newPromptVariables, setNewPromptVariables] = React.useState(promptVariables) + const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) + const handlePromptChange = (newValue: string) => { + if (value === newValue) + return + onChange(newValue) + } + const handleBlur = () => { + const keys = getVars(value) + const newPromptVariables = keys.filter(key => !(key in promptVariablesObj)).map(key => getNewVar(key)) + if (newPromptVariables.length > 0) { + setNewPromptVariables(newPromptVariables) + showConfirmAddVar() + } + } + + const handleAutoAdd = (isAdd: boolean) => { + return () => { + if (isAdd) { + const newModelConfig = produce(modelConfig, (draft) => { + draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newPromptVariables] + }) + setModelConfig(newModelConfig) + } + hideConfirmAddVar() + } + } + + const editorHeight = isChatMode ? 'h-[200px]' : 'h-[508px]' + + return ( +
+
+
+ {isChatMode + ? ( + + ) + : ( +
+ +
{t('appDebug.pageTitle.line1')} +
+ + {t('appDebug.promptTip')} +
} + selector='config-prompt-tooltip'> + + +
)} +
+ {canDelete && ( + + )} + {!isCopied + ? ( + { + copy(value) + setIsCopied(true) + }} /> + ) + : ( + + )} + +
+
+
+ ({ + id: item.id, + name: item.name, + type: item.data_source_type, + })), + onAddContext: showSelectDataSet, + }} + variableBlock={{ + variables: modelConfig.configs.prompt_variables.map(item => ({ + name: item.name, + value: item.key, + })), + }} + historyBlock={{ + show: !isChatMode && isChatApp, + selectable: !hasSetBlockStatus.history, + history: { + user: conversationHistoriesRole?.user_prefix, + assistant: conversationHistoriesRole?.assistant_prefix, + }, + onEditRole: showHistoryModal, + }} + queryBlock={{ + show: !isChatMode && isChatApp, + selectable: !hasSetBlockStatus.query, + }} + onChange={handlePromptChange} + onBlur={handleBlur} + /> +
+
+
{value.length}
+
+
+ + {isShowConfirmAddVar && ( + v.name)} + onConfrim={handleAutoAdd(true)} + onCancel={handleAutoAdd(false)} + onHide={hideConfirmAddVar} + /> + )} +
+ ) +} +export default React.memo(AdvancedPromptInput) diff --git a/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx b/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx index 972f097be2..47a9ca11cb 100644 --- a/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx +++ b/web/app/components/app/configuration/config-prompt/confirm-add-var/index.tsx @@ -1,11 +1,11 @@ 'use client' -import React, { FC, useRef } from 'react' +import type { FC } from 'react' +import React, { useRef } from 'react' import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' -import { useClickAway } from 'ahooks' import VarHighlight from '../../base/var-highlight' +import Button from '@/app/components/base/button' -export interface IConfirmAddVarProps { +export type IConfirmAddVarProps = { varNameArr: string[] onConfrim: () => void onCancel: () => void @@ -28,19 +28,20 @@ const ConfirmAddVar: FC = ({ }) => { const { t } = useTranslation() const mainContentRef = useRef(null) - useClickAway(() => { - onHide() - }, mainContentRef) + // new prompt editor blur trigger click... + // useClickAway(() => { + // onHide() + // }, mainContentRef) return (
@@ -48,13 +49,13 @@ const ConfirmAddVar: FC = ({ className='shrink-0 flex items-center justify-center h-10 w-10 rounded-xl border border-gray-100' style={{ backgroundColor: 'rgba(255, 255, 255, 0.9)', - boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)' + boxShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)', }} >{VarIcon}
{t('appDebug.autoAddVar')}
- {varNameArr.map((name) => ( + {varNameArr.map(name => ( ))}
diff --git a/web/app/components/app/configuration/config-prompt/conversation-histroy/edit-modal.tsx b/web/app/components/app/configuration/config-prompt/conversation-histroy/edit-modal.tsx new file mode 100644 index 0000000000..aad3d4e930 --- /dev/null +++ b/web/app/components/app/configuration/config-prompt/conversation-histroy/edit-modal.tsx @@ -0,0 +1,59 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import Modal from '@/app/components/base/modal' +import type { ConversationHistoriesRole } from '@/models/debug' +import Button from '@/app/components/base/button' +type Props = { + isShow: boolean + saveLoading: boolean + data: ConversationHistoriesRole + onClose: () => void + onSave: (data: any) => void +} + +const EditModal: FC = ({ + isShow, + saveLoading, + data, + onClose, + onSave, +}) => { + const { t } = useTranslation() + const [tempData, setTempData] = useState(data) + return ( + +
{t('appDebug.feature.conversationHistory.editModal.userPrefix')}
+ setTempData({ + ...tempData, + user_prefix: e.target.value, + })} + /> + +
{t('appDebug.feature.conversationHistory.editModal.assistantPrefix')}
+ setTempData({ + ...tempData, + assistant_prefix: e.target.value, + })} + placeholder={t('common.chat.conversationNamePlaceholder') || ''} + /> + +
+ + +
+
+ ) +} + +export default React.memo(EditModal) diff --git a/web/app/components/app/configuration/config-prompt/conversation-histroy/history-panel.tsx b/web/app/components/app/configuration/config-prompt/conversation-histroy/history-panel.tsx new file mode 100644 index 0000000000..c5c54db73a --- /dev/null +++ b/web/app/components/app/configuration/config-prompt/conversation-histroy/history-panel.tsx @@ -0,0 +1,50 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import OperationBtn from '@/app/components/app/configuration/base/operation-btn' +import Panel from '@/app/components/app/configuration/base/feature-panel' +import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general' + +type Props = { + showWarning: boolean + onShowEditModal: () => void +} + +const HistoryPanel: FC = ({ + showWarning, + onShowEditModal, +}) => { + const { t } = useTranslation() + + return ( + +
{t('appDebug.feature.conversationHistory.title')}
+
+ } + headerIcon={ +
+ +
} + headerRight={ +
+
{t('appDebug.feature.conversationHistory.description')}
+
+ +
+ } + noBodySpacing + > + {showWarning && ( +
+ {/*
{t('appDebug.feature.conversationHistory.tip')} {t('appDebug.feature.conversationHistory.learnMore')}
*/} +
{t('appDebug.feature.conversationHistory.tip')}
+
+ )} + + ) +} +export default React.memo(HistoryPanel) diff --git a/web/app/components/app/configuration/config-prompt/index.tsx b/web/app/components/app/configuration/config-prompt/index.tsx index 4bc5ca8ce8..cfcd1ec05b 100644 --- a/web/app/components/app/configuration/config-prompt/index.tsx +++ b/web/app/components/app/configuration/config-prompt/index.tsx @@ -1,23 +1,23 @@ 'use client' import type { FC } from 'react' import React from 'react' +import { useContext } from 'use-context-selector' +import produce from 'immer' import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' -import cn from 'classnames' -import ConfirmAddVar from './confirm-add-var' -import s from './style.module.css' -import BlockInput from '@/app/components/base/block-input' -import type { PromptVariable } from '@/models/debug' -import Tooltip from '@/app/components/base/tooltip' -import { AppType } from '@/types/app' -import { getNewVar } from '@/utils/var' - +import SimplePromptInput from './simple-prompt-input' +import AdvancedMessageInput from '@/app/components/app/configuration/config-prompt/advanced-prompt-input' +import { PromptRole } from '@/models/debug' +import type { PromptItem, PromptVariable } from '@/models/debug' +import { type AppType, ModelModeType } from '@/types/app' +import ConfigContext from '@/context/debug-configuration' +import { Plus } from '@/app/components/base/icons/src/vender/line/general' +import { MAX_PROMPT_MESSAGE_LENGTH } from '@/config' export type IPromptProps = { mode: AppType promptTemplate: string promptVariables: PromptVariable[] readonly?: boolean - onChange?: (promp: string, promptVariables: PromptVariable[]) => void + onChange?: (prompt: string, promptVariables: PromptVariable[]) => void } const Prompt: FC = ({ @@ -28,73 +28,114 @@ const Prompt: FC = ({ onChange, }) => { const { t } = useTranslation() - const promptVariablesObj = (() => { - const obj: Record = {} - promptVariables.forEach((item) => { - obj[item.key] = true + + const { + isAdvancedMode, + currentAdvancedPrompt, + setCurrentAdvancedPrompt, + modelModeType, + } = useContext(ConfigContext) + + const handleMessageTypeChange = (index: number, role: PromptRole) => { + const newPrompt = produce(currentAdvancedPrompt as PromptItem[], (draft) => { + draft[index].role = role }) - return obj - })() - - const [newPromptVariables, setNewPromptVariables] = React.useState(promptVariables) - const [newTemplates, setNewTemplates] = React.useState('') - const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) - - const handleChange = (newTemplates: string, keys: string[]) => { - // const hasRemovedKeysInput = promptVariables.filter(input => keys.includes(input.key)) - const newPromptVariables = keys.filter(key => !(key in promptVariablesObj)).map(key => getNewVar(key)) - if (newPromptVariables.length > 0) { - setNewPromptVariables(newPromptVariables) - setNewTemplates(newTemplates) - showConfirmAddVar() - return - } - onChange?.(newTemplates, []) + setCurrentAdvancedPrompt(newPrompt) } - const handleAutoAdd = (isAdd: boolean) => { - return () => { - onChange?.(newTemplates, isAdd ? newPromptVariables : []) - hideConfirmAddVar() + const handleValueChange = (value: string, index?: number) => { + if (modelModeType === ModelModeType.chat) { + const newPrompt = produce(currentAdvancedPrompt as PromptItem[], (draft) => { + draft[index as number].text = value + }) + setCurrentAdvancedPrompt(newPrompt, true) } + else { + const prompt = currentAdvancedPrompt as PromptItem + setCurrentAdvancedPrompt({ + ...prompt, + text: value, + }, true) + } + } + + const handleAddMessage = () => { + const currentAdvancedPromptList = currentAdvancedPrompt as PromptItem[] + if (currentAdvancedPromptList.length === 0) { + setCurrentAdvancedPrompt([{ + role: PromptRole.system, + text: '', + }]) + return + } + const lastMessageType = currentAdvancedPromptList[currentAdvancedPromptList.length - 1].role + const appendMessage = { + role: lastMessageType === PromptRole.user ? PromptRole.assistant : PromptRole.user, + text: '', + } + setCurrentAdvancedPrompt([...currentAdvancedPromptList, appendMessage]) + } + + const handlePromptDelete = (index: number) => { + const currentAdvancedPromptList = currentAdvancedPrompt as PromptItem[] + const newPrompt = produce(currentAdvancedPromptList, (draft) => { + draft.splice(index, 1) + }) + setCurrentAdvancedPrompt(newPrompt) + } + + if (!isAdvancedMode) { + return ( + + ) } return ( -
-
- - - -
{mode === AppType.chat ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}
- {!readonly && ( - - {t('appDebug.promptTip')} -
} - selector='config-prompt-tooltip'> - - - - - )} - +
+
+ {modelModeType === ModelModeType.chat + ? ( + (currentAdvancedPrompt as PromptItem[]).map((item, index) => ( + handleMessageTypeChange(index, type)} + canDelete={(currentAdvancedPrompt as PromptItem[]).length > 1} + onDelete={() => handlePromptDelete(index)} + onChange={value => handleValueChange(value, index)} + promptVariables={promptVariables} + /> + )) + ) + : ( + handleMessageTypeChange(0, type)} + canDelete={false} + onDelete={() => handlePromptDelete(0)} + onChange={value => handleValueChange(value)} + promptVariables={promptVariables} + /> + ) + }
- - { - handleChange(value, vars) - }} - /> - - {isShowConfirmAddVar && ( - v.name)} - onConfrim={handleAutoAdd(true)} - onCancel={handleAutoAdd(false)} - onHide={hideConfirmAddVar} - /> + {(modelModeType === ModelModeType.chat && (currentAdvancedPrompt as PromptItem[]).length < MAX_PROMPT_MESSAGE_LENGTH) && ( +
+ +
{t('appDebug.promptMode.operation.addMessage')}
+
)}
) diff --git a/web/app/components/app/configuration/config-prompt/message-type-selector.tsx b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx new file mode 100644 index 0000000000..8e8e08cd9a --- /dev/null +++ b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx @@ -0,0 +1,50 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useBoolean, useClickAway } from 'ahooks' +import cn from 'classnames' +import { PromptRole } from '@/models/debug' +import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' +type Props = { + value: PromptRole + onChange: (value: PromptRole) => void +} + +const allTypes = [PromptRole.system, PromptRole.user, PromptRole.assistant] +const MessageTypeSelector: FC = ({ + value, + onChange, +}) => { + const [showOption, { setFalse: setHide, toggle: toggleShow }] = useBoolean(false) + const ref = React.useRef(null) + useClickAway(() => { + setHide() + }, ref) + return ( +
+
+
{value}
+ +
+ {showOption && ( +
+ {allTypes.map(type => ( +
{ + setHide() + onChange(type) + }} + className='flex items-center h-9 min-w-[44px] px-3 rounded-lg cursor-pointer text-sm font-medium text-gray-700 uppercase hover:bg-gray-50' + >{type}
+ )) + } +
+ ) + } +
+ ) +} +export default React.memo(MessageTypeSelector) 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 new file mode 100644 index 0000000000..10a7377502 --- /dev/null +++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx @@ -0,0 +1,174 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useBoolean } from 'ahooks' +import cn from 'classnames' +import produce from 'immer' +import { useContext } from 'use-context-selector' +import ConfirmAddVar from './confirm-add-var' +import s from './style.module.css' +import type { PromptVariable } from '@/models/debug' +import Tooltip from '@/app/components/base/tooltip' +import { AppType } from '@/types/app' +import { getNewVar, getVars } from '@/utils/var' +import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general' +import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn' +import type { AutomaticRes } from '@/service/debug' +import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res' +import PromptEditor from '@/app/components/base/prompt-editor' +import ConfigContext from '@/context/debug-configuration' + +export type ISimplePromptInput = { + mode: AppType + promptTemplate: string + promptVariables: PromptVariable[] + readonly?: boolean + onChange?: (promp: string, promptVariables: PromptVariable[]) => void +} + +const Prompt: FC = ({ + mode, + promptTemplate, + promptVariables, + readonly = false, + onChange, +}) => { + const { t } = useTranslation() + const { + modelConfig, + dataSets, + setModelConfig, + setPrevPromptConfig, + setIntroduction, + hasSetBlockStatus, + showSelectDataSet, + } = useContext(ConfigContext) + const promptVariablesObj = (() => { + const obj: Record = {} + promptVariables.forEach((item) => { + obj[item.key] = true + }) + return obj + })() + + const [newPromptVariables, setNewPromptVariables] = React.useState(promptVariables) + const [newTemplates, setNewTemplates] = React.useState('') + const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false) + + const handleChange = (newTemplates: string, keys: string[]) => { + const newPromptVariables = keys.filter(key => !(key in promptVariablesObj)).map(key => getNewVar(key)) + if (newPromptVariables.length > 0) { + setNewPromptVariables(newPromptVariables) + setNewTemplates(newTemplates) + showConfirmAddVar() + return + } + onChange?.(newTemplates, []) + } + + const handleAutoAdd = (isAdd: boolean) => { + return () => { + onChange?.(newTemplates, isAdd ? newPromptVariables : []) + hideConfirmAddVar() + } + } + + const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) + const handleAutomaticRes = (res: AutomaticRes) => { + 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 })) + }) + setModelConfig(newModelConfig) + setPrevPromptConfig(modelConfig.configs) + if (mode === AppType.chat) + setIntroduction(res.opening_statement) + showAutomaticFalse() + } + + return ( +
+
+
+
+ + + +
{mode === AppType.chat ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}
+ {!readonly && ( + + {t('appDebug.promptTip')} +
} + selector='config-prompt-tooltip'> + + + )} +
+ +
+
+ ({ + id: item.id, + name: item.name, + type: item.data_source_type, + })), + onAddContext: showSelectDataSet, + }} + variableBlock={{ + variables: modelConfig.configs.prompt_variables.map(item => ({ + name: item.name, + value: item.key, + })), + }} + historyBlock={{ + show: false, + selectable: false, + history: { + user: '', + assistant: '', + }, + onEditRole: () => {}, + }} + queryBlock={{ + show: false, + selectable: !hasSetBlockStatus.query, + }} + onChange={(value) => { + handleChange?.(value, []) + }} + onBlur={() => { + handleChange(promptTemplate, getVars(promptTemplate)) + }} + /> +
+
+ + {isShowConfirmAddVar && ( + v.name)} + onConfrim={handleAutoAdd(true)} + onCancel={handleAutoAdd(false)} + onHide={hideConfirmAddVar} + /> + )} + + {showAutomatic && ( + + )} +
+ ) +} + +export default React.memo(Prompt) diff --git a/web/app/components/app/configuration/config-prompt/style.module.css b/web/app/components/app/configuration/config-prompt/style.module.css index 40b3f3515f..2c9f0022ae 100644 --- a/web/app/components/app/configuration/config-prompt/style.module.css +++ b/web/app/components/app/configuration/config-prompt/style.module.css @@ -12,4 +12,12 @@ border-radius: 12px; padding: 2px; box-sizing: border-box; +} + +.optionWrap { + display: none; +} + +.boxHeader:hover .optionWrap { + display: flex; } \ No newline at end of file diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index 803381560e..37f066c3da 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -73,7 +73,6 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar delete newItem.max_length delete newItem.options } - console.log(newItem) return newItem } @@ -175,8 +174,7 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar {t('appDebug.variableTip')}
} selector='config-var-tooltip'> - - + )}
diff --git a/web/app/components/app/configuration/config/automatic/automatic-btn.tsx b/web/app/components/app/configuration/config/automatic/automatic-btn.tsx index d5ba091e60..d492ad6839 100644 --- a/web/app/components/app/configuration/config/automatic/automatic-btn.tsx +++ b/web/app/components/app/configuration/config/automatic/automatic-btn.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import Button from '@/app/components/base/button' export type IAutomaticBtnProps = { onClick: () => void @@ -22,12 +21,12 @@ const AutomaticBtn: FC = ({ const { t } = useTranslation() return ( - + {t('appDebug.operation.automatic')} +
) } export default React.memo(AutomaticBtn) diff --git a/web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx b/web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx index 90c9fd9153..eb3edc7593 100644 --- a/web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx +++ b/web/app/components/app/configuration/config/feature/add-feature-btn/index.tsx @@ -1,30 +1,39 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { PlusIcon } from '@heroicons/react/24/solid' -export interface IAddFeatureBtnProps { +export type IAddFeatureBtnProps = { + toBottomHeight: number onClick: () => void } +const ITEM_HEIGHT = 48 + const AddFeatureBtn: FC = ({ - onClick + toBottomHeight, + onClick, }) => { const { t } = useTranslation() return (
- -
{t('appDebug.operation.addFeature')}
+
+ +
{t('appDebug.operation.addFeature')}
+
) } diff --git a/web/app/components/app/configuration/config/index.tsx b/web/app/components/app/configuration/config/index.tsx index 3777feeb22..95919bd729 100644 --- a/web/app/components/app/configuration/config/index.tsx +++ b/web/app/components/app/configuration/config/index.tsx @@ -1,29 +1,32 @@ 'use client' import type { FC } from 'react' -import React from 'react' +import React, { useRef } from 'react' import { useContext } from 'use-context-selector' import produce from 'immer' -import { useBoolean } from 'ahooks' +import { useBoolean, useScroll } from 'ahooks' import DatasetConfig from '../dataset-config' import ChatGroup from '../features/chat-group' import ExperienceEnchanceGroup from '../features/experience-enchance-group' import Toolbox from '../toolbox' +import HistoryPanel from '../config-prompt/conversation-histroy/history-panel' import AddFeatureBtn from './feature/add-feature-btn' -import AutomaticBtn from './automatic/automatic-btn' -import type { AutomaticRes } from './automatic/get-automatic-res' -import GetAutomaticResModal from './automatic/get-automatic-res' import ChooseFeature from './feature/choose-feature' import useFeature from './feature/use-feature' +import AdvancedModeWaring from '@/app/components/app/configuration/prompt-mode/advanced-mode-waring' import ConfigContext from '@/context/debug-configuration' import ConfigPrompt from '@/app/components/app/configuration/config-prompt' import ConfigVar from '@/app/components/app/configuration/config-var' import type { PromptVariable } from '@/models/debug' -import { AppType } from '@/types/app' +import { AppType, ModelModeType } from '@/types/app' import { useProviderContext } from '@/context/provider-context' - const Config: FC = () => { const { mode, + isAdvancedMode, + modelModeType, + canReturnToSimpleMode, + hasSetBlockStatus, + showHistoryModal, introduction, setIntroduction, modelConfig, @@ -44,6 +47,7 @@ const Config: FC = () => { const promptTemplate = modelConfig.configs.prompt_template const promptVariables = modelConfig.configs.prompt_variables + // simple mode const handlePromptChange = (newTemplate: string, newVariables: PromptVariable[]) => { const newModelConfig = produce(modelConfig, (draft) => { draft.configs.prompt_template = newTemplate @@ -101,26 +105,29 @@ const Config: FC = () => { const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && !!speech2textDefaultModel) || featureConfig.citation) const hasToolbox = false - const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) - const handleAutomaticRes = (res: AutomaticRes) => { - 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 })) - }) - setModelConfig(newModelConfig) - setPrevPromptConfig(modelConfig.configs) - if (mode === AppType.chat) - setIntroduction(res.opening_statement) - showAutomaticFalse() - } + const wrapRef = useRef(null) + const wrapScroll = useScroll(wrapRef) + const toBottomHeight = (() => { + if (!wrapRef.current) + return 999 + const elem = wrapRef.current + const { clientHeight } = elem + const value = (wrapScroll?.top || 0) + clientHeight + return value + })() + return ( <> -
-
- - -
- +
+ + { + (isAdvancedMode && canReturnToSimpleMode) && ( + + ) + } {showChooseFeature && ( { showSpeechToTextItem={!!speech2textDefaultModel} /> )} - {showAutomatic && ( - - )} + {/* Template */} { {/* Dataset */} + {/* Chat History */} + {isAdvancedMode && isChatApp && modelModeType === ModelModeType.completion && ( + + )} + {/* ChatConifig */} { hasChatConfig && ( diff --git a/web/app/components/app/configuration/dataset-config/index.tsx b/web/app/components/app/configuration/dataset-config/index.tsx index 05f7a35899..036533bef3 100644 --- a/web/app/components/app/configuration/dataset-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/index.tsx @@ -3,16 +3,13 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import { useBoolean } from 'ahooks' -import { isEqual } from 'lodash-es' import produce from 'immer' import FeaturePanel from '../base/feature-panel' import OperationBtn from '../base/operation-btn' import CardItem from './card-item' -import SelectDataSet from './select-dataset' +import ParamsConfig from './params-config' import ContextVar from './context-var' import ConfigContext from '@/context/debug-configuration' -import type { DataSet } from '@/models/datasets' import { AppType } from '@/types/app' const Icon = ( @@ -31,35 +28,12 @@ const DatasetConfig: FC = () => { setFormattingChanged, modelConfig, setModelConfig, + showSelectDataSet, } = useContext(ConfigContext) const selectedIds = dataSet.map(item => item.id) const hasData = dataSet.length > 0 - const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) - const handleSelect = (data: DataSet[]) => { - if (isEqual(data.map(item => item.id), dataSet.map(item => item.id))) { - hideSelectDataSet() - return - } - setFormattingChanged(true) - if (data.find(item => !item.name)) { // has not loaded selected dataset - const newSelected = produce(data, (draft) => { - data.forEach((item, index) => { - if (!item.name) { // not fetched database - const newItem = dataSet.find(i => i.id === item.id) - if (newItem) - draft[index] = newItem - } - }) - }) - setDataSet(newSelected) - } - else { - setDataSet(data) - } - hideSelectDataSet() - } const onRemove = (id: string) => { setDataSet(dataSet.filter(item => item.id !== id)) setFormattingChanged(true) @@ -89,7 +63,12 @@ const DatasetConfig: FC = () => { className='mt-3' headerIcon={Icon} title={t('appDebug.feature.dataSet.title')} - headerRight={} + headerRight={ +
+ + +
+ } hasHeaderBottomBorder={!hasData} noBodySpacing > @@ -120,14 +99,6 @@ const DatasetConfig: FC = () => { /> )} - {isShowSelectDataSet && ( - - )} ) } diff --git a/web/app/components/app/configuration/dataset-config/params-config/index.tsx b/web/app/components/app/configuration/dataset-config/params-config/index.tsx new file mode 100644 index 0000000000..101bf15a10 --- /dev/null +++ b/web/app/components/app/configuration/dataset-config/params-config/index.tsx @@ -0,0 +1,181 @@ +'use client' +import type { FC } from 'react' +import { memo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import cn from 'classnames' +import { HelpCircle, Settings04 } from '@/app/components/base/icons/src/vender/line/general' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Tooltip from '@/app/components/base/tooltip-plus' +import Slider from '@/app/components/base/slider' +import Switch from '@/app/components/base/switch' +import ConfigContext from '@/context/debug-configuration' + +// TODO +const PARAMS_KEY = [ + 'top_k', + 'score_threshold', +] +const PARAMS = { + top_k: { + default: 2, + step: 1, + min: 1, + max: 10, + }, + score_threshold: { + default: 0.7, + step: 0.01, + min: 0, + max: 1, + }, +} as any + +export type IParamItemProps = { + id: string + name: string + tip: string + value: number + enable: boolean + step?: number + min?: number + max: number + onChange: (key: string, value: number) => void + onSwitchChange: (key: string, enable: boolean) => void +} + +const ParamItem: FC = ({ id, name, tip, step = 0.1, min = 0, max, value, enable, onChange, onSwitchChange }) => { + return ( +
+
+
+ {id === 'score_threshold' && ( + { + onSwitchChange(id, val) + }} + /> + )} + {name} + {tip}
}> + + +
+
+
+
+
+
+ onChange(id, value / (max < 5 ? 100 : 1))} + /> +
+
+
+ { + const value = parseFloat(e.target.value) + if (value < min || value > max) + return + + onChange(id, value) + }} /> +
+
+
+ ) +} + +const ParamsConfig: FC = () => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const { + datasetConfigs, + setDatasetConfigs, + } = useContext(ConfigContext) + + const handleParamChange = (key: string, value: number) => { + let notOutRangeValue = parseFloat(value.toFixed(2)) + notOutRangeValue = Math.max(PARAMS[key].min, notOutRangeValue) + notOutRangeValue = Math.min(PARAMS[key].max, notOutRangeValue) + if (key === 'top_k') { + setDatasetConfigs({ + ...datasetConfigs, + top_k: notOutRangeValue, + }) + } + else if (key === 'score_threshold') { + setDatasetConfigs({ + ...datasetConfigs, + [key]: { + enable: datasetConfigs.score_threshold.enable, + value: notOutRangeValue, + }, + }) + } + } + + const handleSwitch = (key: string, enable: boolean) => { + if (key === 'top_k') + return + + setDatasetConfigs({ + ...datasetConfigs, + [key]: { + enable, + value: (datasetConfigs as any)[key].value, + }, + }) + } + + return ( + + setOpen(v => !v)}> +
+ +
+ {t('appDebug.datasetConfig.params')} +
+
+
+ +
+ {PARAMS_KEY.map((key: string) => { + const currentValue = key === 'top_k' ? datasetConfigs[key] : (datasetConfigs as any)[key].value + const currentEnableState = key === 'top_k' ? true : (datasetConfigs as any)[key].enable + return ( + + ) + })} +
+
+
+ ) +} +export default memo(ParamsConfig) diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx index 1bcc742317..0bba98b070 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx @@ -94,6 +94,7 @@ const SelectDataSet: FC = ({ isShow={isShow} onClose={onClose} className='w-[400px]' + wrapperClassName='!z-[101]' title={t('appDebug.feature.dataSet.selectTitle')} > {!loaded && ( diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index e6d3d9c9a5..1dd3b49bf1 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -11,7 +11,7 @@ import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api' import FormattingChanged from '../base/warning-mask/formatting-changed' import GroupName from '../base/group-name' import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset' -import { AppType } from '@/types/app' +import { AppType, ModelModeType } from '@/types/app' import PromptValuePanel, { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel' import type { IChatItem } from '@/app/components/app/chat/type' import Chat from '@/app/components/app/chat' @@ -37,6 +37,12 @@ const Debug: FC = ({ const { appId, mode, + modelModeType, + hasSetBlockStatus, + isAdvancedMode, + promptMode, + chatPromptConfig, + completionPromptConfig, introduction, suggestedQuestionsAfterAnswerConfig, speechToTextConfig, @@ -53,6 +59,7 @@ const Debug: FC = ({ modelConfig, completionParams, hasSetContextVar, + datasetConfigs, } = useContext(ConfigContext) const { speech2textDefaultModel } = useProviderContext() const [chatList, setChatList, getChatList] = useGetState([]) @@ -120,6 +127,18 @@ const Debug: FC = ({ } const checkCanSend = () => { + if (isAdvancedMode && mode === AppType.chat) { + if (modelModeType === ModelModeType.completion) { + if (!hasSetBlockStatus.history) { + notify({ type: 'error', message: t('appDebug.otherError.historyNoBeEmpty'), duration: 3000 }) + return false + } + if (!hasSetBlockStatus.query) { + notify({ type: 'error', message: t('appDebug.otherError.queryNoBeEmpty'), duration: 3000 }) + return false + } + } + } let hasEmptyInput = '' const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required }) => { const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) @@ -155,11 +174,15 @@ const Debug: FC = ({ id, }, })) + const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key const postModelConfig: BackendModelConfig = { - pre_prompt: modelConfig.configs.prompt_template, + pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '', + prompt_type: promptMode, + chat_prompt_config: {}, + completion_prompt_config: {}, user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables), - dataset_query_variable: '', + dataset_query_variable: contextVar || '', opening_statement: introduction, more_like_this: { enabled: false, @@ -174,8 +197,15 @@ const Debug: FC = ({ model: { provider: modelConfig.provider, name: modelConfig.model_id, + mode: modelConfig.mode, completion_params: completionParams as any, }, + dataset_configs: datasetConfigs, + } + + if (isAdvancedMode) { + postModelConfig.chat_prompt_config = chatPromptConfig + postModelConfig.completion_prompt_config = completionPromptConfig } const data = { @@ -254,6 +284,11 @@ const Debug: FC = ({ setChatList(produce(getChatList(), (draft) => { const index = draft.findIndex(item => item.id === responseItem.id) if (index !== -1) { + const requestion = draft[index - 1] + draft[index - 1] = { + ...requestion, + log: newResponseItem.message, + } draft[index] = { ...draft[index], more: { @@ -326,7 +361,10 @@ const Debug: FC = ({ const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key const postModelConfig: BackendModelConfig = { - pre_prompt: modelConfig.configs.prompt_template, + pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '', + prompt_type: promptMode, + chat_prompt_config: {}, + completion_prompt_config: {}, user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables), dataset_query_variable: contextVar || '', opening_statement: introduction, @@ -341,8 +379,15 @@ const Debug: FC = ({ model: { provider: modelConfig.provider, name: modelConfig.model_id, + mode: modelConfig.mode, completion_params: completionParams as any, }, + dataset_configs: datasetConfigs, + } + + if (isAdvancedMode) { + postModelConfig.chat_prompt_config = chatPromptConfig + postModelConfig.completion_prompt_config = completionPromptConfig } const data = { @@ -413,6 +458,7 @@ const Debug: FC = ({ isShowSpeechToText={speechToTextConfig.enabled && !!speech2textDefaultModel} isShowCitation={citationConfig.enabled} isShowCitationHitInfo + isShowPromptLog />
@@ -427,8 +473,11 @@ const Debug: FC = ({ className="mt-2" content={completionRes} isLoading={!completionRes && isResponsing} + isResponsing={isResponsing} isInstalledApp={false} messageId={messageId} + isError={false} + onRetry={() => { }} /> )}
diff --git a/web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx b/web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx index 0c5b8cf4db..533d249487 100644 --- a/web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx +++ b/web/app/components/app/configuration/features/chat-group/suggested-questions-after-answer/index.tsx @@ -1,9 +1,11 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import Panel from '@/app/components/app/configuration/base/feature-panel' import SuggestedQuestionsAfterAnswerIcon from '@/app/components/app/configuration/base/icons/suggested-questions-after-answer-icon' import Tooltip from '@/app/components/base/tooltip' +import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general' const SuggestedQuestionsAfterAnswer: FC = () => { const { t } = useTranslation() @@ -16,9 +18,7 @@ const SuggestedQuestionsAfterAnswer: FC = () => { {t('appDebug.feature.suggestedQuestionsAfterAnswer.description')}
} selector='suggestion-question-tooltip'> - - - +
} diff --git a/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts b/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts new file mode 100644 index 0000000000..ea7a49fecd --- /dev/null +++ b/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts @@ -0,0 +1,176 @@ +import { useState } from 'react' +import { clone } from 'lodash-es' +import produce from 'immer' +import type { ChatPromptConfig, CompletionPromptConfig, ConversationHistoriesRole, PromptItem } from '@/models/debug' +import { PromptMode } from '@/models/debug' +import { AppType, ModelModeType } from '@/types/app' +import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' +import { PRE_PROMPT_PLACEHOLDER_TEXT, checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants' +import { fetchPromptTemplate } from '@/service/debug' + +type Param = { + appMode: string + modelModeType: ModelModeType + modelName: string + promptMode: PromptMode + prePrompt: string + onUserChangedPrompt: () => void + hasSetDataSet: boolean +} + +const useAdvancedPromptConfig = ({ + appMode, + modelModeType, + modelName, + promptMode, + prePrompt, + onUserChangedPrompt, + hasSetDataSet, +}: Param) => { + const isAdvancedPrompt = promptMode === PromptMode.advanced + const [chatPromptConfig, setChatPromptConfig] = useState(clone(DEFAULT_CHAT_PROMPT_CONFIG)) + const [completionPromptConfig, setCompletionPromptConfig] = useState(clone(DEFAULT_COMPLETION_PROMPT_CONFIG)) + + const currentAdvancedPrompt = (() => { + if (!isAdvancedPrompt) + return [] + + return (modelModeType === ModelModeType.chat) ? chatPromptConfig.prompt : completionPromptConfig.prompt + })() + + const setCurrentAdvancedPrompt = (prompt: PromptItem | PromptItem[], isUserChanged?: boolean) => { + if (!isAdvancedPrompt) + return + + if (modelModeType === ModelModeType.chat) { + setChatPromptConfig({ + ...chatPromptConfig, + prompt: prompt as PromptItem[], + }) + } + else { + setCompletionPromptConfig({ + ...completionPromptConfig, + prompt: prompt as PromptItem, + }) + } + if (isUserChanged) + onUserChangedPrompt() + } + + const setConversationHistoriesRole = (conversationHistoriesRole: ConversationHistoriesRole) => { + setCompletionPromptConfig({ + ...completionPromptConfig, + conversation_histories_role: conversationHistoriesRole, + }) + } + + const hasSetBlockStatus = (() => { + if (!isAdvancedPrompt) { + return { + context: checkHasContextBlock(prePrompt), + history: false, + query: false, + } + } + if (modelModeType === ModelModeType.chat) { + return { + context: !!chatPromptConfig.prompt.find(p => checkHasContextBlock(p.text)), + history: false, + query: !!chatPromptConfig.prompt.find(p => checkHasQueryBlock(p.text)), + } + } + else { + const prompt = completionPromptConfig.prompt.text + return { + context: checkHasContextBlock(prompt), + history: checkHasHistoryBlock(prompt), + query: checkHasQueryBlock(prompt), + } + } + })() + + /* prompt: simple to advanced process, or chat model to completion model + * 1. migrate prompt + * 2. change promptMode to advanced + */ + const migrateToDefaultPrompt = async (isMigrateToCompetition?: boolean, toModelModeType?: ModelModeType) => { + const mode = modelModeType + const toReplacePrePrompt = prePrompt || '' + if (!isAdvancedPrompt) { + const { chat_prompt_config, completion_prompt_config } = await fetchPromptTemplate({ + appMode, + mode, + modelName, + hasSetDataSet, + }) + if (modelModeType === ModelModeType.chat) { + const newPromptConfig = produce(chat_prompt_config, (draft) => { + draft.prompt = draft.prompt.map((p) => { + return { + ...p, + text: p.text.replace(PRE_PROMPT_PLACEHOLDER_TEXT, toReplacePrePrompt), + } + }) + }) + setChatPromptConfig(newPromptConfig) + } + + else { + const newPromptConfig = produce(completion_prompt_config, (draft) => { + draft.prompt.text = draft.prompt.text.replace(PRE_PROMPT_PLACEHOLDER_TEXT, toReplacePrePrompt) + }) + setCompletionPromptConfig(newPromptConfig) + } + return + } + + if (isMigrateToCompetition) { + const { completion_prompt_config, chat_prompt_config } = await fetchPromptTemplate({ + appMode, + mode: toModelModeType as ModelModeType, + modelName, + hasSetDataSet, + }) + + if (toModelModeType === ModelModeType.completion) { + const newPromptConfig = produce(completion_prompt_config, (draft) => { + if (!completionPromptConfig.prompt.text) + draft.prompt.text = draft.prompt.text.replace(PRE_PROMPT_PLACEHOLDER_TEXT, toReplacePrePrompt) + + else + draft.prompt.text = completionPromptConfig.prompt.text.replace(PRE_PROMPT_PLACEHOLDER_TEXT, toReplacePrePrompt) + + if (appMode === AppType.chat && completionPromptConfig.conversation_histories_role.assistant_prefix && completionPromptConfig.conversation_histories_role.user_prefix) + draft.conversation_histories_role = completionPromptConfig.conversation_histories_role + }) + setCompletionPromptConfig(newPromptConfig) + } + else { + const newPromptConfig = produce(chat_prompt_config, (draft) => { + draft.prompt = draft.prompt.map((p) => { + return { + ...p, + text: p.text.replace(PRE_PROMPT_PLACEHOLDER_TEXT, toReplacePrePrompt), + } + }) + }) + setChatPromptConfig(newPromptConfig) + } + } + } + + return { + chatPromptConfig, + setChatPromptConfig, + completionPromptConfig, + setCompletionPromptConfig, + currentAdvancedPrompt, + setCurrentAdvancedPrompt, + hasSetBlockStatus, + setConversationHistoriesRole, + migrateToDefaultPrompt, + } +} + +export default useAdvancedPromptConfig diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index e48bbb4d81..94f7b758e6 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -7,9 +7,13 @@ import { usePathname } from 'next/navigation' import produce from 'immer' import { useBoolean } from 'ahooks' import cn from 'classnames' +import { clone, isEqual } from 'lodash-es' import Button from '../../base/button' import Loading from '../../base/loading' -import type { CompletionParams, Inputs, ModelConfig, MoreLikeThisConfig, PromptConfig, PromptVariable } from '@/models/debug' +import s from './style.module.css' +import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config' +import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal' +import type { CompletionParams, DatasetConfigs, Inputs, ModelConfig, MoreLikeThisConfig, PromptConfig, PromptVariable } from '@/models/debug' import type { DataSet } from '@/models/datasets' import type { ModelConfig as BackendModelConfig } from '@/types/app' import ConfigContext from '@/context/debug-configuration' @@ -24,7 +28,11 @@ import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from import { fetchDatasets } from '@/service/datasets' import AccountSetting from '@/app/components/header/account-setting' import { useProviderContext } from '@/context/provider-context' -import { AppType } from '@/types/app' +import { AppType, ModelModeType } from '@/types/app' +import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows' +import { PromptMode } from '@/models/debug' +import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' +import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset' type PublichConfig = { modelConfig: ModelConfig @@ -44,6 +52,7 @@ const Configuration: FC = () => { const [publishedConfig, setPublishedConfig] = useState(null) const [conversationId, setConversationId] = useState('') + const [introduction, setIntroduction] = useState('') const [controlClearChatMessage, setControlClearChatMessage] = useState(0) const [prevPromptConfig, setPrevPromptConfig] = useState({ @@ -75,6 +84,7 @@ const Configuration: FC = () => { const [modelConfig, doSetModelConfig] = useState({ provider: ProviderEnum.openai, model_id: 'gpt-3.5-turbo', + mode: ModelModeType.unset, configs: { prompt_template: '', prompt_variables: [] as PromptVariable[], @@ -87,21 +97,52 @@ const Configuration: FC = () => { dataSets: [], }) + const [datasetConfigs, setDatasetConfigs] = useState({ + top_k: 2, + score_threshold: { + enable: false, + value: 0.7, + }, + }) + const setModelConfig = (newModelConfig: ModelConfig) => { doSetModelConfig(newModelConfig) } - const setModelId = (modelId: string, provider: ProviderEnum) => { - const newModelConfig = produce(modelConfig, (draft: any) => { - draft.provider = provider - draft.model_id = modelId - }) - setModelConfig(newModelConfig) - } + const modelModeType = modelConfig.mode const [dataSets, setDataSets] = useState([]) const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key const hasSetContextVar = !!contextVar + const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) + const selectedIds = dataSets.map(item => item.id) + const handleSelect = (data: DataSet[]) => { + if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) { + hideSelectDataSet() + return + } + + setFormattingChanged(true) + if (data.find(item => !item.name)) { // has not loaded selected dataset + const newSelected = produce(data, (draft) => { + data.forEach((item, index) => { + if (!item.name) { // not fetched database + const newItem = dataSets.find(i => i.id === item.id) + if (newItem) + draft[index] = newItem + } + }) + }) + setDataSets(newSelected) + } + else { + setDataSets(data) + } + hideSelectDataSet() + } + + const [isShowHistoryModal, { setTrue: showHistoryModal, setFalse: hideHistoryModal }] = useBoolean(false) + const syncToPublishedConfig = (_publishedConfig: PublichConfig) => { const modelConfig = _publishedConfig.modelConfig setModelConfig(_publishedConfig.modelConfig) @@ -140,14 +181,101 @@ const Configuration: FC = () => { return quota_used === quota_limit }) + // Fill old app data missing model mode. + useEffect(() => { + if (hasFetchedDetail && !modelModeType) { + const mode = textGenerationModelList.find(({ model_name }) => model_name === modelConfig.model_id)?.model_mode + if (mode) { + const newModelConfig = produce(modelConfig, (draft) => { + draft.mode = mode + }) + setModelConfig(newModelConfig) + } + } + }, [textGenerationModelList, hasFetchedDetail]) + const hasSetAPIKEY = hasSetCustomAPIKEY || !isTrailFinished const [isShowSetAPIKey, { setTrue: showSetAPIKey, setFalse: hideSetAPIkey }] = useBoolean() + const [promptMode, doSetPromptMode] = useState(PromptMode.advanced) + const isAdvancedMode = promptMode === PromptMode.advanced + const [canReturnToSimpleMode, setCanReturnToSimpleMode] = useState(true) + const setPromptMode = async (mode: PromptMode) => { + if (mode === PromptMode.advanced) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + await migrateToDefaultPrompt() + setCanReturnToSimpleMode(true) + } + + doSetPromptMode(mode) + } + + const { + chatPromptConfig, + setChatPromptConfig, + completionPromptConfig, + setCompletionPromptConfig, + currentAdvancedPrompt, + setCurrentAdvancedPrompt, + hasSetBlockStatus, + setConversationHistoriesRole, + migrateToDefaultPrompt, + } = useAdvancedPromptConfig({ + appMode: mode, + modelName: modelConfig.model_id, + promptMode, + modelModeType, + prePrompt: modelConfig.configs.prompt_template, + hasSetDataSet: dataSets.length > 0, + onUserChangedPrompt: () => { + setCanReturnToSimpleMode(false) + }, + }) + + const setModel = async ({ + id: modelId, + provider, + mode: modeMode, + }: { id: string; provider: ProviderEnum; mode: ModelModeType }) => { + if (isAdvancedMode) { + const appMode = mode + + if (modeMode === ModelModeType.completion) { + if (appMode === AppType.chat) { + if (!completionPromptConfig.prompt.text || !completionPromptConfig.conversation_histories_role.assistant_prefix || !completionPromptConfig.conversation_histories_role.user_prefix) + await migrateToDefaultPrompt(true, ModelModeType.completion) + } + else { + if (!completionPromptConfig.prompt.text) + await migrateToDefaultPrompt(true, ModelModeType.completion) + } + } + if (modeMode === ModelModeType.chat) { + if (chatPromptConfig.prompt.length === 0) + await migrateToDefaultPrompt(true, ModelModeType.chat) + } + } + const newModelConfig = produce(modelConfig, (draft) => { + draft.provider = provider + draft.model_id = modelId + draft.mode = modeMode + }) + + setModelConfig(newModelConfig) + } useEffect(() => { fetchAppDetail({ url: '/apps', id: appId }).then(async (res) => { setMode(res.mode) const modelConfig = res.model_config + const promptMode = modelConfig.prompt_type === PromptMode.advanced ? PromptMode.advanced : PromptMode.simple + doSetPromptMode(promptMode) + if (promptMode === PromptMode.advanced) { + setChatPromptConfig(modelConfig.chat_prompt_config || clone(DEFAULT_CHAT_PROMPT_CONFIG) as any) + setCompletionPromptConfig(modelConfig.completion_prompt_config || clone(DEFAULT_COMPLETION_PROMPT_CONFIG) as any) + setCanReturnToSimpleMode(false) + } + const model = res.model_config.model let datasets: any = null @@ -177,6 +305,7 @@ const Configuration: FC = () => { modelConfig: { provider: model.provider, model_id: model.name, + mode: model.mode, configs: { prompt_template: modelConfig.pre_prompt, prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form, modelConfig.dataset_query_variable), @@ -197,10 +326,38 @@ const Configuration: FC = () => { }) }, [appId]) - const promptEmpty = mode === AppType.completion && !modelConfig.configs.prompt_template + const promptEmpty = (() => { + if (mode === AppType.chat) + return false + + if (isAdvancedMode) { + if (modelModeType === ModelModeType.chat) + return chatPromptConfig.prompt.every(({ text }) => !text) + + else + return !completionPromptConfig.prompt.text + } + + else { return !modelConfig.configs.prompt_template } + })() + const cannotPublish = (() => { + if (mode === AppType.chat) { + if (!isAdvancedMode) + return false + + if (modelModeType === ModelModeType.completion) { + if (!hasSetBlockStatus.history || !hasSetBlockStatus.query) + return true + + return false + } + + return false + } + else { return promptEmpty } + })() const contextVarEmpty = mode === AppType.completion && dataSets.length > 0 && !hasSetContextVar - const cannotPublish = promptEmpty || contextVarEmpty - const saveAppConfig = async () => { + const handlePublish = async (isSilence?: boolean) => { const modelId = modelConfig.model_id const promptTemplate = modelConfig.configs.prompt_template const promptVariables = modelConfig.configs.prompt_variables @@ -209,6 +366,18 @@ const Configuration: FC = () => { notify({ type: 'error', message: t('appDebug.otherError.promptNoBeEmpty'), duration: 3000 }) return } + if (isAdvancedMode && mode === AppType.chat) { + if (modelModeType === ModelModeType.completion) { + if (!hasSetBlockStatus.history) { + notify({ type: 'error', message: t('appDebug.otherError.historyNoBeEmpty'), duration: 3000 }) + return + } + if (!hasSetBlockStatus.query) { + notify({ type: 'error', message: t('appDebug.otherError.queryNoBeEmpty'), duration: 3000 }) + return + } + } + } if (contextVarEmpty) { notify({ type: 'error', message: t('appDebug.feature.dataSet.queryVariable.contextVarNotEmpty'), duration: 3000 }) return @@ -222,7 +391,11 @@ const Configuration: FC = () => { // new model config data struct const data: BackendModelConfig = { - pre_prompt: promptTemplate, + // Simple Mode prompt + pre_prompt: !isAdvancedMode ? promptTemplate : '', + prompt_type: promptMode, + chat_prompt_config: {}, + completion_prompt_config: {}, user_input_form: promptVariablesToUserInputsForm(promptVariables), dataset_query_variable: contextVar || '', opening_statement: introduction || '', @@ -237,8 +410,15 @@ const Configuration: FC = () => { model: { provider: modelConfig.provider, name: modelId, + mode: modelConfig.mode, completion_params: completionParams as any, }, + dataset_configs: datasetConfigs, + } + + if (isAdvancedMode) { + data.chat_prompt_config = chatPromptConfig + data.completion_prompt_config = completionPromptConfig } await updateAppModelConfig({ url: `/apps/${appId}/model-config`, body: data }) @@ -254,7 +434,11 @@ const Configuration: FC = () => { modelConfig: newModelConfig, completionParams, }) - notify({ type: 'success', message: t('common.api.success'), duration: 3000 }) + if (!isSilence) + notify({ type: 'success', message: t('common.api.success'), duration: 3000 }) + + setCanReturnToSimpleMode(false) + return true } const [showConfirm, setShowConfirm] = useState(false) @@ -278,6 +462,20 @@ const Configuration: FC = () => { hasSetAPIKEY, isTrailFinished, mode, + modelModeType, + promptMode, + isAdvancedMode, + setPromptMode, + canReturnToSimpleMode, + setCanReturnToSimpleMode, + chatPromptConfig, + completionPromptConfig, + currentAdvancedPrompt, + setCurrentAdvancedPrompt, + conversationHistoriesRole: completionPromptConfig.conversation_histories_role, + showHistoryModal, + setConversationHistoriesRole, + hasSetBlockStatus, conversationId, introduction, setIntroduction, @@ -304,23 +502,56 @@ const Configuration: FC = () => { setCompletionParams, modelConfig, setModelConfig, + showSelectDataSet, dataSets, setDataSets, + datasetConfigs, + setDatasetConfigs, hasSetContextVar, }} > <>
-
-
{t('appDebug.pageTitle')}
+
+
+
{t('appDebug.pageTitle.line1')}
+
+
{t('appDebug.pageTitle.line2')}
+ {/* modelModeType missing can not load template */} + {(!isAdvancedMode && modelModeType) && ( +
setPromptMode(PromptMode.advanced)} + className={'cursor-pointer text-indigo-600'} + > + {t('appDebug.promptMode.simple')} +
+ )} + {isAdvancedMode && ( +
+
{t('appDebug.promptMode.advanced')}
+ {canReturnToSimpleMode && ( +
setPromptMode(PromptMode.simple)} + className='flex items-center h-6 px-2 bg-indigo-600 shadow-xs border border-gray-200 rounded-lg text-white text-xs font-semibold cursor-pointer space-x-1' + > + +
{t('appDebug.promptMode.switchBack')}
+
+ )} +
+ )} +
+
+
{/* Model and Parameters */} { setCompletionParams(newParams) }} @@ -328,14 +559,14 @@ const Configuration: FC = () => { />
- +
-
+
-
+
@@ -373,6 +604,28 @@ const Configuration: FC = () => { {isShowSetAPIKey && { hideSetAPIkey() }} />} + + {isShowSelectDataSet && ( + + )} + + {isShowHistoryModal && ( + { + setConversationHistoriesRole(data) + hideHistoryModal() + }} + /> + )} ) diff --git a/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx b/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx new file mode 100644 index 0000000000..8684dbab34 --- /dev/null +++ b/web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx @@ -0,0 +1,35 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' + +const AdvancedModeWarning: FC = () => { + const { t } = useTranslation() + const [show, setShow] = React.useState(true) + if (!show) + return null + return ( +
+
{t('appDebug.promptMode.advancedWarning.title')}
+
+
+ {t('appDebug.promptMode.advancedWarning.description')} + {/* TODO: Doc link */} + {/* + {t('appDebug.promptMode.advancedWarning.learnMore')} + */} +
+ +
setShow(false)} + >{t('appDebug.promptMode.advancedWarning.ok')}
+
+
+ ) +} +export default React.memo(AdvancedModeWarning) diff --git a/web/app/components/app/configuration/prompt-value-panel/index.tsx b/web/app/components/app/configuration/prompt-value-panel/index.tsx index f1c652a160..f2c5a5d6c7 100644 --- a/web/app/components/app/configuration/prompt-value-panel/index.tsx +++ b/web/app/components/app/configuration/prompt-value-panel/index.tsx @@ -6,10 +6,9 @@ import { useContext } from 'use-context-selector' import { PlayIcon, } from '@heroicons/react/24/solid' -import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development' import ConfigContext from '@/context/debug-configuration' import type { PromptVariable } from '@/models/debug' -import { AppType } from '@/types/app' +import { AppType, ModelModeType } from '@/types/app' import Select from '@/app/components/base/select' import { DEFAULT_VALUE_MAX_LEN } from '@/config' import Button from '@/app/components/base/button' @@ -21,23 +20,13 @@ export type IPromptValuePanelProps = { onSend?: () => void } -const starIcon = ( - - - - - -) - const PromptValuePanel: FC = ({ appType, onSend, }) => { const { t } = useTranslation() - const { modelConfig, inputs, setInputs, mode } = useContext(ConfigContext) - const [promptPreviewCollapse, setPromptPreviewCollapse] = useState(false) + const { modelModeType, modelConfig, inputs, setInputs, mode, isAdvancedMode, completionPromptConfig, chatPromptConfig } = useContext(ConfigContext) const [userInputFieldCollapse, setUserInputFieldCollapse] = useState(false) - const promptTemplate = modelConfig.configs.prompt_template const promptVariables = modelConfig.configs.prompt_variables.filter(({ key, name }) => { return key && key?.trim() && name && name?.trim() }) @@ -50,7 +39,18 @@ const PromptValuePanel: FC = ({ return obj })() - const canNotRun = mode === AppType.completion && !modelConfig.configs.prompt_template + const canNotRun = (() => { + if (mode !== AppType.completion) + return true + + if (isAdvancedMode) { + if (modelModeType === ModelModeType.chat) + return chatPromptConfig.prompt.every(({ text }) => !text) + return !completionPromptConfig.prompt.text + } + + else { return !modelConfig.configs.prompt_template } + })() const renderRunButton = () => { return (