From 18c710c906bed46fd0723947aeb7b663d3c33c3d Mon Sep 17 00:00:00 2001 From: Garfield Dai Date: Wed, 27 Sep 2023 14:53:22 +0800 Subject: [PATCH] feat: support binding context var (#1227) Co-authored-by: Joel --- api/commands.py | 71 +++++++++++++ api/controllers/console/app/app.py | 4 +- api/controllers/console/app/model_config.py | 3 +- api/core/completion.py | 15 ++- ...dd_dataset_query_variable_at_app_model_.py | 31 ++++++ api/models/model.py | 4 + api/services/app_model_config_service.py | 22 ++++- api/services/completion_service.py | 3 +- .../app/configuration/base/icons/var-icon.tsx | 11 --- .../warning-mask/cannot-query-dataset.tsx | 31 ++++++ .../base/warning-mask/formatting-changed.tsx | 7 +- .../app/configuration/config-var/index.tsx | 54 ++++++++-- .../config-var/input-type-icon.tsx | 25 ++--- .../dataset-config/context-var/index.tsx | 39 ++++++++ .../context-var/style.module.css | 3 + .../dataset-config/context-var/var-picker.tsx | 99 +++++++++++++++++++ .../configuration/dataset-config/index.tsx | 39 +++++++- .../app/configuration/debug/index.tsx | 23 ++++- .../components/app/configuration/index.tsx | 17 +++- .../prompt-value-panel/index.tsx | 4 +- web/app/components/base/confirm/common.tsx | 4 +- .../vender/line/development/brackets-x.svg | 3 + .../assets/vender/solid/editor/paragraph.svg | 5 + .../vender/solid/editor/type-square.svg | 3 + .../vender/solid/general/check-done-01.svg | 4 + .../vender/line/development/BracketsX.json | 29 ++++++ .../src/vender/line/development/BracketsX.tsx | 16 +++ .../src/vender/line/development/index.ts | 1 + .../src/vender/solid/editor/Paragraph.json | 44 +++++++++ .../src/vender/solid/editor/Paragraph.tsx | 16 +++ .../src/vender/solid/editor/TypeSquare.json | 28 ++++++ .../src/vender/solid/editor/TypeSquare.tsx | 16 +++ .../icons/src/vender/solid/editor/index.ts | 2 + .../src/vender/solid/general/CheckDone01.json | 37 +++++++ .../src/vender/solid/general/CheckDone01.tsx | 16 +++ .../icons/src/vender/solid/general/index.ts | 1 + web/context/debug-configuration.ts | 2 + web/i18n/lang/app-debug.en.ts | 13 +++ web/i18n/lang/app-debug.zh.ts | 13 +++ web/models/debug.ts | 1 + web/tailwind.config.js | 1 + web/types/app.ts | 1 + web/utils/model-config.ts | 7 +- web/yarn.lock | 20 ---- 44 files changed, 711 insertions(+), 77 deletions(-) create mode 100644 api/migrations/versions/ab23c11305d4_add_dataset_query_variable_at_app_model_.py delete mode 100644 web/app/components/app/configuration/base/icons/var-icon.tsx create mode 100644 web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx create mode 100644 web/app/components/app/configuration/dataset-config/context-var/index.tsx create mode 100644 web/app/components/app/configuration/dataset-config/context-var/style.module.css create mode 100644 web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx create mode 100644 web/app/components/base/icons/assets/vender/line/development/brackets-x.svg create mode 100644 web/app/components/base/icons/assets/vender/solid/editor/paragraph.svg create mode 100644 web/app/components/base/icons/assets/vender/solid/editor/type-square.svg create mode 100644 web/app/components/base/icons/assets/vender/solid/general/check-done-01.svg create mode 100644 web/app/components/base/icons/src/vender/line/development/BracketsX.json create mode 100644 web/app/components/base/icons/src/vender/line/development/BracketsX.tsx create mode 100644 web/app/components/base/icons/src/vender/solid/editor/Paragraph.json create mode 100644 web/app/components/base/icons/src/vender/solid/editor/Paragraph.tsx create mode 100644 web/app/components/base/icons/src/vender/solid/editor/TypeSquare.json create mode 100644 web/app/components/base/icons/src/vender/solid/editor/TypeSquare.tsx create mode 100644 web/app/components/base/icons/src/vender/solid/general/CheckDone01.json create mode 100644 web/app/components/base/icons/src/vender/solid/general/CheckDone01.tsx diff --git a/api/commands.py b/api/commands.py index d5a0e4785..105f93656 100644 --- a/api/commands.py +++ b/api/commands.py @@ -647,6 +647,76 @@ def update_app_model_configs(batch_size): pbar.update(len(data_batch)) +@click.command('migrate_default_input_to_dataset_query_variable') +@click.option("--batch-size", default=500, help="Number of records to migrate in each batch.") +def migrate_default_input_to_dataset_query_variable(batch_size): + + click.secho("Starting...", fg='green') + + total_records = db.session.query(AppModelConfig) \ + .join(App, App.app_model_config_id == AppModelConfig.id) \ + .filter(App.mode == 'completion') \ + .filter(AppModelConfig.dataset_query_variable == None) \ + .count() + + if total_records == 0: + click.secho("No data to migrate.", fg='green') + return + + num_batches = (total_records + batch_size - 1) // batch_size + + with tqdm(total=total_records, desc="Migrating Data") as pbar: + for i in range(num_batches): + offset = i * batch_size + limit = min(batch_size, total_records - offset) + + click.secho(f"Fetching batch {i + 1}/{num_batches} from source database...", fg='green') + + data_batch = db.session.query(AppModelConfig) \ + .join(App, App.app_model_config_id == AppModelConfig.id) \ + .filter(App.mode == 'completion') \ + .filter(AppModelConfig.dataset_query_variable == None) \ + .order_by(App.created_at) \ + .offset(offset).limit(limit).all() + + if not data_batch: + click.secho("No more data to migrate.", fg='green') + break + + try: + click.secho(f"Migrating {len(data_batch)} records...", fg='green') + for data in data_batch: + config = AppModelConfig.to_dict(data) + + tools = config["agent_mode"]["tools"] + dataset_exists = "dataset" in str(tools) + if not dataset_exists: + continue + + user_input_form = config.get("user_input_form", []) + for form in user_input_form: + paragraph = form.get('paragraph') + if paragraph \ + and paragraph.get('variable') == 'query': + data.dataset_query_variable = 'query' + break + + if paragraph \ + and paragraph.get('variable') == 'default_input': + data.dataset_query_variable = 'default_input' + break + + db.session.commit() + + except Exception as e: + click.secho(f"Error while migrating data: {e}, app_id: {data.app_id}, app_model_config_id: {data.id}", + fg='red') + continue + + click.secho(f"Successfully migrated batch {i + 1}/{num_batches}.", fg='green') + + pbar.update(len(data_batch)) + def register_commands(app): app.cli.add_command(reset_password) @@ -660,3 +730,4 @@ def register_commands(app): app.cli.add_command(update_qdrant_indexes) app.cli.add_command(update_app_model_configs) app.cli.add_command(normalization_collections) + app.cli.add_command(migrate_default_input_to_dataset_query_variable) diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index 97b862c76..7c88a14b7 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -34,6 +34,7 @@ model_config_fields = { 'sensitive_word_avoidance': fields.Raw(attribute='sensitive_word_avoidance_dict'), 'model': fields.Raw(attribute='model_dict'), 'user_input_form': fields.Raw(attribute='user_input_form_list'), + 'dataset_query_variable': fields.String, 'pre_prompt': fields.String, 'agent_mode': fields.Raw(attribute='agent_mode_dict'), } @@ -162,7 +163,8 @@ class AppListApi(Resource): model_configuration = AppModelConfigService.validate_configuration( tenant_id=current_user.current_tenant_id, account=current_user, - config=model_config_dict + config=model_config_dict, + mode=args['mode'] ) app = App( diff --git a/api/controllers/console/app/model_config.py b/api/controllers/console/app/model_config.py index 4ffb8dd13..5b35d234d 100644 --- a/api/controllers/console/app/model_config.py +++ b/api/controllers/console/app/model_config.py @@ -31,7 +31,8 @@ class ModelConfigResource(Resource): model_configuration = AppModelConfigService.validate_configuration( tenant_id=current_user.current_tenant_id, account=current_user, - config=request.json + config=request.json, + mode=app_model.mode ) new_app_model_config = AppModelConfig( diff --git a/api/core/completion.py b/api/core/completion.py index b66e965b3..59d589eab 100644 --- a/api/core/completion.py +++ b/api/core/completion.py @@ -108,12 +108,14 @@ class Completion: retriever_from=retriever_from ) + query_for_agent = cls.get_query_for_agent(app, app_model_config, query, inputs) + # run agent executor agent_execute_result = None - if agent_executor: - should_use_agent = agent_executor.should_use_agent(query) + if query_for_agent and agent_executor: + should_use_agent = agent_executor.should_use_agent(query_for_agent) if should_use_agent: - agent_execute_result = agent_executor.run(query) + agent_execute_result = agent_executor.run(query_for_agent) # When no extra pre prompt is specified, # the output of the agent can be used directly as the main output content without calling LLM again @@ -142,6 +144,13 @@ class Completion: logging.warning(f'ChunkedEncodingError: {e}') conversation_message_task.end() return + + @classmethod + def get_query_for_agent(cls, app: App, app_model_config: AppModelConfig, query: str, inputs: dict) -> str: + if app.mode != 'completion': + return query + + return inputs.get(app_model_config.dataset_query_variable, "") @classmethod def run_final_llm(cls, model_instance: BaseLLM, mode: str, app_model_config: AppModelConfig, query: str, diff --git a/api/migrations/versions/ab23c11305d4_add_dataset_query_variable_at_app_model_.py b/api/migrations/versions/ab23c11305d4_add_dataset_query_variable_at_app_model_.py new file mode 100644 index 000000000..eec2dc64b --- /dev/null +++ b/api/migrations/versions/ab23c11305d4_add_dataset_query_variable_at_app_model_.py @@ -0,0 +1,31 @@ +"""add dataset query variable at app model configs. + +Revision ID: ab23c11305d4 +Revises: 6e2cfb077b04 +Create Date: 2023-09-26 12:22:59.044088 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'ab23c11305d4' +down_revision = '6e2cfb077b04' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('app_model_configs', schema=None) as batch_op: + batch_op.add_column(sa.Column('dataset_query_variable', sa.String(length=255), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('app_model_configs', schema=None) as batch_op: + batch_op.drop_column('dataset_query_variable') + + # ### end Alembic commands ### diff --git a/api/models/model.py b/api/models/model.py index 078b45c9e..5fb2abe71 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -88,6 +88,7 @@ class AppModelConfig(db.Model): more_like_this = db.Column(db.Text) model = db.Column(db.Text) user_input_form = db.Column(db.Text) + dataset_query_variable = db.Column(db.String(255)) pre_prompt = db.Column(db.Text) agent_mode = db.Column(db.Text) sensitive_word_avoidance = db.Column(db.Text) @@ -152,6 +153,7 @@ class AppModelConfig(db.Model): "sensitive_word_avoidance": self.sensitive_word_avoidance_dict, "model": self.model_dict, "user_input_form": self.user_input_form_list, + "dataset_query_variable": self.dataset_query_variable, "pre_prompt": self.pre_prompt, "agent_mode": self.agent_mode_dict } @@ -170,6 +172,7 @@ class AppModelConfig(db.Model): if model_config.get('sensitive_word_avoidance') else None self.model = json.dumps(model_config['model']) self.user_input_form = json.dumps(model_config['user_input_form']) + self.dataset_query_variable = model_config['dataset_query_variable'] self.pre_prompt = model_config['pre_prompt'] self.agent_mode = json.dumps(model_config['agent_mode']) self.retriever_resource = json.dumps(model_config['retriever_resource']) \ @@ -191,6 +194,7 @@ class AppModelConfig(db.Model): sensitive_word_avoidance=self.sensitive_word_avoidance, model=self.model, user_input_form=self.user_input_form, + dataset_query_variable=self.dataset_query_variable, pre_prompt=self.pre_prompt, agent_mode=self.agent_mode ) diff --git a/api/services/app_model_config_service.py b/api/services/app_model_config_service.py index f402b4e2b..f3334ca9b 100644 --- a/api/services/app_model_config_service.py +++ b/api/services/app_model_config_service.py @@ -81,7 +81,7 @@ class AppModelConfigService: return filtered_cp @staticmethod - def validate_configuration(tenant_id: str, account: Account, config: dict) -> dict: + def validate_configuration(tenant_id: str, account: Account, config: dict, mode: str) -> dict: # opening_statement if 'opening_statement' not in config or not config["opening_statement"]: config["opening_statement"] = "" @@ -335,6 +335,9 @@ class AppModelConfigService: if not AppModelConfigService.is_dataset_exists(account, tool_item["id"]): raise ValueError("Dataset ID does not exist, please check your permission.") + + # dataset_query_variable + AppModelConfigService.is_dataset_query_variable_valid(config, mode) # Filter out extra parameters filtered_config = { @@ -351,8 +354,25 @@ class AppModelConfigService: "completion_params": config["model"]["completion_params"] }, "user_input_form": config["user_input_form"], + "dataset_query_variable": config["dataset_query_variable"], "pre_prompt": config["pre_prompt"], "agent_mode": config["agent_mode"] } return filtered_config + + @staticmethod + def is_dataset_query_variable_valid(config: dict, mode: str) -> None: + # Only check when mode is completion + if mode != 'completion': + return + + agent_mode = config.get("agent_mode", {}) + tools = agent_mode.get("tools", []) + dataset_exists = "dataset" in str(tools) + + dataset_query_variable = config.get("dataset_query_variable") + + if dataset_exists and not dataset_query_variable: + raise ValueError("Dataset query variable is required when dataset is exist") + diff --git a/api/services/completion_service.py b/api/services/completion_service.py index f8cb0d9bc..8f495db4a 100644 --- a/api/services/completion_service.py +++ b/api/services/completion_service.py @@ -117,7 +117,8 @@ class CompletionService: model_config = AppModelConfigService.validate_configuration( tenant_id=app_model.tenant_id, account=user, - config=args['model_config'] + config=args['model_config'], + mode=app_model.mode ) app_model_config = AppModelConfig( diff --git a/web/app/components/app/configuration/base/icons/var-icon.tsx b/web/app/components/app/configuration/base/icons/var-icon.tsx deleted file mode 100644 index e991e5bb3..000000000 --- a/web/app/components/app/configuration/base/icons/var-icon.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client' -import React, { FC } from 'react' - -const VarIcon: FC = () => { - return ( - - - - ) -} -export default React.memo(VarIcon) diff --git a/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx b/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx new file mode 100644 index 000000000..fa00cfc66 --- /dev/null +++ b/web/app/components/app/configuration/base/warning-mask/cannot-query-dataset.tsx @@ -0,0 +1,31 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import WarningMask from '.' +import Button from '@/app/components/base/button' + +export type IFormattingChangedProps = { + onConfirm: () => void +} + +const FormattingChanged: FC = ({ + onConfirm, +}) => { + const { t } = useTranslation() + + return ( + + + + } + /> + ) +} +export default React.memo(FormattingChanged) diff --git a/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx b/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx index 61787a4d0..421e4ba59 100644 --- a/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx +++ b/web/app/components/app/configuration/base/warning-mask/formatting-changed.tsx @@ -1,10 +1,11 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import WarningMask from '.' import Button from '@/app/components/base/button' -export interface IFormattingChangedProps { +export type IFormattingChangedProps = { onConfirm: () => void onCancel: () => void } @@ -17,7 +18,7 @@ const icon = ( const FormattingChanged: FC = ({ onConfirm, - onCancel + onCancel, }) => { const { t } = useTranslation() diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index 6ca0f3562..803381560 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -5,19 +5,24 @@ import { useTranslation } from 'react-i18next' import { Cog8ToothIcon, TrashIcon } from '@heroicons/react/24/outline' import { useBoolean } from 'ahooks' import type { Timeout } from 'ahooks/lib/useRequest/src/types' +import { useContext } from 'use-context-selector' import Panel from '../base/feature-panel' import OperationBtn from '../base/operation-btn' -import VarIcon from '../base/icons/var-icon' import EditModal from './config-modal' import IconTypeIcon from './input-type-icon' import type { IInputTypeIconProps } from './input-type-icon' import s from './style.module.css' +import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development' import Tooltip from '@/app/components/base/tooltip' import type { PromptVariable } from '@/models/debug' import { DEFAULT_VALUE_MAX_LEN, getMaxVarNameLength } from '@/config' import { checkKeys, getNewVar } from '@/utils/var' import Switch from '@/app/components/base/switch' import Toast from '@/app/components/base/toast' +import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general' +import ConfirmModal from '@/app/components/base/confirm/common' +import ConfigContext from '@/context/debug-configuration' +import { AppType } from '@/types/app' export type IConfigVarProps = { promptVariables: PromptVariable[] @@ -29,6 +34,11 @@ let conflictTimer: Timeout const ConfigVar: FC = ({ promptVariables, readonly, onPromptVariablesChange }) => { const { t } = useTranslation() + const { + mode, + dataSets, + } = useContext(ConfigContext) + const hasVar = promptVariables.length > 0 const promptVariableObj = (() => { const obj: Record = {} @@ -127,10 +137,23 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar onPromptVariablesChange?.([...promptVariables, newVar]) } - const handleRemoveVar = (index: number) => { + const [isShowDeleteContextVarModal, { setTrue: showDeleteContextVarModal, setFalse: hideDeleteContextVarModal }] = useBoolean(false) + const [removeIndex, setRemoveIndex] = useState(null) + const didRemoveVar = (index: number) => { onPromptVariablesChange?.(promptVariables.filter((_, i) => i !== index)) } + const handleRemoveVar = (index: number) => { + const removeVar = promptVariables[index] + + if (mode === AppType.completion && dataSets.length > 0 && removeVar.is_context_var) { + showDeleteContextVarModal() + setRemoveIndex(index) + return + } + didRemoveVar(index) + } + const [currKey, setCurrKey] = useState(null) const currItem = currKey ? promptVariables.find(item => item.key === currKey) : null const [isShowEditModal, { setTrue: showEditModal, setFalse: hideEditModal }] = useBoolean(false) @@ -143,18 +166,17 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar + } title={ -
-
{t('appDebug.variableTitle')}
+
+
{t('appDebug.variableTitle')}
{!readonly && ( {t('appDebug.variableTip')}
} selector='config-var-tooltip'> - - - + + )}
@@ -185,7 +207,7 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar
- + {!readonly ? ( = ({ promptVariables, readonly, onPromptVar /> )} + {isShowDeleteContextVarModal && ( + { + didRemoveVar(removeIndex as number) + hideDeleteContextVarModal() + }} + onCancel={hideDeleteContextVarModal} + /> + )} + ) } diff --git a/web/app/components/app/configuration/config-var/input-type-icon.tsx b/web/app/components/app/configuration/config-var/input-type-icon.tsx index 9922ef16d..c5c8ae02b 100644 --- a/web/app/components/app/configuration/config-var/input-type-icon.tsx +++ b/web/app/components/app/configuration/config-var/input-type-icon.tsx @@ -1,31 +1,25 @@ 'use client' import React from 'react' import type { FC } from 'react' +import { Paragraph, TypeSquare } from '@/app/components/base/icons/src/vender/solid/editor' +import { CheckDone01 } from '@/app/components/base/icons/src/vender/solid/general' export type IInputTypeIconProps = { type: 'string' | 'select' + className: string } -const IconMap = (type: IInputTypeIconProps['type']) => { +const IconMap = (type: IInputTypeIconProps['type'], className: string) => { + const classNames = `w-3.5 h-3.5 ${className}` const icons = { string: ( - - - + ), paragraph: ( - - - - - - + ), select: ( - - - - + ), } @@ -34,8 +28,9 @@ const IconMap = (type: IInputTypeIconProps['type']) => { const InputTypeIcon: FC = ({ type, + className, }) => { - const Icon = IconMap(type) + const Icon = IconMap(type, className) return Icon } diff --git a/web/app/components/app/configuration/dataset-config/context-var/index.tsx b/web/app/components/app/configuration/dataset-config/context-var/index.tsx new file mode 100644 index 000000000..c38176d9b --- /dev/null +++ b/web/app/components/app/configuration/dataset-config/context-var/index.tsx @@ -0,0 +1,39 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import type { Props } from './var-picker' +import VarPicker from './var-picker' +import { BracketsX } from '@/app/components/base/icons/src/vender/line/development' +import Tooltip from '@/app/components/base/tooltip' +import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general' + +const ContextVar: FC = (props) => { + const { t } = useTranslation() + const { value, options } = props + const currItem = options.find(item => item.value === value) + const notSetVar = !currItem + return ( +
+
+
+ +
+
{t('appDebug.feature.dataSet.queryVariable.title')}
+ + {t('appDebug.feature.dataSet.queryVariable.tip')} +
} + selector='context-var-tooltip' + > + + +
+ + +
+ ) +} + +export default React.memo(ContextVar) diff --git a/web/app/components/app/configuration/dataset-config/context-var/style.module.css b/web/app/components/app/configuration/dataset-config/context-var/style.module.css new file mode 100644 index 000000000..cbfaae8e2 --- /dev/null +++ b/web/app/components/app/configuration/dataset-config/context-var/style.module.css @@ -0,0 +1,3 @@ +.trigger:hover .dropdownIcon { + color: #98A2B3; +} \ No newline at end of file diff --git a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx new file mode 100644 index 000000000..54ad45aae --- /dev/null +++ b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx @@ -0,0 +1,99 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { ChevronDownIcon } from '@heroicons/react/24/outline' +import cn from 'classnames' +import s from './style.module.css' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import type { IInputTypeIconProps } from '@/app/components/app/configuration/config-var/input-type-icon' +import IconTypeIcon from '@/app/components/app/configuration/config-var/input-type-icon' + +type Option = { name: string; value: string; type: string } +export type Props = { + value: string | undefined + options: Option[] + onChange: (value: string) => void +} + +const VarItem: FC<{ item: Option }> = ({ item }) => ( +
+ +
+ {'{{'} + {item.value} + {'}}'} +
+
+) +const VarPicker: FC = ({ + value, + options, + onChange, +}) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const currItem = options.find(item => item.value === value) + const notSetVar = !currItem + return ( + + setOpen(v => !v)}> +
+
+ {value + ? ( + + ) + : (
+ {t('appDebug.feature.dataSet.queryVariable.choosePlaceholder')} +
)} +
+ +
+
+ + {options.length > 0 + ? (
+ {options.map(({ name, value, type }, index) => ( +
{ + onChange(value) + setOpen(false) + }} + > + +
+ ))} +
) + : ( +
+
{t('appDebug.feature.dataSet.queryVariable.noVar')}
+
{t('appDebug.feature.dataSet.queryVariable.noVarTip')}
+
+ )} + +
+
+ ) +} +export default React.memo(VarPicker) diff --git a/web/app/components/app/configuration/dataset-config/index.tsx b/web/app/components/app/configuration/dataset-config/index.tsx index d69bb094c..05f7a3589 100644 --- a/web/app/components/app/configuration/dataset-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/index.tsx @@ -10,8 +10,10 @@ import FeaturePanel from '../base/feature-panel' import OperationBtn from '../base/operation-btn' import CardItem from './card-item' import SelectDataSet from './select-dataset' +import ContextVar from './context-var' import ConfigContext from '@/context/debug-configuration' import type { DataSet } from '@/models/datasets' +import { AppType } from '@/types/app' const Icon = ( @@ -23,9 +25,12 @@ const Icon = ( const DatasetConfig: FC = () => { const { t } = useTranslation() const { + mode, dataSets: dataSet, setDataSets: setDataSet, setFormattingChanged, + modelConfig, + setModelConfig, } = useContext(ConfigContext) const selectedIds = dataSet.map(item => item.id) @@ -60,6 +65,25 @@ const DatasetConfig: FC = () => { setFormattingChanged(true) } + const promptVariables = modelConfig.configs.prompt_variables + const promptVariablesToSelect = promptVariables.map(item => ({ + name: item.name, + type: item.type, + value: item.key, + })) + const selectedContextVar = promptVariables?.find(item => item.is_context_var) + const handleSelectContextVar = (selectedValue: string) => { + const newModelConfig = produce(modelConfig, (draft) => { + draft.configs.prompt_variables = modelConfig.configs.prompt_variables.map((item) => { + return ({ + ...item, + is_context_var: item.key === selectedValue, + }) + }) + }) + setModelConfig(newModelConfig) + } + return ( { title={t('appDebug.feature.dataSet.title')} headerRight={} hasHeaderBottomBorder={!hasData} + noBodySpacing > {hasData ? ( -
+
{dataSet.map(item => ( {
) : ( -
{t('appDebug.feature.dataSet.noData')}
+
+
{t('appDebug.feature.dataSet.noData')}
+
)} + {mode === AppType.completion && dataSet.length > 0 && ( + + )} + {isShowSelectDataSet && ( void @@ -52,6 +52,7 @@ const Debug: FC = ({ dataSets, modelConfig, completionParams, + hasSetContextVar, } = useContext(ConfigContext) const { speech2textDefaultModel } = useProviderContext() const [chatList, setChatList, getChatList] = useGetState([]) @@ -77,6 +78,7 @@ const Debug: FC = ({ const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const [abortController, setAbortController] = useState(null) const [isShowFormattingChangeConfirm, setIsShowFormattingChangeConfirm] = useState(false) + const [isShowCannotQueryDataset, setShowCannotQueryDataset] = useState(false) const [isShowSuggestion, setIsShowSuggestion] = useState(false) const [messageTaskId, setMessageTaskId] = useState('') const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false) @@ -157,6 +159,7 @@ const Debug: FC = ({ const postModelConfig: BackendModelConfig = { pre_prompt: modelConfig.configs.prompt_template, user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables), + dataset_query_variable: '', opening_statement: introduction, more_like_this: { enabled: false, @@ -298,6 +301,7 @@ const Debug: FC = ({ }, [controlClearChatMessage]) const [completionRes, setCompletionRes] = useState('') + const [messageId, setMessageId] = useState(null) const sendTextCompletion = async () => { if (isResponsing) { @@ -305,6 +309,11 @@ const Debug: FC = ({ return false } + if (dataSets.length > 0 && !hasSetContextVar) { + setShowCannotQueryDataset(true) + return true + } + if (!checkCanSend()) return @@ -314,10 +323,12 @@ 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, user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables), + dataset_query_variable: contextVar || '', opening_statement: introduction, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, speech_to_text: speechToTextConfig, @@ -340,13 +351,15 @@ const Debug: FC = ({ } setCompletionRes('') + setMessageId('') const res: string[] = [] setResponsingTrue() sendCompletionMessage(appId, data, { - onData: (data: string) => { + onData: (data: string, _isFirstMessage: boolean, { messageId }) => { res.push(data) setCompletionRes(res.join('')) + setMessageId(messageId) }, onCompleted() { setResponsingFalse() @@ -415,6 +428,7 @@ const Debug: FC = ({ content={completionRes} isLoading={!completionRes && isResponsing} isInstalledApp={false} + messageId={messageId} /> )}
@@ -425,6 +439,11 @@ const Debug: FC = ({ onCancel={handleCancel} /> )} + {isShowCannotQueryDataset && ( + setShowCannotQueryDataset(false)} + /> + )} {!hasSetAPIKEY && ()} diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index c5973210f..e48bbb4d8 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -100,7 +100,8 @@ const Configuration: FC = () => { } const [dataSets, setDataSets] = useState([]) - + const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key + const hasSetContextVar = !!contextVar const syncToPublishedConfig = (_publishedConfig: PublichConfig) => { const modelConfig = _publishedConfig.modelConfig setModelConfig(_publishedConfig.modelConfig) @@ -178,7 +179,7 @@ const Configuration: FC = () => { model_id: model.name, configs: { prompt_template: modelConfig.pre_prompt, - prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form), + prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form, modelConfig.dataset_query_variable), }, opening_statement: modelConfig.opening_statement, more_like_this: modelConfig.more_like_this, @@ -196,16 +197,22 @@ const Configuration: FC = () => { }) }, [appId]) - const cannotPublish = mode === AppType.completion && !modelConfig.configs.prompt_template + const promptEmpty = mode === AppType.completion && !modelConfig.configs.prompt_template + const contextVarEmpty = mode === AppType.completion && dataSets.length > 0 && !hasSetContextVar + const cannotPublish = promptEmpty || contextVarEmpty const saveAppConfig = async () => { const modelId = modelConfig.model_id const promptTemplate = modelConfig.configs.prompt_template const promptVariables = modelConfig.configs.prompt_variables - if (cannotPublish) { + if (promptEmpty) { notify({ type: 'error', message: t('appDebug.otherError.promptNoBeEmpty'), duration: 3000 }) return } + if (contextVarEmpty) { + notify({ type: 'error', message: t('appDebug.feature.dataSet.queryVariable.contextVarNotEmpty'), duration: 3000 }) + return + } const postDatasets = dataSets.map(({ id }) => ({ dataset: { enabled: true, @@ -217,6 +224,7 @@ const Configuration: FC = () => { const data: BackendModelConfig = { pre_prompt: promptTemplate, user_input_form: promptVariablesToUserInputsForm(promptVariables), + dataset_query_variable: contextVar || '', opening_statement: introduction || '', more_like_this: moreLikeThisConfig, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, @@ -298,6 +306,7 @@ const Configuration: FC = () => { setModelConfig, dataSets, setDataSets, + hasSetContextVar, }} > <> 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 7aa4579f2..f1c652a16 100644 --- a/web/app/components/app/configuration/prompt-value-panel/index.tsx +++ b/web/app/components/app/configuration/prompt-value-panel/index.tsx @@ -6,7 +6,7 @@ import { useContext } from 'use-context-selector' import { PlayIcon, } from '@heroicons/react/24/solid' -import VarIcon from '../base/icons/var-icon' +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' @@ -131,7 +131,7 @@ const PromptValuePanel: FC = ({ `${!userInputFieldCollapse && 'mb-2'}` }>
setUserInputFieldCollapse(!userInputFieldCollapse)}> -
+
{t('appDebug.inputs.userInputField')}
{ userInputFieldCollapse diff --git a/web/app/components/base/confirm/common.tsx b/web/app/components/base/confirm/common.tsx index 282ff422f..19ad05340 100644 --- a/web/app/components/base/confirm/common.tsx +++ b/web/app/components/base/confirm/common.tsx @@ -17,6 +17,7 @@ export type ConfirmCommonProps = { onConfirm?: () => void showOperate?: boolean showOperateCancel?: boolean + confirmBtnClassName?: string confirmText?: string } @@ -29,6 +30,7 @@ const ConfirmCommon: FC = ({ onConfirm, showOperate = true, showOperateCancel = true, + confirmBtnClassName, confirmText, }) => { const { t } = useTranslation() @@ -72,7 +74,7 @@ const ConfirmCommon: FC = ({ }