Merge branch 'develop' into feature/workflow_import_zy

This commit is contained in:
yingzhao
2026-03-03 10:16:59 +08:00
committed by GitHub
77 changed files with 3159 additions and 847 deletions

View File

@@ -12,6 +12,7 @@ import dayjs from 'dayjs'
import type { ApiKey, ApiKeyModalRef } from '../types';
import RbModal from '@/components/RbModal'
import { createApiKey, updateApiKey } from '@/api/apiKey';
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -78,7 +79,7 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
form.validateFields()
.then((values) => {
const { memory, rag, expires_at, ...rest } = values
let scopes = []
const scopes = []
if (memory) {
scopes.push('memory')
@@ -130,7 +131,11 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
<FormItem
name="name"
label={t('apiKey.name')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
@@ -138,6 +143,7 @@ const ApiKeyModal = forwardRef<ApiKeyModalRef, CreateModalProps>(({
<FormItem
name="description"
label={t('apiKey.description')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('common.pleaseEnter')} rows={3} />
</FormItem>

View File

@@ -169,8 +169,8 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
getApplicationConfig(id as string).then(res => {
const response = res as Config
const { skills, variables } = response
let allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : []
let allTools = Array.isArray(response.tools) ? response.tools : []
const allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : []
const allTools = Array.isArray(response.tools) ? response.tools : []
const memoryContent = response.memory?.memory_config_id
const parsedMemoryContent = memoryContent === null || memoryContent === ''
? undefined
@@ -431,7 +431,11 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
</Button>
</div>
<Form.Item name="system_prompt" className="rb:mb-0!">
<Form.Item
name="system_prompt"
className="rb:mb-0!"
rules={[{ max: 10000 }]}
>
<Input.TextArea
placeholder={t('application.promptPlaceholder')}
styles={{

View File

@@ -29,7 +29,7 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
const { t } = useTranslation();
const activeMethods = ['POST'];
const { message, modal } = App.useApp()
const copyContent = window.location.origin + '/v1/chat'
const copyContent = window.location.origin + '/v1/app/chat'
const apiKeyModalRef = useRef<ApiKeyModalRef>(null);
const apiKeyConfigModalRef = useRef<ApiKeyConfigModalRef>(null);
const [apiKeyList, setApiKeyList] = useState<ApiKey[]>([])

View File

@@ -21,6 +21,7 @@ import WorkflowIcon from '@/assets/images/application/workflow.svg'
import type { ApplicationModalData, ApplicationModalRef, Application } from '../types'
import RbModal from '@/components/RbModal'
import { addApplication, updateApplication } from '@/api/application'
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -131,13 +132,18 @@ const ApplicationModal = forwardRef<ApplicationModalRef, ApplicationModalProps>(
<FormItem
name="name"
label={t('application.applicationName')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
<FormItem
name="description"
label={t('application.description')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('common.enter')} />
</FormItem>

View File

@@ -152,7 +152,11 @@ const MemberModal = forwardRef<MemberModalRef, MemberModalProps>(({
<FormItem
name="email"
label={t('member.email')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ type: 'email' },
{ max: 100 },
]}
>
<Input placeholder={t('common.enterPlaceholder', { title: t('member.email') })} disabled={!!editingUser} />
</FormItem>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:30:11
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-09 21:04:14
* @Last Modified time: 2026-03-02 11:41:12
*/
/**
* Result Component
@@ -91,7 +91,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
setDeduplication({...initObj} as ModuleItem)
setTestResult({} as TestResult)
const handleStreamMessage = (list: SSEMessage[]) => {
list.forEach((data: AnyObject) => {
switch(data.event) {
case 'text_preprocessing': // Start text preprocessing
@@ -104,7 +104,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
case 'text_preprocessing_result': // Text preprocessing in progress
setTextPreprocessing(prev => ({
...prev,
data: [...prev.data, data.data?.data]
data: [...prev.data, data.data?.deleted_messages ? { deleted_messages: data.data?.deleted_messages } : data.data?.data],
}))
break
case 'text_preprocessing_complete': // Text preprocessing complete
@@ -193,9 +193,9 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
dialogue_text: t('memoryExtractionEngine.exampleText'),
custom_text: runForm.getFieldValue('custom_text')
}, handleStreamMessage)
.finally(() => {
setRunLoading(false)
})
.finally(() => {
setRunLoading(false)
})
}
const completedNum = [textPreprocessing, knowledgeExtraction, creatingNodesEdges, deduplication].filter(item => item.status === 'completed').length
const deduplicationData = groupDataByType(deduplication.data, 'result_type')
@@ -251,10 +251,10 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
</div>
</>
: !testResult || Object.keys(testResult).length === 0
? <RbAlert color="orange" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
{t('memoryExtractionEngine.warning')}
</RbAlert>
: <RbAlert color="green" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
? <RbAlert color="orange" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
{t('memoryExtractionEngine.warning')}
</RbAlert>
: <RbAlert color="green" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
{t('memoryExtractionEngine.success')}
</RbAlert>
}
@@ -266,15 +266,28 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
headerType="borderL"
headerClassName="rb:before:bg-[#155EEF]!"
>
{textPreprocessing.data.map((vo, index) => (
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
<Markdown content={'-' + t('memoryExtractionEngine.fragment') + vo.chunk_index + ': ' + (vo.content.startsWith('\n') ? vo.content : '\n' + vo.content)} />
</div>
))}
{textPreprocessing.data.map((vo, index) => {
if (vo.deleted_messages) {
return <div key={index} className="rb:mb-3 rb:pb-1 rb:border-b rb:border-b-[#EBEBEB]">
<div className="rb:font-medium rb:text-[12px] rb:mb-2">{t('memoryExtractionEngine.Pruned')}</div>
{vo.deleted_messages.map((msg: any, idx: number) => (
<div key={idx} className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
<Markdown content={'-' + t('memoryExtractionEngine.pruning') + (idx + 1) + ': ' + msg.content} />
</div>
))}
</div>
}
return (
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
<Markdown content={'-' + t('memoryExtractionEngine.fragment') + vo.chunk_index + ': ' + (vo.content.startsWith('\n') ? vo.content : '\n' + vo.content)} />
</div>
)
})}
{formatTime(textPreprocessing)}
{textPreprocessing.result &&
<RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
{t('memoryExtractionEngine.text_preprocessing_desc', { count: textPreprocessing.result.total_chunks })},
{t('memoryExtractionEngine.pruning_desc', { count: textPreprocessing.result.pruning.deleted_count || 0 })},
{t('memoryExtractionEngine.text_preprocessing_desc', { count: textPreprocessing.result.total_chunks })},
{t('memoryExtractionEngine.chunkerStrategy')}: {t(`memoryExtractionEngine.${lowercaseFirst(textPreprocessing.result.chunker_strategy)}`)}
</RbAlert>
}
@@ -286,7 +299,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
headerType="borderL"
headerClassName="rb:before:bg-[#155EEF]!"
>
{knowledgeExtraction.data.map((vo, index) =>
{knowledgeExtraction.data.map((vo, index) =>
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{vo.statement}</div>
)}
{formatTime(knowledgeExtraction)}
@@ -345,31 +358,30 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
{Object.keys(resultObj).map((key, index) => {
const keys = (resultObj as Record<string, string>)[key].split('.')
return (
<div key={index}>
<div className="rb:text-[24px] rb:leading-7.5 rb:font-extrabold">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{t(`memoryExtractionEngine.${key}`)}</div>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#369F21] rb:leading-3.5 rb:font-regular">
{}
{key === 'extractTheNumberOfEntities' && testResult.dedup
? t(`memoryExtractionEngine.${key}Desc`, {
num: testResult.dedup.total_merged_count,
exact: testResult.dedup.breakdown.exact,
fuzzy: testResult.dedup.breakdown.fuzzy,
llm: testResult.dedup.breakdown.llm,
})
: key === 'numberOfEntityDisambiguation' && testResult.disambiguation
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count })
: key === 'numberOfRelationalTriples' && testResult.triplets
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count })
:t(`memoryExtractionEngine.${key}Desc`)
}
<div key={index}>
<div className="rb:text-[24px] rb:leading-7.5 rb:font-extrabold">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{t(`memoryExtractionEngine.${key}`)}</div>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#369F21] rb:leading-3.5 rb:font-regular">
{key === 'extractTheNumberOfEntities' && testResult.dedup
? t(`memoryExtractionEngine.${key}Desc`, {
num: testResult.dedup.total_merged_count,
exact: testResult.dedup.breakdown.exact,
fuzzy: testResult.dedup.breakdown.fuzzy,
llm: testResult.dedup.breakdown.llm,
})
: key === 'numberOfEntityDisambiguation' && testResult.disambiguation
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count })
: key === 'numberOfRelationalTriples' && testResult.triplets
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count })
:t(`memoryExtractionEngine.${key}Desc`)
}
</div>
</div>
</div>
)})}
)})}
</div>
</RbCard>
}
{testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.entityDeduplicationImpact')}
@@ -388,7 +400,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
</RbAlert>
</RbCard>
}
{testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.theEffectOfEntityDisambiguationLLMDriven')}
@@ -399,7 +411,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
<div key={index} className={clsx("rb:text-[12px] rb:text-[#5B6167] rb:leading-4", {
'rb:mt-4': index > 0,
})}>
<div className="rb:font-medium rb:mb-2">Disagreement Case {index +1}:</div>
<div className="rb:font-medium rb:mb-2">{t('memoryExtractionEngine.disagreementCase')} {index +1}:</div>
-{item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) <span className="rb:text-[#369F21]">{item.result}</span>
</div>
))}
@@ -409,7 +421,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
</RbAlert>
</RbCard>
}
{testResult?.core_entities && testResult?.core_entities.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.coreEntitiesAfterDedup')}
@@ -433,7 +445,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
</div>
</RbCard>
}
{testResult?.triplet_samples && testResult?.triplet_samples.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.extractRelationalTriples')}

View File

@@ -18,6 +18,7 @@ import RbModal from '@/components/RbModal'
import { createMemoryConfig, updateMemoryConfig } from '@/api/memory'
import { getOntologyScenesSimpleUrl } from '@/api/ontology'
import CustomSelect from '@/components/CustomSelect';
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -110,7 +111,11 @@ const MemoryForm = forwardRef<MemoryFormRef, MemoryFormProps>(({
<FormItem
name="config_name"
label={t('memory.configurationName')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.pleaseEnter')} />
</FormItem>
@@ -118,6 +123,7 @@ const MemoryForm = forwardRef<MemoryFormRef, MemoryFormProps>(({
<FormItem
name="config_desc"
label={t('memory.desc')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('common.pleaseEnter')} />
</FormItem>

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:10
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 16:50:10
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-27 10:20:51
*/
/**
* Model List View
@@ -21,7 +21,7 @@ import PageEmpty from '@/components/Empty/PageEmpty';
import Tag from '@/components/Tag';
import KeyConfigModal from './components/KeyConfigModal'
import ModelListDetail from './components/ModelListDetail'
import { getLogoUrl } from './utils'
import { getListLogoUrl } from './utils'
/**
* Model list component
@@ -70,7 +70,7 @@ const ModelList = forwardRef<BaseRef, { query: any; handleEdit: (vo?: ModelListI
<RbCard
key={item.provider}
title={t(`modelNew.${item.provider}`)}
avatarUrl={getLogoUrl(item.logo)}
avatarUrl={getListLogoUrl(item.provider, item.logo)}
avatar={
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
{item.provider[0].toUpperCase()}

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:49:28
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 16:49:28
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-28 17:24:05
*/
/**
* Custom Model Modal
@@ -20,6 +20,7 @@ import CustomSelect from '@/components/CustomSelect'
import UploadImages from '@/components/Upload/UploadImages'
import { updateCustomModel, addCustomModel, modelTypeUrl, modelProviderUrl } from '@/api/models'
import { getFileLink } from '@/api/fileStorage'
import { validateSquareImage, stringRegExp } from '@/utils/validator'
/**
* Custom model modal component
@@ -50,7 +51,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
setModel(model);
form.setFieldsValue({
...model,
logo: model.logo ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined
logo: model.logo && model.logo.startsWith('http') ? { url: model.logo, uid: model.logo, status: 'done', name: 'logo' } : undefined
});
} else {
setIsEdit(false);
@@ -65,7 +66,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
const res = isEdit ? updateCustomModel(model.id, rest) : addCustomModel(data)
res.then(() => {
refresh && refresh(isEdit)
refresh?.(isEdit)
handleClose()
message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess'))
})
@@ -79,7 +80,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
.validateFields()
.then((values) => {
const { logo, ...rest } = values;
let formData: CustomModelForm = {
const formData: CustomModelForm = {
...rest
}
@@ -125,14 +126,22 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
name="logo"
label={t('modelNew.logo')}
valuePropName="fileList"
rules={[{ required: true, message: t('common.pleaseSelect') }]}
rules={[
{ required: true, message: t('common.pleaseSelect') },
{ validator: validateSquareImage(t('common.imageSquareRequired')) }
]}
extra={t('common.logoTip')?.split('\n').map((vo, index) => <div key={index}>{vo}</div>)}
>
<UploadImages />
<UploadImages fileSize={2} />
</Form.Item>
<Form.Item
name="name"
label={t('modelNew.name')}
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.name') }) }]}
rules={[
{ required: true, message: t('common.inputPlaceholder', { title: t('modelNew.name') }) },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.pleaseEnter')} />
</Form.Item>
@@ -166,6 +175,7 @@ const CustomModelModal = forwardRef<CustomModelModalRef, CustomModelModalProps>(
<Form.Item
name="description"
label={t('modelNew.description')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('common.pleaseEnter')} />
</Form.Item>

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:49:33
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 16:49:33
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-02 12:23:13
*/
/**
* Group Model Modal
@@ -21,6 +21,7 @@ import { updateCompositeModel, modelTypeUrl, addCompositeModel } from '@/api/mod
import UploadImages from '@/components/Upload/UploadImages'
import ModelImplement from './ModelImplement'
import { getFileLink } from '@/api/fileStorage'
import { validateSquareImage, stringRegExp } from '@/utils/validator'
/**
* Group model modal component
@@ -133,15 +134,26 @@ const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
name="logo"
label={t('modelNew.logo')}
valuePropName="fileList"
rules={[{ required: true, message: t('common.pleaseSelect') }]}
rules={[
{ required: true, message: t('common.pleaseSelect') },
{ validator: validateSquareImage(t('common.imageSquareRequired')) }
]}
extra={t('common.logoTip')?.split('\n').map((vo, index) => <div key={index}>{vo}</div>)}
>
<UploadImages />
<UploadImages
fileSize={2}
fileType={['png', 'jpg']}
/>
</Form.Item>
<Form.Item
name="name"
label={t('modelNew.name')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.pleaseEnter')} />
</Form.Item>
@@ -165,6 +177,7 @@ const GroupModelModal = forwardRef<GroupModelModalRef, GroupModelModalProps>(({
<Form.Item
name="description"
label={t('modelNew.description')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('common.pleaseEnter')} />
</Form.Item>

View File

@@ -121,6 +121,7 @@ const tabKeys = ['group', 'list', 'square']
{activeTab !== 'list' &&
<Form.Item name="search" noStyle>
<SearchInput
maxLength={50}
placeholder={t(`modelNew.${activeTab}SearchPlaceholder`)}
className="rb:w-70!"
/>

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 16:50:22
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 16:50:22
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-27 10:22:46
*/
/**
* Utility functions for Model Management
@@ -40,5 +40,26 @@ export const getLogoUrl = (logo?: string) => {
return logo
}
return ICONS[logo as keyof typeof ICONS] || undefined
}
/**
* Get logo URL from provider name or URL
* @param provider - Provider name
* @param logo - Provider name or logo URL
* @returns Logo URL or undefined
*/
export const getListLogoUrl = (provider?: string, logo?: string) => {
let url = ICONS[provider as keyof typeof ICONS]
if (url) return url
if (!logo) {
return undefined
}
if (logo.startsWith('http')) {
return logo
}
return ICONS[logo as keyof typeof ICONS] || undefined
}

View File

@@ -182,7 +182,10 @@ const OntologyClassExtractModal = forwardRef<OntologyClassExtractModalRef, Ontol
<FormItem
name="scenario"
label={t('ontology.scenario')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 2000 },
]}
>
<Input.TextArea placeholder={t('ontology.scenarioPlaceholder')} />
</FormItem>

View File

@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next';
import type { AddClassItem, OntologyClassModalRef } from '../types'
import RbModal from '@/components/RbModal'
import { createOntologyClass } from '@/api/ontology'
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -105,7 +106,11 @@ const OntologyClassModal = forwardRef<OntologyClassModalRef, OntologyClassModalP
<FormItem
name="class_name"
label={t('ontology.class_name')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
@@ -113,6 +118,7 @@ const OntologyClassModal = forwardRef<OntologyClassModalRef, OntologyClassModalP
<FormItem
name="class_description"
label={t('ontology.class_description')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('ontology.classDescriptionPlaceholder')} />
</FormItem>

View File

@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next';
import type { OntologyItem, OntologyModalData, OntologyModalRef } from '../types'
import RbModal from '@/components/RbModal'
import { createOntologyScene, updateOntologyScene } from '@/api/ontology'
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -109,7 +110,11 @@ const OntologyModal = forwardRef<OntologyModalRef, OntologyModalProps>(({
<FormItem
name="scene_name"
label={t('ontology.scene_name')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
@@ -117,6 +122,7 @@ const OntologyModal = forwardRef<OntologyModalRef, OntologyModalProps>(({
<FormItem
name="scene_description"
label={t('ontology.scene_description')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('ontology.descriptionPlaceholder')} />
</FormItem>

View File

@@ -17,6 +17,7 @@ import type { AiPromptModalRef } from '@/views/ApplicationConfig/types'
import exitIcon from '@/assets/images/knowledgeBase/exit.png';
import type { SkillFormData } from '../types'
import { getSkillDetail, createSkill, updateSkill } from '@/api/skill'
import { stringRegExp } from '@/utils/validator';
/**
* Skill Configuration Page Component
@@ -110,7 +111,7 @@ const SkillConfig: FC = () => {
// Format tools data for API
const formData = {
...rest,
tools: tools?.map((item: any) => ({
tools: tools?.map((item) => ({
tool_id: item.tool_id,
operation: item.operation
}))
@@ -144,13 +145,18 @@ const SkillConfig: FC = () => {
<Form.Item
name="name"
label={t('skills.name')}
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('skills.name') }) }]}
rules={[
{ required: true, message: t('common.inputPlaceholder', { title: t('skills.name') }) },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.pleaseEnter')} />
</Form.Item>
<Form.Item
name="description"
label={t('skills.description')}
rules={[{ max: 500 }]}
>
<Input.TextArea placeholder={t('skills.descriptionPlaceholder')} />
</Form.Item>

View File

@@ -17,6 +17,8 @@ export interface SkillFormData {
tools: Array<{
/** Tool identifier */
tool_id: string;
/** Tool operation/action */
operation?: string;
}>;
/** Skill configuration settings */
config: {

View File

@@ -23,6 +23,7 @@ import UploadImages from '@/components/Upload/UploadImages'
import { getFileLink } from '@/api/fileStorage'
import ragIcon from '@/assets/images/space/rag.png'
import neo4jIcon from '@/assets/images/space/neo4j.png'
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -34,8 +35,8 @@ interface SpaceModalProps {
}
/** Storage types */
const types: StorageType[] = [
'rag',
'neo4j',
'rag',
]
/** Type icons mapping */
const typeIcons: Record<StorageType, string> = {
@@ -91,7 +92,7 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
setCurrentStep(1)
} else {
const { icon, ...rest } = values
let formData: SpaceModalData = {
const formData: SpaceModalData = {
...rest
}
if (icon?.response?.data.file_id) {
@@ -154,6 +155,9 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
<Form
form={form}
layout="vertical"
initialValues={{
storage_type: types[0],
}}
>
<Form.Item
name="icon"
@@ -161,14 +165,19 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
valuePropName="fileList"
hidden={currentStep === 1}
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('space.spaceIcon') }) }]}
extra={t('common.logoTip')?.split('\n').map((vo, index) => <div key={index}>{vo}</div>)}
>
<UploadImages />
<UploadImages fileSize={2} />
</Form.Item>
<FormItem
name="name"
label={t('space.spaceName')}
hidden={currentStep === 1}
rules={[{ required: true, message: t('common.inputPlaceholder', { title: t('space.spaceName') }) }]}
rules={[
{ required: true, message: t('common.inputPlaceholder', { title: t('space.spaceName') }) },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('common.inputPlaceholder', { title: t('space.spaceName') })} />
</FormItem>
@@ -183,7 +192,8 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
value: type,
label: t(`space.${type}`),
labelDesc: t(`space.${type}Desc`),
icon: typeIcons[type]
icon: typeIcons[type],
recommend: type === 'neo4j',
}))}
block={true}
/>

View File

@@ -4,7 +4,7 @@
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-03 10:12:33
*/
import { useEffect, useState, type FC } from 'react';
import { useEffect, useState, useRef, type FC } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical';
@@ -30,6 +30,26 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
const [showSuggestions, setShowSuggestions] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0 });
const popupRef = useRef<HTMLDivElement>(null);
const scrollSelectedIntoView = () => {
if (!popupRef.current) return;
const selectedElement = popupRef.current.querySelector('[data-selected="true"]');
if (!selectedElement) return;
const container = popupRef.current;
const element = selectedElement as HTMLElement;
const containerRect = container.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
if (elementRect.bottom > containerRect.bottom) {
container.scrollTop += elementRect.bottom - containerRect.bottom;
} else if (elementRect.top < containerRect.top) {
container.scrollTop -= containerRect.top - elementRect.top;
}
};
// Listen to editor updates and show suggestions when '/' is typed
useEffect(() => {
@@ -140,7 +160,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
};
// Group suggestions by node ID
const groupedSuggestions = options.reduce((groups: Record<string, any[]>, suggestion) => {
const groupedSuggestions = options.reduce((groups: Record<string, Suggestion[]>, suggestion) => {
const { nodeData } = suggestion
const nodeId = nodeData.id as string;
if (!groups[nodeId]) {
@@ -190,7 +210,9 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
while (nextIndex < allOptions.length && allOptions[nextIndex].disabled) {
nextIndex++;
}
return nextIndex >= allOptions.length ? prev : nextIndex;
const newIndex = nextIndex >= allOptions.length ? prev : nextIndex;
setTimeout(() => scrollSelectedIntoView(), 0);
return newIndex;
});
return true;
}
@@ -210,7 +232,9 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
while (prevIndex >= 0 && allOptions[prevIndex].disabled) {
prevIndex--;
}
return prevIndex < 0 ? prev : prevIndex;
const newIndex = prevIndex < 0 ? prev : prevIndex;
setTimeout(() => scrollSelectedIntoView(), 0);
return newIndex;
});
return true;
}
@@ -247,6 +271,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
}
return (
<div
ref={popupRef}
data-autocomplete-popup="true"
onMouseDown={(e) => e.preventDefault()}
style={{
@@ -279,6 +304,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
return (
<div
key={option.key}
data-selected={selectedIndex === globalIndex}
style={{
padding: '8px 12px',
cursor: option.disabled ? 'not-allowed' : 'pointer',