diff --git a/api/commands.py b/api/commands.py index fff77430a9..9880115341 100644 --- a/api/commands.py +++ b/api/commands.py @@ -6,6 +6,7 @@ import string import time import click +from tqdm import tqdm from flask import current_app from langchain.embeddings import OpenAIEmbeddings from werkzeug.exceptions import NotFound @@ -21,9 +22,9 @@ from libs.password import password_pattern, valid_password, hash_password from libs.helper import email as email_validate from extensions.ext_database import db from libs.rsa import generate_key_pair -from models.account import InvitationCode, Tenant +from models.account import InvitationCode, Tenant, TenantAccountJoin from models.dataset import Dataset, DatasetQuery, Document -from models.model import Account +from models.model import Account, AppModelConfig, App import secrets import base64 @@ -439,6 +440,107 @@ def update_qdrant_indexes(): click.echo(click.style('Congratulations! Update {} dataset indexes.'.format(create_count), fg='green')) +@click.command('update_app_model_configs', help='Migrate data to support paragraph variable.') +@click.option("--batch-size", default=500, help="Number of records to migrate in each batch.") +def update_app_model_configs(batch_size): + pre_prompt_template = '{{default_input}}' + user_input_form_template = { + "en-US": [ + { + "paragraph": { + "label": "Query", + "variable": "default_input", + "required": False, + "default": "" + } + } + ], + "zh-Hans": [ + { + "paragraph": { + "label": "查询内容", + "variable": "default_input", + "required": False, + "default": "" + } + } + ] + } + + click.secho("Start migrate old data that the text generator can support paragraph variable.", fg='green') + + total_records = db.session.query(AppModelConfig) \ + .join(App, App.app_model_config_id == AppModelConfig.id) \ + .filter(App.mode == 'completion') \ + .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') \ + .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: + # click.secho(f"Migrating data {data.id}, pre_prompt: {data.pre_prompt}, user_input_form: {data.user_input_form}", fg='green') + + if data.pre_prompt is None: + data.pre_prompt = pre_prompt_template + else: + if pre_prompt_template in data.pre_prompt: + continue + data.pre_prompt += pre_prompt_template + + app_data = db.session.query(App) \ + .filter(App.id == data.app_id) \ + .one() + + account_data = db.session.query(Account) \ + .join(TenantAccountJoin, Account.id == TenantAccountJoin.account_id) \ + .filter(TenantAccountJoin.role == 'owner') \ + .filter(TenantAccountJoin.tenant_id == app_data.tenant_id) \ + .one_or_none() + + if not account_data: + continue + + if data.user_input_form is None or data.user_input_form == 'null': + data.user_input_form = json.dumps(user_input_form_template[account_data.interface_language]) + else: + raw_json_data = json.loads(data.user_input_form) + raw_json_data.append(user_input_form_template[account_data.interface_language][0]) + data.user_input_form = json.dumps(raw_json_data) + + # click.secho(f"Updated data {data.id}, pre_prompt: {data.pre_prompt}, user_input_form: {data.user_input_form}", fg='green') + + 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) app.cli.add_command(reset_email) @@ -448,4 +550,5 @@ def register_commands(app): app.cli.add_command(sync_anthropic_hosted_providers) app.cli.add_command(clean_unused_dataset_indexes) app.cli.add_command(create_qdrant_indexes) - app.cli.add_command(update_qdrant_indexes) \ No newline at end of file + app.cli.add_command(update_qdrant_indexes) + app.cli.add_command(update_app_model_configs) \ No newline at end of file diff --git a/api/constants/model_template.py b/api/constants/model_template.py index f8d7e0b74a..95574b83f7 100644 --- a/api/constants/model_template.py +++ b/api/constants/model_template.py @@ -38,7 +38,18 @@ model_templates = { "presence_penalty": 0, "frequency_penalty": 0 } - }) + }), + 'user_input_form': json.dumps([ + { + "paragraph": { + "label": "Query", + "variable": "query", + "required": True, + "default": "" + } + } + ]), + 'pre_prompt': '{{query}}' } }, diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index 866721775b..3e22ca96df 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -39,7 +39,7 @@ class CompletionMessageApi(Resource): parser = reqparse.RequestParser() parser.add_argument('inputs', type=dict, required=True, location='json') - parser.add_argument('query', type=str, location='json') + parser.add_argument('query', type=str, location='json', default='') parser.add_argument('model_config', type=dict, required=True, location='json') parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json') args = parser.parse_args() diff --git a/api/controllers/console/explore/completion.py b/api/controllers/console/explore/completion.py index d48c85a731..b708367258 100644 --- a/api/controllers/console/explore/completion.py +++ b/api/controllers/console/explore/completion.py @@ -31,7 +31,7 @@ class CompletionApi(InstalledAppResource): parser = reqparse.RequestParser() parser.add_argument('inputs', type=dict, required=True, location='json') - parser.add_argument('query', type=str, location='json') + parser.add_argument('query', type=str, location='json', default='') parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json') args = parser.parse_args() diff --git a/api/controllers/service_api/app/completion.py b/api/controllers/service_api/app/completion.py index 2b802dc71c..8a441aa2fa 100644 --- a/api/controllers/service_api/app/completion.py +++ b/api/controllers/service_api/app/completion.py @@ -27,7 +27,7 @@ class CompletionApi(AppApiResource): parser = reqparse.RequestParser() parser.add_argument('inputs', type=dict, required=True, location='json') - parser.add_argument('query', type=str, location='json') + parser.add_argument('query', type=str, location='json', default='') parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json') parser.add_argument('user', type=str, location='json') args = parser.parse_args() diff --git a/api/controllers/web/completion.py b/api/controllers/web/completion.py index 4325362a5b..25744b61af 100644 --- a/api/controllers/web/completion.py +++ b/api/controllers/web/completion.py @@ -29,7 +29,7 @@ class CompletionApi(WebApiResource): parser = reqparse.RequestParser() parser.add_argument('inputs', type=dict, required=True, location='json') - parser.add_argument('query', type=str, location='json') + parser.add_argument('query', type=str, location='json', default='') parser.add_argument('response_mode', type=str, choices=['blocking', 'streaming'], location='json') args = parser.parse_args() diff --git a/api/core/model_providers/models/llm/base.py b/api/core/model_providers/models/llm/base.py index 4093db3387..13b302cce5 100644 --- a/api/core/model_providers/models/llm/base.py +++ b/api/core/model_providers/models/llm/base.py @@ -342,7 +342,7 @@ class BaseLLM(BaseProviderModel): if order == 'context_prompt': prompt += context_prompt_content elif order == 'pre_prompt': - prompt += (pre_prompt_content + '\n\n') if pre_prompt_content else '' + prompt += pre_prompt_content query_prompt = prompt_rules['query_prompt'] if 'query_prompt' in prompt_rules else '{{query}}' diff --git a/api/core/prompt/generate_prompts/baichuan_chat.json b/api/core/prompt/generate_prompts/baichuan_chat.json index 81e7295486..5bf83cd9c7 100644 --- a/api/core/prompt/generate_prompts/baichuan_chat.json +++ b/api/core/prompt/generate_prompts/baichuan_chat.json @@ -8,6 +8,6 @@ "pre_prompt", "histories_prompt" ], - "query_prompt": "用户:{{query}}", + "query_prompt": "\n\n用户:{{query}}", "stops": ["用户:"] } \ No newline at end of file diff --git a/api/core/prompt/generate_prompts/common_chat.json b/api/core/prompt/generate_prompts/common_chat.json index baa000a7a2..c817caf36c 100644 --- a/api/core/prompt/generate_prompts/common_chat.json +++ b/api/core/prompt/generate_prompts/common_chat.json @@ -8,6 +8,6 @@ "pre_prompt", "histories_prompt" ], - "query_prompt": "Human: {{query}}\n\nAssistant: ", + "query_prompt": "\n\nHuman: {{query}}\n\nAssistant: ", "stops": ["\nHuman:", ""] } \ No newline at end of file diff --git a/api/services/app_model_config_service.py b/api/services/app_model_config_service.py index abcb7e6235..18ca399dbd 100644 --- a/api/services/app_model_config_service.py +++ b/api/services/app_model_config_service.py @@ -216,8 +216,8 @@ class AppModelConfigService: variables = [] for item in config["user_input_form"]: key = list(item.keys())[0] - if key not in ["text-input", "select"]: - raise ValueError("Keys in user_input_form list can only be 'text-input' or 'select'") + if key not in ["text-input", "select", "paragraph"]: + raise ValueError("Keys in user_input_form list can only be 'text-input', 'paragraph' or 'select'") form_item = item[key] if 'label' not in form_item: diff --git a/api/services/completion_service.py b/api/services/completion_service.py index 531d803f54..6311d5a01d 100644 --- a/api/services/completion_service.py +++ b/api/services/completion_service.py @@ -34,7 +34,7 @@ class CompletionService: inputs = args['inputs'] query = args['query'] - if not query: + if app_model.mode != 'completion' and not query: raise ValueError('query is required') query = query.replace('\x00', '') @@ -347,8 +347,8 @@ class CompletionService: if value not in options: raise ValueError(f"{variable} in input form must be one of the following: {options}") else: - if 'max_length' in variable: - max_length = variable['max_length'] + if 'max_length' in input_config: + max_length = input_config['max_length'] if len(value) > max_length: raise ValueError(f'{variable} in input form must be less than {max_length} characters') diff --git a/web/app/components/app/configuration/config-var/config-model/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx similarity index 74% rename from web/app/components/app/configuration/config-var/config-model/index.tsx rename to web/app/components/app/configuration/config-var/config-modal/index.tsx index 086721ae4d..b923ce72f2 100644 --- a/web/app/components/app/configuration/config-var/config-model/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -2,6 +2,7 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' import ModalFoot from '../modal-foot' import type { Options } from '../config-select' import ConfigSelect from '../config-select' @@ -11,6 +12,7 @@ import s from './style.module.css' import Toast from '@/app/components/base/toast' import type { PromptVariable } from '@/models/debug' import { getNewVar } from '@/utils/var' +import ConfigContext from '@/context/debug-configuration' import Modal from '@/app/components/base/modal' @@ -28,6 +30,7 @@ const ConfigModal: FC = ({ onClose, onConfirm, }) => { + const { modelConfig } = useContext(ConfigContext) const { t } = useTranslation() const { type, name, key, options, max_length } = payload || getNewVar('') @@ -41,7 +44,7 @@ const ConfigModal: FC = ({ } } - const isStringInput = tempType === 'string' + const isStringInput = tempType === 'string' || tempType === 'paragraph' const title = isStringInput ? t('appDebug.variableConig.maxLength') : t('appDebug.variableConig.options') // string type @@ -93,22 +96,24 @@ const ConfigModal: FC = ({
{t('appDebug.variableConig.fieldType')}
- - + + +
-
-
{title}
- {isStringInput - ? ( - - ) - : ( - - )} -
- + {tempType !== 'paragraph' && ( +
+
{title}
+ {isStringInput + ? ( + + ) + : ( + + )} +
+ )} void } @@ -13,6 +14,11 @@ const ConfigString: FC = ({ value, onChange, }) => { + useEffect(() => { + if (value && value > MAX_LENGTH) + onChange(MAX_LENGTH) + }, [value, MAX_LENGTH]) + return (
= ({ min={1} value={value || ''} onChange={(e) => { - const value = Math.max(1, Math.min(MAX_LENGTH, parseInt(e.target.value))) || 1 + let value = parseInt(e.target.value, 10) + if (value > MAX_LENGTH) + value = MAX_LENGTH + + else if (value < 1) + value = 1 + onChange(value) }} className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index 4e18cb42d2..3e204bb46c 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -8,7 +8,7 @@ import type { Timeout } from 'ahooks/lib/useRequest/src/types' import Panel from '../base/feature-panel' import OperationBtn from '../base/operation-btn' import VarIcon from '../base/icons/var-icon' -import EditModel from './config-model' +import EditModal from './config-modal' import IconTypeIcon from './input-type-icon' import type { IInputTypeIconProps } from './input-type-icon' import s from './style.module.css' @@ -240,7 +240,7 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar )} {isShowEditModal && ( - { ), + paragraph: ( + + + + + + + ), select: ( diff --git a/web/app/components/app/configuration/config-var/select-type-item/index.tsx b/web/app/components/app/configuration/config-var/select-type-item/index.tsx index 632a3301aa..dc4ea1aae4 100644 --- a/web/app/components/app/configuration/config-var/select-type-item/index.tsx +++ b/web/app/components/app/configuration/config-var/select-type-item/index.tsx @@ -1,11 +1,12 @@ 'use client' -import React, { FC } from 'react' +import type { FC } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' import s from './style.module.css' -export interface ISelectTypeItemProps { +export type ISelectTypeItemProps = { type: string selected: boolean onClick: () => void @@ -14,46 +15,75 @@ export interface ISelectTypeItemProps { const Icon = ({ type, selected }: Partial) => { switch (type) { case 'select': - return selected ? ( - - - - - - ) : ( - - - - - - ) + return selected + ? ( + + + + + ) + : ( + + + + + ) + case 'paragraph': + return selected + ? ( + + + + + + + + + + + ) + : ( + + + + + + + + + + + ) case 'string': default: - return selected ? ( - - - - - ) : ( - - ) + return selected + ? ( + + + + ) + : ( + + ) } } const SelectTypeItem: FC = ({ type, selected, - onClick + onClick, }) => { const { t } = useTranslation() const typeName = t(`appDebug.variableConig.${type}`) return (
- +
+ +
{typeName}
) diff --git a/web/app/components/app/configuration/config-var/select-type-item/style.module.css b/web/app/components/app/configuration/config-var/select-type-item/style.module.css index 9f3dc278d2..95856ae77b 100644 --- a/web/app/components/app/configuration/config-var/select-type-item/style.module.css +++ b/web/app/components/app/configuration/config-var/select-type-item/style.module.css @@ -1,9 +1,10 @@ .item { display: flex; + flex-direction: column; + justify-content: center; align-items: center; - height: 32px; - width: 133px; - padding-left: 12px; + height: 58px; + width: 98px; border-radius: 8px; border: 1px solid #EAECF0; box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index 49df642f97..bbfc0499b9 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -297,7 +297,6 @@ const Debug: FC = ({ setChatList([]) }, [controlClearChatMessage]) - const [completionQuery, setCompletionQuery] = useState('') const [completionRes, setCompletionRes] = useState('') const sendTextCompletion = async () => { @@ -309,11 +308,6 @@ const Debug: FC = ({ if (!checkCanSend()) return - if (!completionQuery) { - logError(t('appDebug.errorMessage.queryRequired')) - return false - } - const postDatasets = dataSets.map(({ id }) => ({ dataset: { enabled: true, @@ -342,7 +336,6 @@ const Debug: FC = ({ const data = { inputs, - query: completionQuery, model_config: postModelConfig, } @@ -380,8 +373,6 @@ const Debug: FC = ({
diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 68ddca9c90..528b1347b7 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -6,6 +6,7 @@ import { useContext } from 'use-context-selector' import { usePathname } from 'next/navigation' import produce from 'immer' import { useBoolean } from 'ahooks' +import cn from 'classnames' import Button from '../../base/button' import Loading from '../../base/loading' import type { CompletionParams, Inputs, ModelConfig, MoreLikeThisConfig, PromptConfig, PromptVariable } from '@/models/debug' @@ -24,6 +25,7 @@ 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' const Configuration: FC = () => { const { t } = useTranslation() @@ -193,11 +195,16 @@ const Configuration: FC = () => { }) }, [appId]) + const cannotPublish = mode === AppType.completion && !modelConfig.configs.prompt_template const saveAppConfig = async () => { const modelId = modelConfig.model_id const promptTemplate = modelConfig.configs.prompt_template const promptVariables = modelConfig.configs.prompt_variables + if (cannotPublish) { + notify({ type: 'error', message: t('appDebug.otherError.promptNoBeEmpty'), duration: 3000 }) + return + } const postDatasets = dataSets.map(({ id }) => ({ dataset: { enabled: true, @@ -311,7 +318,7 @@ const Configuration: FC = () => { />
- +
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 ef0925499a..7aa4579f25 100644 --- a/web/app/components/app/configuration/prompt-value-panel/index.tsx +++ b/web/app/components/app/configuration/prompt-value-panel/index.tsx @@ -14,11 +14,10 @@ import Select from '@/app/components/base/select' import { DEFAULT_VALUE_MAX_LEN } from '@/config' import Button from '@/app/components/base/button' import { ChevronDown, ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' +import Tooltip from '@/app/components/base/tooltip-plus' export type IPromptValuePanelProps = { appType: AppType - value?: string - onChange?: (value: string) => void onSend?: () => void } @@ -32,12 +31,10 @@ const starIcon = ( const PromptValuePanel: FC = ({ appType, - value, - onChange, onSend, }) => { const { t } = useTranslation() - const { modelConfig, inputs, setInputs } = useContext(ConfigContext) + const { modelConfig, inputs, setInputs, mode } = useContext(ConfigContext) const [promptPreviewCollapse, setPromptPreviewCollapse] = useState(false) const [userInputFieldCollapse, setUserInputFieldCollapse] = useState(false) const promptTemplate = modelConfig.configs.prompt_template @@ -53,6 +50,19 @@ const PromptValuePanel: FC = ({ return obj })() + const canNotRun = mode === AppType.completion && !modelConfig.configs.prompt_template + const renderRunButton = () => { + return ( + + ) + } const handleInputValueChange = (key: string, value: string) => { if (!(key in promptVariableObj)) return @@ -65,6 +75,14 @@ const PromptValuePanel: FC = ({ setInputs(newInputs) } + const onClear = () => { + const newInputs: Record = {} + promptVariables.forEach((item) => { + newInputs[item.key] = '' + }) + setInputs(newInputs) + } + const promptPreview = (
@@ -125,83 +143,78 @@ const PromptValuePanel: FC = ({
{t('appDebug.inputs.completionVarTip')}
)}
- { - !userInputFieldCollapse && ( - <> - { - promptVariables.length > 0 - ? ( -
- {promptVariables.map(({ key, name, type, options, max_length, required }) => ( -
-
{name || key}
- {type === 'select' - ? ( - { handleInputValueChange(key, e.target.value) }} - maxLength={max_length || DEFAULT_VALUE_MAX_LEN} - /> - )} + {!userInputFieldCollapse && ( + <> + { + promptVariables.length > 0 + ? ( +
+ {promptVariables.map(({ key, name, type, options, max_length, required }) => ( +
+
{name || key}
+ {type === 'select' && ( + { handleInputValueChange(key, e.target.value) }} + maxLength={max_length || DEFAULT_VALUE_MAX_LEN} + /> + )} + {type === 'paragraph' && ( + -
-
-
- {value?.length} -
- -
-
-
+
+
+
+ + + {canNotRun + ? ( + {renderRunButton()} + ) + : renderRunButton()}
) diff --git a/web/app/components/base/tooltip-plus/index.tsx b/web/app/components/base/tooltip-plus/index.tsx new file mode 100644 index 0000000000..6fc791596e --- /dev/null +++ b/web/app/components/base/tooltip-plus/index.tsx @@ -0,0 +1,50 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' +export type TooltipProps = { + position?: 'top' | 'right' | 'bottom' | 'left' + triggerMethod?: 'hover' | 'click' + popupContent: React.ReactNode + children: React.ReactNode +} + +const arrow = ( + +) + +const Tooltip: FC< TooltipProps> = ({ + position = 'top', + triggerMethod = 'hover', + popupContent, + children, +}) => { + const [open, setOpen] = useState(false) + + return ( + + triggerMethod === 'click' && setOpen(v => !v)} + onMouseEnter={() => triggerMethod === 'hover' && setOpen(true)} + onMouseLeave={() => triggerMethod === 'hover' && setOpen(false)} + > + {children} + + +
+ {popupContent} + {arrow} +
+
+
+ ) +} + +export default React.memo(Tooltip) diff --git a/web/app/components/develop/template/template.en.mdx b/web/app/components/develop/template/template.en.mdx index 4816adfcd5..e828667449 100644 --- a/web/app/components/develop/template/template.en.mdx +++ b/web/app/components/develop/template/template.en.mdx @@ -30,9 +30,6 @@ For high-quality text generation, such as articles, summaries, and translations, )} - - User input text content. - - Blocking type, waiting for execution to complete and returning results. (Requests may be interrupted if the process is long) - streaming returns. Implementation of streaming return based on SSE (**[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)**). @@ -44,7 +41,7 @@ For high-quality text generation, such as articles, summaries, and translations, - + ```bash {{ title: 'cURL' }} curl --location --request POST 'https://cloud.langgenius.dev/api/completion-messages' \ @@ -52,7 +49,6 @@ For high-quality text generation, such as articles, summaries, and translations, --header 'Content-Type: application/json' \ --data-raw '{ "inputs": {}, - "query": "Hi", "response_mode": "streaming", "user": "abc-123" }' diff --git a/web/app/components/develop/template/template.zh.mdx b/web/app/components/develop/template/template.zh.mdx index 74aa168f2d..8a542009fb 100644 --- a/web/app/components/develop/template/template.zh.mdx +++ b/web/app/components/develop/template/template.zh.mdx @@ -30,9 +30,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' )} - - 用户输入的文本正文。 - - blocking 阻塞型,等待执行完毕后返回结果。(请求若流程较长可能会被中断) - streaming 流式返回。基于 SSE(**[Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)**)实现流式返回。 @@ -44,7 +41,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - + ```bash {{ title: 'cURL' }} curl --location --request POST 'https://cloud.langgenius.dev/api/completion-messages' \ @@ -52,7 +49,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' --header 'Content-Type: application/json' \ --data-raw '{ "inputs": {}, - "query": "Hi", "response_mode": "streaming", "user": "abc-123" }' diff --git a/web/app/components/share/chat/welcome/index.tsx b/web/app/components/share/chat/welcome/index.tsx index 15b7855e09..1ad54d6cea 100644 --- a/web/app/components/share/chat/welcome/index.tsx +++ b/web/app/components/share/chat/welcome/index.tsx @@ -97,10 +97,10 @@ const Welcome: FC = ({ return (
{promptConfig.prompt_variables.map(item => ( -
- +
+ {item.type === 'select' - ? ( + && ( { setInputs({ ...inputs, [item.key]: e.target.value }) }} - className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'} - maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN} - /> )} + {item.type === 'string' && ( + { setInputs({ ...inputs, [item.key]: e.target.value }) }} + className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'} + maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN} + /> + )} + {item.type === 'paragraph' && ( + -
-
-
- {query?.length} -
- -
+
+
+ +
diff --git a/web/config/index.ts b/web/config/index.ts index 991de2e6d3..c81e3e44f5 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -91,9 +91,14 @@ export const TONE_LIST = [ }, ] +export const getMaxToken = (modelId: string) => { + return (modelId === 'gpt-4' || modelId === 'gpt-3.5-turbo-16k') ? 8000 : 4000 +} + export const LOCALE_COOKIE_NAME = 'locale' export const DEFAULT_VALUE_MAX_LEN = 48 +export const DEFAULT_PARAGRAPH_VALUE_MAX_LEN = 1000 export const zhRegex = /^[\u4E00-\u9FA5]$/m export const emojiRegex = /^[\uD800-\uDBFF][\uDC00-\uDFFF]$/m diff --git a/web/i18n/lang/app-debug.en.ts b/web/i18n/lang/app-debug.en.ts index fe616f5319..9ac5fc86d3 100644 --- a/web/i18n/lang/app-debug.en.ts +++ b/web/i18n/lang/app-debug.en.ts @@ -128,11 +128,15 @@ const translation = { notStartWithNumber: 'Variable key: {{key}} can not start with a number', keyAlreadyExists: 'Variable key: :{{key}} already exists', }, + otherError: { + promptNoBeEmpty: 'Prefix prompt can not be empty', + }, variableConig: { modalTitle: 'Field settings', description: 'Setting for variable {{varName}}', fieldType: 'Field type', - string: 'Text', + string: 'Short Text', + paragraph: 'Paragraph', select: 'Select', notSet: 'Not set, try typing {{input}} in the prefix prompt', stringTitle: 'Form text box options', diff --git a/web/i18n/lang/app-debug.zh.ts b/web/i18n/lang/app-debug.zh.ts index 49a1608f94..e974af66e6 100644 --- a/web/i18n/lang/app-debug.zh.ts +++ b/web/i18n/lang/app-debug.zh.ts @@ -124,11 +124,15 @@ const translation = { notStartWithNumber: '变量: {{key}} 不能以数字开头', keyAlreadyExists: '变量:{{key}} 已存在', }, + otherError: { + promptNoBeEmpty: '前缀提示词不能为空', + }, variableConig: { modalTitle: '变量设置', description: '设置变量 {{varName}}', fieldType: '字段类型', string: '文本', + paragraph: '段落', select: '下拉选项', notSet: '未设置,在 Prompt 中输入 {{input}} 试试', stringTitle: '文本框设置', diff --git a/web/i18n/lang/share-app.en.ts b/web/i18n/lang/share-app.en.ts index f621bddfa2..fc22af5777 100644 --- a/web/i18n/lang/share-app.en.ts +++ b/web/i18n/lang/share-app.en.ts @@ -58,7 +58,8 @@ const translation = { empty: 'Please input content in the uploaded file.', fileStructNotMatch: 'The uploaded CSV file not match the struct.', emptyLine: 'Row {{rowIndex}} is empty', - invalidLine: 'Row {{rowIndex}}: variables value can not be empty', + invalidLine: 'Row {{rowIndex}}: {{varName}} value can not be empty', + moreThanMaxLengthLine: 'Row {{rowIndex}}: {{varName}} value can not be more than {{maxLength}} characters', atLeastOne: 'Please input at least one row in the uploaded file.', }, }, diff --git a/web/i18n/lang/share-app.zh.ts b/web/i18n/lang/share-app.zh.ts index 6ed563b3f7..5db6bf20d9 100644 --- a/web/i18n/lang/share-app.zh.ts +++ b/web/i18n/lang/share-app.zh.ts @@ -31,6 +31,7 @@ const translation = { create: '运行一次', batch: '批量运行', saved: '已保存', + }, savedNoData: { title: '您还没有保存结果!', @@ -54,7 +55,8 @@ const translation = { empty: '上传文件的内容不能为空', fileStructNotMatch: '上传文件的内容与结构不匹配', emptyLine: '第 {{rowIndex}} 行的内容为空', - invalidLine: '第 {{rowIndex}} 行: 变量值必填', + invalidLine: '第 {{rowIndex}} 行: {{varName}}值必填', + moreThanMaxLengthLine: '第 {{rowIndex}} 行: {{varName}}值超过最大长度 {{maxLength}}', atLeastOne: '上传文件的内容不能少于一条', }, }, diff --git a/web/utils/model-config.ts b/web/utils/model-config.ts index 1c7594b9fe..e0f9ee1204 100644 --- a/web/utils/model-config.ts +++ b/web/utils/model-config.ts @@ -1,22 +1,32 @@ -import { UserInputFormItem, } from '@/types/app' -import { PromptVariable } from '@/models/debug' +import type { UserInputFormItem } from '@/types/app' +import type { PromptVariable } from '@/models/debug' export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | null) => { - if (!useInputs) return [] + if (!useInputs) + return [] const promptVariables: PromptVariable[] = [] useInputs.forEach((item: any) => { - const type = item['text-input'] ? 'string' : 'select' - const content = type === 'string' ? item['text-input'] : item['select'] - if (type === 'string') { + const isParagraph = !!item.paragraph + const [type, content] = (() => { + if (isParagraph) + return ['paragraph', item.paragraph] + + if (item['text-input']) + return ['string', item['text-input']] + + return ['select', item.select] + })() + if (type === 'string' || type === 'paragraph') { promptVariables.push({ key: content.variable, name: content.label, required: content.required, - type: 'string', + type, max_length: content.max_length, options: [], }) - } else { + } + else { promptVariables.push({ key: content.variable, name: content.label, @@ -32,29 +42,30 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[]) => { const userInputs: UserInputFormItem[] = [] promptVariables.filter(({ key, name }) => { - if (key && key.trim() && name && name.trim()) { + if (key && key.trim() && name && name.trim()) return true - } + return false }).forEach((item: any) => { - if (item.type === 'string') { + if (item.type === 'string' || item.type === 'paragraph') { userInputs.push({ - 'text-input': { + [item.type === 'string' ? 'text-input' : 'paragraph']: { label: item.name, variable: item.key, - required: item.required === false ? false : true, // default true + required: item.required !== false, // default true max_length: item.max_length, - default: '' + default: '', }, } as any) - } else { + } + else { userInputs.push({ - 'select': { + select: { label: item.name, variable: item.key, - required: item.required === false ? false : true, // default true + required: item.required !== false, // default true options: item.options, - default: '' + default: '', }, } as any) }