Unverified Commit 2d5ad0d2 authored by Joel's avatar Joel Committed by GitHub

feat: support optional query content (#1097)

Co-authored-by: 's avatarGarfield Dai <dai.hai@foxmail.com>
parent 1ade70aa
......@@ -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
......@@ -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}}'
}
},
......
......@@ -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()
......
......@@ -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()
......
......@@ -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()
......
......@@ -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()
......
......@@ -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}}'
......
......@@ -8,6 +8,6 @@
"pre_prompt",
"histories_prompt"
],
"query_prompt": "用户:{{query}}",
"query_prompt": "\n\n用户:{{query}}",
"stops": ["用户:"]
}
\ No newline at end of file
......@@ -8,6 +8,6 @@
"pre_prompt",
"histories_prompt"
],
"query_prompt": "Human: {{query}}\n\nAssistant: ",
"query_prompt": "\n\nHuman: {{query}}\n\nAssistant: ",
"stops": ["\nHuman:", "</histories>"]
}
\ No newline at end of file
......@@ -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:
......
......@@ -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')
......
......@@ -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<IConfigModalProps> = ({
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<IConfigModalProps> = ({
}
}
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<IConfigModalProps> = ({
<div className='mb-2'>
<div className={s.title}>{t('appDebug.variableConig.fieldType')}</div>
<div className='flex space-x-2'>
<SelectTypeItem type='string' selected={isStringInput} onClick={handleTypeChange('string')} />
<SelectTypeItem type='select' selected={!isStringInput} onClick={handleTypeChange('select')} />
<SelectTypeItem type='string' selected={tempType === 'string'} onClick={handleTypeChange('string')} />
<SelectTypeItem type='paragraph' selected={tempType === 'paragraph'} onClick={handleTypeChange('paragraph')} />
<SelectTypeItem type='select' selected={tempType === 'select'} onClick={handleTypeChange('select')} />
</div>
</div>
<div className='mt-6'>
<div className={s.title}>{title}</div>
{isStringInput
? (
<ConfigString value={tempMaxLength} onChange={setTempMaxValue} />
)
: (
<ConfigSelect options={tempOptions} onChange={setTempOptions} />
)}
</div>
{tempType !== 'paragraph' && (
<div className='mt-6'>
<div className={s.title}>{title}</div>
{isStringInput
? (
<ConfigString modelId={modelConfig.model_id} value={tempMaxLength} onChange={setTempMaxValue} />
)
: (
<ConfigSelect options={tempOptions} onChange={setTempOptions} />
)}
</div>
)}
</div>
<ModalFoot
onConfirm={handleConfirm}
......
'use client'
import type { FC } from 'react'
import React from 'react'
import React, { useEffect } from 'react'
export type IConfigStringProps = {
value: number | undefined
modelId: string
onChange: (value: number | undefined) => void
}
......@@ -13,6 +14,11 @@ const ConfigString: FC<IConfigStringProps> = ({
value,
onChange,
}) => {
useEffect(() => {
if (value && value > MAX_LENGTH)
onChange(MAX_LENGTH)
}, [value, MAX_LENGTH])
return (
<div>
<input
......@@ -21,7 +27,13 @@ const ConfigString: FC<IConfigStringProps> = ({
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"
......
......@@ -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<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
)}
{isShowEditModal && (
<EditModel
<EditModal
payload={currItem as PromptVariable}
isShow={isShowEditModal}
onClose={hideEditModal}
......
......@@ -13,6 +13,14 @@ const IconMap = (type: IInputTypeIconProps['type']) => {
<path fillRule="evenodd" clipRule="evenodd" d="M3.52593 0.166672H8.47411C8.94367 0.166665 9.33123 0.166659 9.64692 0.192452C9.97481 0.219242 10.2762 0.276738 10.5593 0.420991C10.9984 0.644695 11.3553 1.00165 11.579 1.44069C11.7233 1.72381 11.7808 2.02522 11.8076 2.35311C11.8334 2.6688 11.8334 3.05634 11.8334 3.5259V8.47411C11.8334 8.94367 11.8334 9.33121 11.8076 9.6469C11.7808 9.97479 11.7233 10.2762 11.579 10.5593C11.3553 10.9984 10.9984 11.3553 10.5593 11.579C10.2762 11.7233 9.97481 11.7808 9.64692 11.8076C9.33123 11.8334 8.94369 11.8333 8.47413 11.8333H3.52592C3.05636 11.8333 2.66882 11.8334 2.35312 11.8076C2.02523 11.7808 1.72382 11.7233 1.44071 11.579C1.00167 11.3553 0.644711 10.9984 0.421006 10.5593C0.276753 10.2762 0.219257 9.97479 0.192468 9.6469C0.166674 9.33121 0.16668 8.94366 0.166687 8.4741V3.52591C0.16668 3.05635 0.166674 2.6688 0.192468 2.35311C0.219257 2.02522 0.276753 1.72381 0.421006 1.44069C0.644711 1.00165 1.00167 0.644695 1.44071 0.420991C1.72382 0.276738 2.02523 0.219242 2.35312 0.192452C2.66882 0.166659 3.05637 0.166665 3.52593 0.166672ZM3.08335 3.08334C3.08335 2.76117 3.34452 2.50001 3.66669 2.50001H8.33335C8.65552 2.50001 8.91669 2.76117 8.91669 3.08334C8.91669 3.4055 8.65552 3.66667 8.33335 3.66667H6.58335V8.91667C6.58335 9.23884 6.32219 9.5 6.00002 9.5C5.67785 9.5 5.41669 9.23884 5.41669 8.91667V3.66667H3.66669C3.34452 3.66667 3.08335 3.4055 3.08335 3.08334Z" fill="#98A2B3" />
</svg>
),
paragraph: (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M1.16669 5.83329C1.16669 5.51113 1.42785 5.24996 1.75002 5.24996H9.33335C9.65552 5.24996 9.91669 5.51113 9.91669 5.83329C9.91669 6.15546 9.65552 6.41663 9.33335 6.41663H1.75002C1.42785 6.41663 1.16669 6.15546 1.16669 5.83329Z" fill="#98A2B3"/>
<path fillRule="evenodd" clipRule="evenodd" d="M1.16669 3.49996C1.16669 3.17779 1.42785 2.91663 1.75002 2.91663H11.6667C11.9889 2.91663 12.25 3.17779 12.25 3.49996C12.25 3.82213 11.9889 4.08329 11.6667 4.08329H1.75002C1.42785 4.08329 1.16669 3.82213 1.16669 3.49996Z" fill="#98A2B3"/>
<path fillRule="evenodd" clipRule="evenodd" d="M1.16669 8.16663C1.16669 7.84446 1.42785 7.58329 1.75002 7.58329H11.6667C11.9889 7.58329 12.25 7.84446 12.25 8.16663C12.25 8.48879 11.9889 8.74996 11.6667 8.74996H1.75002C1.42785 8.74996 1.16669 8.48879 1.16669 8.16663Z" fill="#98A2B3"/>
<path fillRule="evenodd" clipRule="evenodd" d="M1.16669 10.5C1.16669 10.1778 1.42785 9.91663 1.75002 9.91663H9.33335C9.65552 9.91663 9.91669 10.1778 9.91669 10.5C9.91669 10.8221 9.65552 11.0833 9.33335 11.0833H1.75002C1.42785 11.0833 1.16669 10.8221 1.16669 10.5Z" fill="#98A2B3"/>
</svg>
),
select: (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M7.48913 4.08334H3.01083C2.70334 4.08333 2.43804 4.08333 2.21955 4.10118C1.98893 4.12002 1.75955 4.16162 1.53883 4.27408C1.20955 4.44186 0.941831 4.70958 0.774053 5.03886C0.66159 5.25958 0.619989 5.48896 0.601147 5.71958C0.583295 5.93807 0.583304 6.20334 0.583313 6.51084V10.9892C0.583304 11.2967 0.583295 11.5619 0.601147 11.7804C0.619989 12.0111 0.66159 12.2404 0.774053 12.4612C0.941831 12.7904 1.20955 13.0582 1.53883 13.2259C1.75955 13.3384 1.98893 13.38 2.21955 13.3988C2.43803 13.4167 2.70329 13.4167 3.01077 13.4167H7.48912C7.7966 13.4167 8.06193 13.4167 8.28041 13.3988C8.51103 13.38 8.74041 13.3384 8.96113 13.2259C9.29041 13.0582 9.55813 12.7904 9.72591 12.4612C9.83837 12.2404 9.87997 12.0111 9.89882 11.7804C9.91667 11.5619 9.91666 11.2967 9.91665 10.9892V6.51087C9.91666 6.20336 9.91667 5.93808 9.89882 5.71958C9.87997 5.48896 9.83837 5.25958 9.72591 5.03886C9.55813 4.70958 9.29041 4.44186 8.96113 4.27408C8.74041 4.16162 8.51103 4.12002 8.28041 4.10118C8.06192 4.08333 7.79663 4.08333 7.48913 4.08334ZM7.70413 7.70416C7.93193 7.47635 7.93193 7.107 7.70413 6.8792C7.47632 6.65139 7.10697 6.65139 6.87917 6.8792L4.66665 9.09172L3.91246 8.33753C3.68465 8.10973 3.31531 8.10973 3.0875 8.33753C2.8597 8.56534 2.8597 8.93468 3.0875 9.16249L4.25417 10.3292C4.48197 10.557 4.85132 10.557 5.07913 10.3292L7.70413 7.70416Z" fill="#98A2B3" />
......
.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);
......
......@@ -297,7 +297,6 @@ const Debug: FC<IDebug> = ({
setChatList([])
}, [controlClearChatMessage])
const [completionQuery, setCompletionQuery] = useState('')
const [completionRes, setCompletionRes] = useState('')
const sendTextCompletion = async () => {
......@@ -309,11 +308,6 @@ const Debug: FC<IDebug> = ({
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<IDebug> = ({
const data = {
inputs,
query: completionQuery,
model_config: postModelConfig,
}
......@@ -380,8 +373,6 @@ const Debug: FC<IDebug> = ({
</div>
<PromptValuePanel
appType={mode as AppType}
value={completionQuery}
onChange={setCompletionQuery}
onSend={sendTextCompletion}
/>
</div>
......
......@@ -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 = () => {
/>
<div className='mx-3 w-[1px] h-[14px] bg-gray-200'></div>
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
<Button type='primary' onClick={saveAppConfig} className='shrink-0 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.applyConfig')}</Button>
<Button type='primary' onClick={saveAppConfig} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
</div>
</div>
<div className='flex grow h-[200px]'>
......
......@@ -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<IPromptValuePanelProps> = ({
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<IPromptValuePanelProps> = ({
return obj
})()
const canNotRun = mode === AppType.completion && !modelConfig.configs.prompt_template
const renderRunButton = () => {
return (
<Button
type="primary"
disabled={canNotRun}
onClick={() => onSend && onSend()}
className="w-[80px] !h-8">
<PlayIcon className="shrink-0 w-4 h-4 mr-1" aria-hidden="true" />
<span className='uppercase text-[13px]'>{t('appDebug.inputs.run')}</span>
</Button>
)
}
const handleInputValueChange = (key: string, value: string) => {
if (!(key in promptVariableObj))
return
......@@ -65,6 +75,14 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
setInputs(newInputs)
}
const onClear = () => {
const newInputs: Record<string, any> = {}
promptVariables.forEach((item) => {
newInputs[item.key] = ''
})
setInputs(newInputs)
}
const promptPreview = (
<div className='py-3 rounded-t-xl bg-indigo-25'>
<div className="px-4">
......@@ -125,83 +143,78 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
<div className="mt-1 text-xs leading-normal text-gray-500">{t('appDebug.inputs.completionVarTip')}</div>
)}
</div>
{
!userInputFieldCollapse && (
<>
{
promptVariables.length > 0
? (
<div className="space-y-3 ">
{promptVariables.map(({ key, name, type, options, max_length, required }) => (
<div key={key} className="flex items-center justify-between">
<div className="mr-1 shrink-0 w-[120px] text-sm text-gray-900 break-all">{name || key}</div>
{type === 'select'
? (
<Select
className='w-full'
defaultValue={inputs[key] as string}
onSelect={(i) => { handleInputValueChange(key, i.value as string) }}
items={(options || []).map(i => ({ name: i, value: i }))}
allowSearch={false}
bgClassName='bg-gray-50'
overlayClassName='z-[11]'
/>
)
: (
<input
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"
placeholder={`${name}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
type="text"
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
</div>
))}
</div>
)
: (
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noVar')}</div>
)
}
</>
)
{!userInputFieldCollapse && (
<>
{
promptVariables.length > 0
? (
<div className="space-y-3 ">
{promptVariables.map(({ key, name, type, options, max_length, required }) => (
<div key={key} className="flex justify-between">
<div className="mr-1 pt-2 shrink-0 w-[120px] text-sm text-gray-900">{name || key}</div>
{type === 'select' && (
<Select
className='w-full'
defaultValue={inputs[key] as string}
onSelect={(i) => { handleInputValueChange(key, i.value as string) }}
items={(options || []).map(i => ({ name: i, value: i }))}
allowSearch={false}
bgClassName='bg-gray-50'
/>
)
}
{type === 'string' && (
<input
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"
placeholder={`${name}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
type="text"
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
{type === 'paragraph' && (
<textarea
className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-[120px] bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
placeholder={`${name}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
/>
)}
</div>
))}
</div>
)
: (
<div className='text-xs text-gray-500'>{t('appDebug.inputs.noVar')}</div>
)
}
</>
)
}
</div>
{
appType === AppType.completion && (
<div className='px-4'>
<div className="mt-3 border-b border-gray-100"></div>
<div className="mt-4">
<div>
<div className="text-[13px] text-gray-900 font-medium">{t('appDebug.inputs.queryTitle')}</div>
<div className="mt-2 mb-4 overflow-hidden border border-gray-200 rounded-lg grow bg-gray-50 ">
<div className="px-4 py-2 rounded-t-lg bg-gray-50">
<textarea
rows={4}
className="w-full px-0 text-sm text-gray-900 border-0 bg-gray-50 focus:outline-none placeholder:bg-gray-50"
placeholder={t('appDebug.inputs.queryPlaceholder') as string}
value={value}
onChange={e => onChange && onChange(e.target.value)}
></textarea>
</div>
<div className="flex items-center justify-between px-3 py-2 bg-gray-50">
<div className="flex pl-0 space-x-1 sm:pl-2">
<span className="bg-gray-100 text-gray-500 text-xs font-medium mr-2 px-2.5 py-0.5 rounded cursor-pointer">{value?.length}</span>
</div>
<Button
type="primary"
onClick={() => onSend && onSend()}
className="w-[80px] !h-8">
<PlayIcon className="shrink-0 w-4 h-4 mr-1" aria-hidden="true" />
<span className='uppercase text-[13px]'>{t('appDebug.inputs.run')}</span>
</Button>
</div>
</div>
</div>
<div>
<div className="mt-5 border-b border-gray-100"></div>
<div className="flex justify-between mt-4 px-4">
<Button
className='!h-8 !p-3'
onClick={onClear}
disabled={false}
>
<span className='text-[13px]'>{t('common.operation.clear')}</span>
</Button>
{canNotRun
? (<Tooltip
popupContent={t('appDebug.otherError.promptNoBeEmpty')}
>
{renderRunButton()}
</Tooltip>)
: renderRunButton()}
</div>
</div>
)
......
'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 = (
<svg className="absolute text-white h-2 w-full left-0 top-full" x="0px" y="0px" viewBox="0 0 255 255"><polygon className="fill-current" points="0,0 127.5,127.5 255,0"></polygon></svg>
)
const Tooltip: FC< TooltipProps> = ({
position = 'top',
triggerMethod = 'hover',
popupContent,
children,
}) => {
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement={position}
offset={10}
>
<PortalToFollowElemTrigger
onClick={() => triggerMethod === 'click' && setOpen(v => !v)}
onMouseEnter={() => triggerMethod === 'hover' && setOpen(true)}
onMouseLeave={() => triggerMethod === 'hover' && setOpen(false)}
>
{children}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent
className="z-[999]"
>
<div className='relative px-3 py-2 text-xs font-normal text-gray-700 bg-white rounded-md shadow-lg'>
{popupContent}
{arrow}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default React.memo(Tooltip)
......@@ -30,9 +30,6 @@ For high-quality text generation, such as articles, summaries, and translations,
)}
</ul>
</Property>
<Property name='query' type='string' key='query'>
User input text content.
</Property>
<Property name='response_mode' type='string' key='response_mode'>
- 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,
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/completion-messages" targetCode={`curl --location --request POST '${props.appDetail.api_base_url}/completion-messages' \\\n--header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": ${JSON.stringify(props.inputs)},\n "query": "Hi",\n "response_mode": "streaming"\n "user": "abc-123"\n}'\n`}>
<CodeGroup title="Request" tag="POST" label="/completion-messages" targetCode={`curl --location --request POST '${props.appDetail.api_base_url}/completion-messages' \\\n--header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": ${JSON.stringify(props.inputs)},\n "response_mode": "streaming"\n "user": "abc-123"\n}'\n`}>
```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"
}'
......
......@@ -30,9 +30,6 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
)}
</ul>
</Property>
<Property name='query' type='string' key='query'>
用户输入的文本正文。
</Property>
<Property name='response_mode' type='string' key='response_mode'>
- 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'
</Col>
<Col sticky>
<CodeGroup title="Request" tag="POST" label="/completion-messages" targetCode={`curl --location --request POST '${props.appDetail.api_base_url}/completion-messages' \\\n--header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": ${JSON.stringify(props.inputs)},\n "query": "Hi",\n "response_mode": "streaming",\n "user": "abc-123"\n}'\n`}>
<CodeGroup title="Request" tag="POST" label="/completion-messages" targetCode={`curl --location --request POST '${props.appDetail.api_base_url}/completion-messages' \\\n--header 'Authorization: Bearer ENTER-YOUR-SECRET-KEY' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "inputs": ${JSON.stringify(props.inputs)},\n "response_mode": "streaming",\n "user": "abc-123"\n}'\n`}>
```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"
}'
......
......@@ -97,10 +97,10 @@ const Welcome: FC<IWelcomeProps> = ({
return (
<div className='space-y-3'>
{promptConfig.prompt_variables.map(item => (
<div className='tablet:flex tablet:!h-9 mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
<label className={`flex-shrink-0 flex items-center mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
<div className='tablet:flex items-start mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
<label className={`flex-shrink-0 flex items-center tablet:leading-9 mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
{item.type === 'select'
? (
&& (
<Select
className='w-full'
defaultValue={inputs?.[item.key]}
......@@ -109,16 +109,24 @@ const Welcome: FC<IWelcomeProps> = ({
allowSearch={false}
bgClassName='bg-gray-50'
/>
)
: (
<input
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs?.[item.key] || ''}
onChange={(e) => { 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' && (
<input
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs?.[item.key] || ''}
onChange={(e) => { 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' && (
<textarea
className="w-full h-[104px] flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50"
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs?.[item.key] || ''}
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
/>
)}
</div>
))}
</div>
......
......@@ -97,10 +97,10 @@ const Welcome: FC<IWelcomeProps> = ({
return (
<div className='space-y-3'>
{promptConfig.prompt_variables.map(item => (
<div className='tablet:flex tablet:!h-9 mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
<label className={`flex-shrink-0 flex items-center mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
<div className='tablet:flex items-start mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
<label className={`flex-shrink-0 flex items-center tablet:leading-9 mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
{item.type === 'select'
? (
&& (
<Select
className='w-full'
defaultValue={inputs?.[item.key]}
......@@ -110,15 +110,24 @@ const Welcome: FC<IWelcomeProps> = ({
bgClassName='bg-gray-50'
/>
)
: (
<input
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs?.[item.key] || ''}
onChange={(e) => { 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' && (
<input
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs?.[item.key] || ''}
onChange={(e) => { 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' && (
<textarea
className="w-full h-[104px] flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50"
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs?.[item.key] || ''}
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
/>
)}
</div>
))}
</div>
......
......@@ -23,8 +23,9 @@ import { userInputsFormToPromptVariables } from '@/utils/model-config'
import Res from '@/app/components/share/text-generation/result'
import SavedItems from '@/app/components/app/text-generate/saved-items'
import type { InstalledApp } from '@/models/explore'
import { appDefaultIconBackground } from '@/config'
import { DEFAULT_VALUE_MAX_LEN, appDefaultIconBackground } from '@/config'
import Toast from '@/app/components/base/toast'
const PARALLEL_LIMIT = 5
enum TaskStatus {
pending = 'pending',
......@@ -34,7 +35,6 @@ enum TaskStatus {
type TaskParam = {
inputs: Record<string, any>
query: string
}
type Task = {
......@@ -65,7 +65,6 @@ const TextGeneration: FC<IMainProps> = ({
const [isCallBatchAPI, setIsCallBatchAPI] = useState(false)
const isInBatchTab = currTab === 'batch'
const [inputs, setInputs] = useState<Record<string, any>>({})
const [query, setQuery] = useState('') // run once query content
const [appId, setAppId] = useState<string>('')
const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null)
const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null)
......@@ -111,11 +110,10 @@ const TextGeneration: FC<IMainProps> = ({
return {}
const batchCompletionResLatest = getBatchCompletionRes()
const res: Record<string, string> = {}
const { inputs, query } = task.params
const { inputs } = task.params
promptConfig?.prompt_variables.forEach((v) => {
res[v.name] = inputs[v.key]
})
res[t('share.generation.queryTitle')] = query
res[t('share.generation.completionResult')] = batchCompletionResLatest[task.id]
return res
})
......@@ -135,9 +133,6 @@ const TextGeneration: FC<IMainProps> = ({
isMapVarName = false
})
if (headerData[varLen] !== t('share.generation.queryTitle'))
isMapVarName = false
if (!isMapVarName) {
notify({ type: 'error', message: t('share.generation.errorMsg.fileStructNotMatch') })
return false
......@@ -180,6 +175,8 @@ const TextGeneration: FC<IMainProps> = ({
}
let errorRowIndex = 0
let requiredVarName = ''
let moreThanMaxLengthVarName = ''
let maxLength = 0
payloadData.forEach((item, index) => {
if (errorRowIndex !== 0)
return
......@@ -187,6 +184,15 @@ const TextGeneration: FC<IMainProps> = ({
promptConfig?.prompt_variables.forEach((varItem, varIndex) => {
if (errorRowIndex !== 0)
return
if (varItem.type === 'string') {
const maxLen = varItem.max_length || DEFAULT_VALUE_MAX_LEN
if (item[varIndex].length > maxLen) {
moreThanMaxLengthVarName = varItem.name
maxLength = maxLen
errorRowIndex = index + 1
return
}
}
if (varItem.required === false)
return
......@@ -195,18 +201,15 @@ const TextGeneration: FC<IMainProps> = ({
errorRowIndex = index + 1
}
})
if (errorRowIndex !== 0)
return
if (item[varLen] === '') {
requiredVarName = t('share.generation.queryTitle')
errorRowIndex = index + 1
}
})
if (errorRowIndex !== 0) {
notify({ type: 'error', message: t('share.generation.errorMsg.invalidLine', { rowIndex: errorRowIndex + 1, varName: requiredVarName }) })
if (requiredVarName)
notify({ type: 'error', message: t('share.generation.errorMsg.invalidLine', { rowIndex: errorRowIndex + 1, varName: requiredVarName }) })
if (moreThanMaxLengthVarName)
notify({ type: 'error', message: t('share.generation.errorMsg.moreThanMaxLengthLine', { rowIndex: errorRowIndex + 1, varName: moreThanMaxLengthVarName, maxLength }) })
return false
}
return true
......@@ -234,7 +237,6 @@ const TextGeneration: FC<IMainProps> = ({
status: i < PARALLEL_LIMIT ? TaskStatus.running : TaskStatus.pending,
params: {
inputs,
query: item[varLen],
},
}
})
......@@ -334,7 +336,6 @@ const TextGeneration: FC<IMainProps> = ({
promptConfig={promptConfig}
moreLikeThisEnabled={!!moreLikeThisConfig?.enabled}
inputs={isCallBatchAPI ? (task as Task).params.inputs : inputs}
query={isCallBatchAPI ? (task as Task).params.query : query}
controlSend={controlSend}
controlStopResponding={controlStopResponding}
onShowRes={showResSidebar}
......@@ -379,7 +380,6 @@ const TextGeneration: FC<IMainProps> = ({
</div>
)}
</div>
</div>
<div className='grow overflow-y-auto'>
......@@ -459,8 +459,6 @@ const TextGeneration: FC<IMainProps> = ({
inputs={inputs}
onInputsChange={setInputs}
promptConfig={promptConfig}
query={query}
onQueryChange={setQuery}
onSend={handleSend}
/>
</div>
......
......@@ -21,7 +21,6 @@ export type IResultProps = {
promptConfig: PromptConfig | null
moreLikeThisEnabled: boolean
inputs: Record<string, any>
query: string
controlSend?: number
controlStopResponding?: number
onShowRes: () => void
......@@ -39,7 +38,6 @@ const Result: FC<IResultProps> = ({
promptConfig,
moreLikeThisEnabled,
inputs,
query,
controlSend,
controlStopResponding,
onShowRes,
......@@ -109,14 +107,8 @@ const Result: FC<IResultProps> = ({
if (!checkCanSend())
return
if (!query) {
logError(t('appDebug.errorMessage.queryRequired'))
return false
}
const data = {
inputs,
query,
}
setMessageId(null)
......
......@@ -16,7 +16,7 @@ const CSVDownload: FC<ICSVDownloadProps> = ({
}) => {
const { t } = useTranslation()
const { CSVDownloader, Type } = useCSVDownloader()
const addQueryContentVars = [...vars, { name: t('share.generation.queryTitle') }]
const addQueryContentVars = [...vars]
const template = (() => {
const res: Record<string, string> = {}
addQueryContentVars.forEach((item) => {
......
......@@ -15,20 +15,24 @@ export type IRunOnceProps = {
promptConfig: PromptConfig
inputs: Record<string, any>
onInputsChange: (inputs: Record<string, any>) => void
query: string
onQueryChange: (query: string) => void
onSend: () => void
}
const RunOnce: FC<IRunOnceProps> = ({
promptConfig,
inputs,
onInputsChange,
query,
onQueryChange,
onSend,
}) => {
const { t } = useTranslation()
const onClear = () => {
const newInputs: Record<string, any> = {}
promptConfig.prompt_variables.forEach((item) => {
newInputs[item.key] = ''
})
onInputsChange(newInputs)
}
return (
<div className="">
<section>
......@@ -38,61 +42,58 @@ const RunOnce: FC<IRunOnceProps> = ({
<div className='w-full mt-4' key={item.key}>
<label className='text-gray-900 text-sm font-medium'>{item.name}</label>
<div className='mt-2'>
{item.type === 'select'
? (
<Select
className='w-full'
defaultValue={inputs[item.key]}
onSelect={(i) => { onInputsChange({ ...inputs, [item.key]: i.value }) }}
items={(item.options || []).map(i => ({ name: i, value: i }))}
allowSearch={false}
bgClassName='bg-gray-50'
/>
)
: (
<input
type="text"
className="block w-full p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 "
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs[item.key]}
onChange={(e) => { onInputsChange({ ...inputs, [item.key]: e.target.value }) }}
maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
{item.type === 'select' && (
<Select
className='w-full'
defaultValue={inputs[item.key]}
onSelect={(i) => { onInputsChange({ ...inputs, [item.key]: i.value }) }}
items={(item.options || []).map(i => ({ name: i, value: i }))}
allowSearch={false}
bgClassName='bg-gray-50'
/>
)}
{item.type === 'string' && (
<input
type="text"
className="block w-full p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 "
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs[item.key]}
onChange={(e) => { onInputsChange({ ...inputs, [item.key]: e.target.value }) }}
maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
{item.type === 'paragraph' && (
<textarea
className="block w-full h-[104px] p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 "
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs[item.key]}
onChange={(e) => { onInputsChange({ ...inputs, [item.key]: e.target.value }) }}
/>
)}
</div>
</div>
))}
{promptConfig.prompt_variables.length > 0 && (
<div className='mt-6 h-[1px] bg-gray-100'></div>
<div className='mt-4 h-[1px] bg-gray-100'></div>
)}
<div className='w-full mt-5'>
<label className='text-gray-900 text-sm font-medium'>{t('share.generation.queryTitle')}</label>
<div className="mt-2 overflow-hidden rounded-lg bg-gray-50 ">
<div className="px-4 py-2 bg-gray-50 rounded-t-lg">
<textarea
value={query}
onChange={(e) => { onQueryChange(e.target.value) }}
rows={4}
className="w-full px-0 text-sm text-gray-900 border-0 bg-gray-50 focus:outline-none placeholder:bg-gray-50"
placeholder={t('share.generation.queryPlaceholder') as string}
required
>
</textarea>
</div>
<div className="flex items-center justify-between px-3 py-2">
<div className="flex pl-0 space-x-1 sm:pl-2">
<span className="bg-gray-100 text-gray-500 text-xs font-medium mr-2 px-2.5 py-0.5 rounded cursor-pointer">{query?.length}</span>
</div>
<Button
type="primary"
className='!h-8 !pl-3 !pr-4'
onClick={onSend}
disabled={!query || query === ''}
>
<PlayIcon className="shrink-0 w-4 h-4 mr-1" aria-hidden="true" />
<span className='uppercase text-[13px]'>{t('share.generation.run')}</span>
</Button>
</div>
<div className='w-full mt-4'>
<div className="flex items-center justify-between">
<Button
className='!h-8 !p-3'
onClick={onClear}
disabled={false}
>
<span className='text-[13px]'>{t('common.operation.clear')}</span>
</Button>
<Button
type="primary"
className='!h-8 !pl-3 !pr-4'
onClick={onSend}
disabled={false}
>
<PlayIcon className="shrink-0 w-4 h-4 mr-1" aria-hidden="true" />
<span className='text-[13px]'>{t('share.generation.run')}</span>
</Button>
</div>
</div>
</form>
......
......@@ -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
......
......@@ -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',
......
......@@ -124,11 +124,15 @@ const translation = {
notStartWithNumber: '变量: {{key}} 不能以数字开头',
keyAlreadyExists: '变量:{{key}} 已存在',
},
otherError: {
promptNoBeEmpty: '前缀提示词不能为空',
},
variableConig: {
modalTitle: '变量设置',
description: '设置变量 {{varName}}',
fieldType: '字段类型',
string: '文本',
paragraph: '段落',
select: '下拉选项',
notSet: '未设置,在 Prompt 中输入 {{input}} 试试',
stringTitle: '文本框设置',
......
......@@ -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.',
},
},
......
......@@ -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: '上传文件的内容不能少于一条',
},
},
......
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)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment