Merge branch 'release/v0.2.4' into develop
# Conflicts: # web/src/views/Workflow/constant.ts # web/src/views/Workflow/hooks/useWorkflowGraph.ts
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 13:59:56
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 13:59:56
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 16:24:05
|
||||
*/
|
||||
import { request, API_PREFIX } from '@/utils/request'
|
||||
|
||||
// Upload file,file storage has expiration period
|
||||
export const fileUploadUrl = `${API_PREFIX}/storage/files`
|
||||
export const fileUploadUrlWithoutApiPrefix = '/storage/files'
|
||||
export const fileUploadUrl = `${API_PREFIX}${fileUploadUrlWithoutApiPrefix}`
|
||||
export const fileUpload = (formData?: unknown) => {
|
||||
return request.uploadFile('/storage/files', formData)
|
||||
return request.uploadFile(fileUploadUrlWithoutApiPrefix, formData)
|
||||
}
|
||||
|
||||
// Get file access URL (no token required)
|
||||
@@ -30,4 +31,5 @@ export const deleteFile = (fileId: string) => {
|
||||
return request.delete(deleteFileUrl(fileId))
|
||||
}
|
||||
|
||||
export const shareFileUploadUrl = `${API_PREFIX}/storage/share/files`
|
||||
export const shareFileUploadUrlWithoutApiPrefix = `/storage/share/files`
|
||||
export const shareFileUploadUrl = `${API_PREFIX}${shareFileUploadUrlWithoutApiPrefix}`
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2025-12-10 16:46:14
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-06 21:23:37
|
||||
* @Last Modified time: 2026-02-10 12:13:52
|
||||
*/
|
||||
import { type FC, useEffect, useMemo } from 'react'
|
||||
import { Flex, Input, Form } from 'antd'
|
||||
@@ -60,13 +60,14 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
}, [fileList])
|
||||
|
||||
const handleSend = () => {
|
||||
if (loading || !values || !values?.message || values?.message?.trim() === '') return
|
||||
onSend(values.message)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`rb:absolute rb:bottom-3 rb:left-0 rb:right-0 rb:w-full ${className}`}>
|
||||
<Flex vertical justify="space-between" className="rb:border rb:border-[#DFE4ED] rb:rounded-xl rb:min-h-30">
|
||||
{previewFileList.length > 0 && <Flex gap={14} className="rb:mx-3! rb:mt-3!">
|
||||
{previewFileList.length > 0 && <div className="rb:overflow-x-auto rb:max-w-full"><Flex gap={14} className="rb:mx-3! rb:mt-3! rb:w-max!">
|
||||
{previewFileList.map((file) => {
|
||||
if (file.type.includes('image')) {
|
||||
return (
|
||||
@@ -101,7 +102,7 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Flex>}
|
||||
</Flex></div>}
|
||||
{/* Message input form */}
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="message" noStyle>
|
||||
|
||||
@@ -766,6 +766,9 @@ export const en = {
|
||||
toWorkspace: 'Authorization to workspace',
|
||||
shareTitle:'Please select the workspace you want to share',
|
||||
shareNote:'Note: Sharing is not possible when workspace app is closed',
|
||||
shareSpace:'Manage Sharing',
|
||||
shareSpaceTitle:'Shared with the following workspaces',
|
||||
shareSpaceNote: 'Note: sharing is turned off, others will no longer have access.',
|
||||
authorizedPerson:'Authorized person',
|
||||
chunkList:'Chunk List',
|
||||
delimiter:'Text paragraph delimiter',
|
||||
@@ -1588,6 +1591,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
creating_nodes_edges_desc: 'Entity relationship creation completed, {{num}} relationships in total',
|
||||
deduplication_desc: 'Deduplication and disambiguation completed, {{count}} unique entities in total',
|
||||
custom_text: 'Debug Text',
|
||||
ontologyCoverage: 'Ontology Type',
|
||||
entity_total: 'Total {{num}} entities',
|
||||
scene_type_distribution: 'Scene Type Distribution',
|
||||
general_type_distribution: 'General Type Distribution',
|
||||
unmatched: 'Unmatched',
|
||||
},
|
||||
memoryConversation: {
|
||||
searchPlaceholder: 'Enter user ID...',
|
||||
@@ -2105,7 +2113,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
search_switch: 'Search Mode',
|
||||
},
|
||||
'memory-write': {
|
||||
message: 'Message',
|
||||
messages: 'Message',
|
||||
config_id: 'Memory Configuration',
|
||||
search_switch: 'Search Mode',
|
||||
},
|
||||
@@ -2446,6 +2454,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
involved_objects: 'Involved Objects',
|
||||
content_records: 'Episode Content Records',
|
||||
emotion: 'Emotion and State Records',
|
||||
none: 'None',
|
||||
},
|
||||
implicitDetail: {
|
||||
title: 'The invisible forces that shaped me',
|
||||
|
||||
@@ -305,6 +305,9 @@ export const zh = {
|
||||
toWorkspace: '授权到工作空间',
|
||||
shareTitle: '请选择要分享的工作空间',
|
||||
shareNote: '注意:工作空间应用关闭时无法分享',
|
||||
shareSpace:'管理共享',
|
||||
shareSpaceTitle:'已共享至以下工作空间',
|
||||
shareSpaceNote: '注意:关闭共享后对方将无法访问',
|
||||
authorizedPerson: '授权人',
|
||||
chunkList: '分块列表',
|
||||
delimiter: '文本段落分隔符',
|
||||
@@ -1668,6 +1671,11 @@ export const zh = {
|
||||
creating_nodes_edges_desc: '实体关系创建完成,共{{num}}条关系',
|
||||
deduplication_desc: '去重消歧完成,最终{{count}}个唯一实体',
|
||||
custom_text: '调试文本',
|
||||
ontologyCoverage: '本体类型',
|
||||
entity_total: '一共{{num}}个实体',
|
||||
scene_type_distribution: '场景类型',
|
||||
general_type_distribution: '通用类型',
|
||||
unmatched: '未匹配',
|
||||
},
|
||||
memoryConversation: {
|
||||
chatEmpty:'有什么我可以帮您的吗?',
|
||||
@@ -2200,7 +2208,7 @@ export const zh = {
|
||||
search_switch: '检索模式',
|
||||
},
|
||||
'memory-write': {
|
||||
message: '消息',
|
||||
messages: '消息',
|
||||
config_id: '记忆配置',
|
||||
search_switch: '检索模式',
|
||||
},
|
||||
@@ -2541,6 +2549,7 @@ export const zh = {
|
||||
involved_objects: '涉及对象',
|
||||
content_records: '情景内容记录',
|
||||
emotion: '情绪与状态记录',
|
||||
none: '无',
|
||||
},
|
||||
implicitDetail: {
|
||||
title: '那些塑造了我的无形力量',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:29:21
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-06 11:20:14
|
||||
* @Last Modified time: 2026-02-09 16:56:27
|
||||
*/
|
||||
import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
||||
import clsx from 'clsx'
|
||||
@@ -210,7 +210,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
})
|
||||
if (default_model_config_id === values?.default_model_config_id) {
|
||||
setChatList([{
|
||||
label: vo.label || '',
|
||||
label: defaultModel?.id === default_model_config_id && defaultModel?.name ? defaultModel.name : vo.label || '',
|
||||
model_config_id: default_model_config_id || '',
|
||||
model_parameters: {...rest},
|
||||
list: []
|
||||
@@ -284,11 +284,19 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
...(item.config || {})
|
||||
}))
|
||||
} as KnowledgeConfig : null,
|
||||
tools: tools.map(vo => ({
|
||||
tool_id: vo.tool_id,
|
||||
operation: vo.operation,
|
||||
enabled: vo.enabled
|
||||
})),
|
||||
tools: tools.map(vo => {
|
||||
if (!vo.operation) {
|
||||
return {
|
||||
tool_id: vo.tool_id,
|
||||
enabled: vo.enabled
|
||||
}
|
||||
}
|
||||
return {
|
||||
tool_id: vo.tool_id,
|
||||
operation: vo.operation,
|
||||
enabled: vo.enabled
|
||||
}
|
||||
}),
|
||||
skills: {
|
||||
...skills,
|
||||
skill_ids: (skills?.skill_ids as Skill[])?.map(vo => vo.id)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:27:39
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 16:27:39
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-10 12:18:23
|
||||
*/
|
||||
/**
|
||||
* Chat debugging component for application testing
|
||||
@@ -61,6 +61,8 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
||||
|
||||
useEffect(() => {
|
||||
setIsCluster(source === 'multi_agent')
|
||||
setFileList([])
|
||||
setMessage(undefined)
|
||||
}, [source])
|
||||
|
||||
/** Add user message to all chat lists */
|
||||
@@ -388,7 +390,6 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
||||
setFileList([...list || []])
|
||||
}
|
||||
|
||||
console.log('chatList', chatList, fileList)
|
||||
return (
|
||||
<div className="rb:relative rb:h-full rb:flex rb:flex-col">
|
||||
{chatList.length === 0
|
||||
@@ -424,9 +425,9 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
||||
}
|
||||
<ChatContent
|
||||
classNames={{
|
||||
'rb:mx-[16px] rb:pt-[24px]': true,
|
||||
'rb:h-[calc(100vh-258px)]': isCluster,
|
||||
'rb:h-[calc(100vh-356px)]': !isCluster,
|
||||
'rb:mx-[16px] rb:mt-6': true,
|
||||
'rb:h-[calc(100vh-282px)]': isCluster,
|
||||
'rb:h-[calc(100vh-380px)]': !isCluster,
|
||||
}}
|
||||
contentClassNames={{
|
||||
'rb:max-w-[400px]!': chatList.length === 1,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:34:12
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-06 11:10:16
|
||||
* @Last Modified time: 2026-02-09 13:52:22
|
||||
*/
|
||||
/**
|
||||
* Application Management Page
|
||||
@@ -83,9 +83,9 @@ const ApplicationManagement: React.FC = () => {
|
||||
setQuery(prev => ({...prev, type: value}))
|
||||
}
|
||||
|
||||
const handleImport = () => {
|
||||
uploadWorkflowModalRef.current?.handleOpen()
|
||||
}
|
||||
// const handleImport = () => {
|
||||
// uploadWorkflowModalRef.current?.handleOpen()
|
||||
// }
|
||||
return (
|
||||
<>
|
||||
<Row gutter={16} className="rb:mb-4">
|
||||
@@ -111,9 +111,9 @@ const ApplicationManagement: React.FC = () => {
|
||||
</Col>
|
||||
<Col span={12} className="rb:text-right">
|
||||
<Space size={12}>
|
||||
<Button onClick={handleImport}>
|
||||
{/* <Button onClick={handleImport}>
|
||||
{t('application.importWorkflow')}
|
||||
</Button>
|
||||
</Button> */}
|
||||
<Button type="primary" onClick={handleCreate}>
|
||||
{t('application.createApplication')}
|
||||
</Button>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-06 21:09:42
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-06 21:09:42
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 16:41:31
|
||||
*/
|
||||
/**
|
||||
* File Upload Component
|
||||
@@ -25,8 +25,8 @@ import { Upload, Progress, App } from 'antd';
|
||||
import type { UploadProps, UploadFile } from 'antd';
|
||||
import type { UploadProps as RcUploadProps } from 'antd/es/upload/interface';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cookieUtils } from '@/utils/request'
|
||||
import { fileUploadUrl } from '@/api/fileStorage'
|
||||
import { request } from '@/utils/request'
|
||||
import { fileUploadUrlWithoutApiPrefix } from '@/api/fileStorage'
|
||||
|
||||
interface UploadFilesProps extends Omit<UploadProps, 'onChange'> {
|
||||
/** Upload API endpoint */
|
||||
@@ -99,7 +99,7 @@ export interface UploadFilesRef {
|
||||
* Supports single/multiple file uploads, drag-and-drop, file validation, and preview
|
||||
*/
|
||||
const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
action = fileUploadUrl,
|
||||
action = fileUploadUrlWithoutApiPrefix,
|
||||
multiple = false,
|
||||
fileList: propFileList = [],
|
||||
onChange,
|
||||
@@ -110,6 +110,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
maxCount = 1,
|
||||
onRemove: customOnRemove,
|
||||
update,
|
||||
requestConfig,
|
||||
...props
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -163,6 +164,24 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
return isAutoUpload;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom upload request handler
|
||||
*/
|
||||
const handleCustomRequest: RcUploadProps['customRequest'] = async (options) => {
|
||||
const { file, onSuccess, onError } = options;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await request.uploadFile(action, formData, requestConfig);
|
||||
|
||||
onSuccess?.({data: response});
|
||||
} catch (error) {
|
||||
onError?.(error as Error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles upload state changes
|
||||
*/
|
||||
@@ -207,13 +226,10 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
|
||||
// Generate upload component configuration
|
||||
const uploadProps: UploadProps = {
|
||||
action,
|
||||
customRequest: handleCustomRequest,
|
||||
multiple: multiple && maxCount > 1,
|
||||
fileList,
|
||||
beforeUpload,
|
||||
headers: {
|
||||
authorization: `Bearer ${cookieUtils.get('authToken')}`,
|
||||
},
|
||||
onChange: handleChange,
|
||||
accept,
|
||||
disabled,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-06 21:09:47
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-06 21:09:47
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 10:17:54
|
||||
*/
|
||||
/**
|
||||
* Upload File List Modal Component
|
||||
@@ -19,8 +19,7 @@
|
||||
* @component
|
||||
*/
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, Select, Button, Space } from 'antd';
|
||||
import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
|
||||
import { Form, Input, Select, Button, Flex } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { UploadFileListModalRef } from '../types'
|
||||
@@ -95,11 +94,12 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
|
||||
<>
|
||||
{/* Render each file entry with type selector and URL input */}
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
<Space key={key} style={{ display: 'flex' }} align="baseline">
|
||||
<Flex key={key} gap={8} align="center" className="rb:mb-3!">
|
||||
<FormItem
|
||||
{...restField}
|
||||
name={[name, 'type']}
|
||||
initialValue="image"
|
||||
className="rb:mb-0!"
|
||||
>
|
||||
<Select
|
||||
placeholder={t('memoryConversation.fileType')}
|
||||
@@ -113,15 +113,19 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
|
||||
{...restField}
|
||||
name={[name, 'url']}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
className="rb:mb-0!"
|
||||
>
|
||||
<Input placeholder={t('memoryConversation.fileUrl')} className="rb:w-82.5" />
|
||||
<Input placeholder={t('memoryConversation.fileUrl')} className="rb:w-82.5!" />
|
||||
</FormItem>
|
||||
<MinusCircleOutlined onClick={() => remove(name)} style={{ marginTop: 30 }} />
|
||||
</Space>
|
||||
<div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
|
||||
onClick={() => remove(name)}
|
||||
></div>
|
||||
</Flex>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
|
||||
{t('common.add')}
|
||||
<Form.Item noStyle>
|
||||
<Button type="dashed" onClick={() => add()} block>
|
||||
+ {t('common.add')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:58:03
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-06 21:11:23
|
||||
* @Last Modified time: 2026-02-09 20:20:01
|
||||
*/
|
||||
/**
|
||||
* Conversation Page
|
||||
@@ -35,7 +35,7 @@ import MemoryFunctionCheckedIcon from '@/assets/images/conversation/memoryFuncti
|
||||
import { type SSEMessage } from '@/utils/stream'
|
||||
import UploadFiles from './components/FileUpload'
|
||||
// import AudioRecorder from '@/components/AudioRecorder'
|
||||
import { shareFileUploadUrl } from '@/api/fileStorage'
|
||||
import { shareFileUploadUrlWithoutApiPrefix } from '@/api/fileStorage'
|
||||
import UploadFileListModal from './components/UploadFileListModal'
|
||||
|
||||
/**
|
||||
@@ -350,11 +350,16 @@ const Conversation: FC = () => {
|
||||
{
|
||||
key: 'upload', label: (
|
||||
<UploadFiles
|
||||
action={shareFileUploadUrl}
|
||||
action={shareFileUploadUrlWithoutApiPrefix}
|
||||
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']}
|
||||
onChange={fileChange}
|
||||
fileList={[]}
|
||||
update={update}
|
||||
requestConfig={{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: `Bearer ${shareToken || ''}`,
|
||||
} }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-04 18:34:36
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-04 18:49:59
|
||||
* @Last Modified time: 2026-02-09 15:46:07
|
||||
*/
|
||||
import { useEffect, type FC } from 'react'
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||
|
||||
import { cookieUtils } from '@/utils/request'
|
||||
import { useI18n } from '@/store/locale'
|
||||
|
||||
/**
|
||||
* JumpPage Component
|
||||
@@ -26,11 +27,17 @@ import { cookieUtils } from '@/utils/request'
|
||||
const JumpPage: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const [searchParams] = useSearchParams()
|
||||
const { changeLanguage } = useI18n()
|
||||
|
||||
useEffect(() => {
|
||||
// Convert URLSearchParams to a plain object for easier access
|
||||
const data = Object.fromEntries(searchParams)
|
||||
const { access_token, refresh_token, target } = data
|
||||
const { access_token, refresh_token, target, language } = data
|
||||
|
||||
if (language) {
|
||||
changeLanguage(language)
|
||||
cookieUtils.set('language', language)
|
||||
}
|
||||
|
||||
// Store authentication tokens in cookies for API authorization
|
||||
cookieUtils.set('authToken', access_token)
|
||||
|
||||
@@ -33,8 +33,8 @@ const DocumentDetails: FC = () => {
|
||||
documentId,
|
||||
parentId: locationParentId,
|
||||
breadcrumbPath
|
||||
} = location.state as {
|
||||
documentId: string;
|
||||
} = (location.state || {}) as {
|
||||
documentId?: string;
|
||||
parentId?: string;
|
||||
breadcrumbPath?: BreadcrumbPath;
|
||||
};
|
||||
@@ -51,6 +51,18 @@ const DocumentDetails: FC = () => {
|
||||
const insertModalRef = useRef<InsertModalRef>(null);
|
||||
const isManualRefreshRef = useRef(false);
|
||||
|
||||
// Early return if no documentId
|
||||
if (!documentId) {
|
||||
return (
|
||||
<div className="rb:flex rb:items-center rb:justify-center rb:h-full rb:flex-col rb:gap-4">
|
||||
<div className="rb:text-gray-500">{t('knowledgeBase.documentIdRequired') || '文档ID不能为空'}</div>
|
||||
<Button type="primary" onClick={() => navigate(-1)}>
|
||||
{t('common.back') || '返回'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (documentId) {
|
||||
fetchDocumentDetail();
|
||||
|
||||
@@ -46,6 +46,16 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
const entityTypes = graphragConfig?.entity_types || '';
|
||||
const entityNormalization = graphragConfig?.resolution || false;
|
||||
const communityReportGeneration = graphragConfig?.community || false;
|
||||
|
||||
// Watch for changes to _third_party_platform field directly
|
||||
const formThirdPartyPlatform = Form.useWatch(['parser_config', '_third_party_platform'], form);
|
||||
|
||||
// Sync form value to state when form value changes
|
||||
useEffect(() => {
|
||||
if (formThirdPartyPlatform && (formThirdPartyPlatform === 'yuque' || formThirdPartyPlatform === 'feishu')) {
|
||||
setThirdPartyPlatform(formThirdPartyPlatform);
|
||||
}
|
||||
}, [formThirdPartyPlatform]);
|
||||
|
||||
// Encapsulate cancel method, add close modal logic
|
||||
const handleClose = () => {
|
||||
@@ -199,6 +209,8 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
type: type || currentType,
|
||||
};
|
||||
form.setFieldsValue(defaults);
|
||||
// Reset third party platform to default when creating new
|
||||
setThirdPartyPlatform('yuque');
|
||||
return;
|
||||
}
|
||||
const baseValues: Partial<KnowledgeBaseFormData> = {
|
||||
@@ -210,7 +222,6 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
};
|
||||
|
||||
// Process parser_config data, set default values if not present
|
||||
const recordAny = record as any;
|
||||
baseValues.parser_config = {
|
||||
...record.parser_config,
|
||||
graphrag: {
|
||||
@@ -224,43 +235,6 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
}
|
||||
};
|
||||
|
||||
// Add Third-party specific fields to parser_config if exists
|
||||
if (recordAny.parser_config?.third_party_platform) {
|
||||
baseValues.parser_config.third_party_platform = recordAny.parser_config.third_party_platform;
|
||||
}
|
||||
if (recordAny.parser_config?.yuque_user_id) {
|
||||
baseValues.parser_config.yuque_user_id = recordAny.parser_config.yuque_user_id;
|
||||
}
|
||||
if (recordAny.parser_config?.yuque_token) {
|
||||
baseValues.parser_config.yuque_token = recordAny.parser_config.yuque_token;
|
||||
}
|
||||
if (recordAny.parser_config?.app_id) {
|
||||
baseValues.parser_config.app_id = recordAny.parser_config.app_id;
|
||||
}
|
||||
if (recordAny.parser_config?.app_secret) {
|
||||
baseValues.parser_config.app_secret = recordAny.parser_config.app_secret;
|
||||
}
|
||||
if (recordAny.parser_config?.folder_token) {
|
||||
baseValues.parser_config.folder_token = recordAny.parser_config.folder_token;
|
||||
}
|
||||
|
||||
// Add Web specific fields to parser_config if exists
|
||||
if (recordAny.parser_config?.entry_url) {
|
||||
baseValues.parser_config.entry_url = recordAny.parser_config.entry_url;
|
||||
}
|
||||
if (recordAny.parser_config?.max_pages) {
|
||||
baseValues.parser_config.max_pages = recordAny.parser_config.max_pages;
|
||||
}
|
||||
if (recordAny.parser_config?.delay_seconds) {
|
||||
baseValues.parser_config.delay_seconds = recordAny.parser_config.delay_seconds;
|
||||
}
|
||||
if (recordAny.parser_config?.timeout_seconds) {
|
||||
baseValues.parser_config.timeout_seconds = recordAny.parser_config.timeout_seconds;
|
||||
}
|
||||
if (recordAny.parser_config?.user_agent) {
|
||||
baseValues.parser_config.user_agent = recordAny.parser_config.user_agent;
|
||||
}
|
||||
|
||||
// If entity_types exists, convert to newline-separated format for TextArea display
|
||||
if (baseValues.parser_config.graphrag.entity_types) {
|
||||
if (Array.isArray(baseValues.parser_config.graphrag.entity_types)) {
|
||||
@@ -272,7 +246,18 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
}
|
||||
}
|
||||
|
||||
// Set form values first
|
||||
form.setFieldsValue(baseValues);
|
||||
|
||||
// Then sync third party platform state from form value
|
||||
// This ensures the state matches what's actually in the form
|
||||
const platform = baseValues.parser_config?._third_party_platform;
|
||||
if (platform === 'yuque' || platform === 'feishu') {
|
||||
setThirdPartyPlatform(platform);
|
||||
} else {
|
||||
// Reset to default if no platform specified
|
||||
setThirdPartyPlatform('yuque');
|
||||
}
|
||||
};
|
||||
|
||||
const setDynamicModelFields = (record: KnowledgeBaseListItem | null, types: string[]) => {
|
||||
@@ -295,20 +280,17 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
setDatasets(record || null);
|
||||
|
||||
// If rebuild mode, use record's actual type, otherwise use passed type
|
||||
const actualType = type === 'rebuild' ? (record?.type || 'General') : (type || currentType);
|
||||
// If editing (record exists but no type passed), use record's type
|
||||
const actualType = type === 'rebuild'
|
||||
? (record?.type || 'General')
|
||||
: (type || record?.type || currentType);
|
||||
|
||||
setCurrentType(actualType as any);
|
||||
setIsRebuildMode(type === 'rebuild'); // Set rebuild mode flag
|
||||
setOriginalType(type || ''); // Save original type parameter
|
||||
|
||||
// Set third party platform if editing Third-party type
|
||||
if (actualType === 'Third-party' && record) {
|
||||
const platform = (record as any).parser_config?.third_party_platform;
|
||||
if (platform === 'yuque' || platform === 'feishu') {
|
||||
setThirdPartyPlatform(platform);
|
||||
}
|
||||
} else {
|
||||
setThirdPartyPlatform('yuque'); // Reset to default
|
||||
}
|
||||
// Note: third party platform state will be set in setBaseFields function
|
||||
// No need to set it here separately to avoid inconsistency
|
||||
|
||||
// If rebuild mode, default to knowledge graph tab
|
||||
if (type === 'rebuild') {
|
||||
@@ -336,9 +318,13 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
setBaseFields(datasets, currentType);
|
||||
setDynamicModelFields(datasets, modelTypeList);
|
||||
}, [visible, datasets, currentType, modelTypeList]);
|
||||
// Only set fields when modal becomes visible, not on every state change
|
||||
// setBaseFields is already called in handleOpen
|
||||
// This useEffect is mainly for syncing dynamic model fields
|
||||
if (datasets && modelTypeList.length > 0) {
|
||||
setDynamicModelFields(datasets, modelTypeList);
|
||||
}
|
||||
}, [visible, modelTypeList]);
|
||||
|
||||
// Encapsulate save method, add submit logic
|
||||
const handleSave = () => {
|
||||
@@ -382,7 +368,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
|
||||
// Check Third-party authentication before saving
|
||||
if (formValues.type === 'Third-party' || currentType === 'Third-party') {
|
||||
const platform = formValues.parser_config?.third_party_platform || thirdPartyPlatform;
|
||||
const platform = formValues.parser_config?._third_party_platform || thirdPartyPlatform;
|
||||
|
||||
try {
|
||||
if (platform === 'yuque') {
|
||||
@@ -404,12 +390,12 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
} else if (platform === 'feishu') {
|
||||
// Validate Feishu credentials
|
||||
const feishuParams = {
|
||||
app_id: formValues.parser_config?.app_id,
|
||||
app_secret: formValues.parser_config?.app_secret,
|
||||
folder_token: formValues.parser_config?.folder_token
|
||||
feishu_app_id: formValues.parser_config?.feishu_app_id,
|
||||
feishu_app_secret: formValues.parser_config?.feishu_app_secret,
|
||||
feishu_folder_token: formValues.parser_config?.feishu_folder_token
|
||||
};
|
||||
|
||||
if (!feishuParams.app_id || !feishuParams.app_secret || !feishuParams.folder_token) {
|
||||
if (!feishuParams.feishu_app_id || !feishuParams.feishu_app_secret || !feishuParams.feishu_folder_token) {
|
||||
messageApi.error(t('knowledgeBase.feishuAuthRequired'));
|
||||
setLoading(false);
|
||||
return;
|
||||
@@ -533,7 +519,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
{ type: 'url', message: t('knowledgeBase.createForm.entryUrlInvalid') }
|
||||
]}
|
||||
>
|
||||
<Input placeholder="https://ai.redbearai.com" />
|
||||
<Input placeholder="https://ai.redbearai.com" disabled={!!datasets?.id} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@@ -545,7 +531,8 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
<SliderInput
|
||||
min={10}
|
||||
max={200}
|
||||
step={1}
|
||||
step={10}
|
||||
disabled={!!datasets?.id}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -553,12 +540,13 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
name={['parser_config', 'delay_seconds']}
|
||||
label={t('knowledgeBase.createForm.delaySeconds')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.delaySecondsRequired') }]}
|
||||
initialValue={1.0}
|
||||
initialValue={2}
|
||||
>
|
||||
<SliderInput
|
||||
min={1}
|
||||
max={3}
|
||||
step={0.1}
|
||||
step={1}
|
||||
disabled={!!datasets?.id}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -572,6 +560,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
min={5}
|
||||
max={15}
|
||||
step={1}
|
||||
disabled={!!datasets?.id}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -581,7 +570,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.userAgentRequired') }]}
|
||||
initialValue="KnowledgeBaseCrawler/1.0"
|
||||
>
|
||||
<Input placeholder="KnowledgeBaseCrawler/1.0" />
|
||||
<Input placeholder="KnowledgeBaseCrawler/1.0" disabled={!!datasets?.id} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
@@ -590,14 +579,14 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
{currentType === 'Third-party' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['parser_config', 'third_party_platform']}
|
||||
name={['parser_config', '_third_party_platform']}
|
||||
label={t('knowledgeBase.createForm.platform')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.platformRequired') }]}
|
||||
initialValue="yuque"
|
||||
>
|
||||
<Select
|
||||
value={thirdPartyPlatform}
|
||||
onChange={(value) => setThirdPartyPlatform(value)}
|
||||
disabled={!!datasets?.id}
|
||||
options={[
|
||||
{ value: 'yuque', label: t('knowledgeBase.createForm.yuque') },
|
||||
{ value: 'feishu', label: t('knowledgeBase.createForm.feishu') }
|
||||
@@ -612,7 +601,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
label={t('knowledgeBase.createForm.yuqueUserId')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.yuqueUserIdRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('knowledgeBase.createForm.yuqueUserIdPlaceholder')} />
|
||||
<Input placeholder={t('knowledgeBase.createForm.yuqueUserIdPlaceholder')} disabled={!!datasets?.id} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
@@ -620,7 +609,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
label={t('knowledgeBase.createForm.yuqueToken')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.yuqueTokenRequired') }]}
|
||||
>
|
||||
<Input.Password placeholder={t('knowledgeBase.createForm.yuqueTokenPlaceholder')} />
|
||||
<Input.Password placeholder={t('knowledgeBase.createForm.yuqueTokenPlaceholder')} disabled={!!datasets?.id} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
@@ -628,27 +617,27 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
{thirdPartyPlatform === 'feishu' && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['parser_config', 'app_id']}
|
||||
name={['parser_config', 'feishu_app_id']}
|
||||
label={t('knowledgeBase.createForm.feishuAppId')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.feishuAppIdRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('knowledgeBase.createForm.feishuAppIdPlaceholder')} />
|
||||
<Input placeholder={t('knowledgeBase.createForm.feishuAppIdPlaceholder')} disabled={!!datasets?.id} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'app_secret']}
|
||||
name={['parser_config', 'feishu_app_secret']}
|
||||
label={t('knowledgeBase.createForm.feishuAppSecret')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.feishuAppSecretRequired') }]}
|
||||
>
|
||||
<Input.Password placeholder={t('knowledgeBase.createForm.feishuAppSecretPlaceholder')} />
|
||||
<Input.Password placeholder={t('knowledgeBase.createForm.feishuAppSecretPlaceholder')} disabled={!!datasets?.id} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={['parser_config', 'folder_token']}
|
||||
name={['parser_config', 'feishu_folder_token']}
|
||||
label={t('knowledgeBase.createForm.feishuFolderToken')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.createForm.feishuFolderTokenRequired') }]}
|
||||
>
|
||||
<Input placeholder={t('knowledgeBase.createForm.feishuFolderTokenPlaceholder')} />
|
||||
<Input placeholder={t('knowledgeBase.createForm.feishuFolderTokenPlaceholder')} disabled={!!datasets?.id} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { NoData } from './noData';
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||
import RbMarkdown from '@/components/Markdown';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
interface RecallTestResultProps {
|
||||
data: RecallTestData[];
|
||||
@@ -61,6 +62,36 @@ const RecallTestResult = ({
|
||||
return `**${t('knowledgeBase.question')}:** ${question}\n**${t('knowledgeBase.answer')}:** ${answer}`;
|
||||
};
|
||||
|
||||
// Check if content is valid HTML
|
||||
const isValidHTML = (content: string): boolean => {
|
||||
if (!content) return false;
|
||||
// Check if content contains HTML tags
|
||||
const htmlTagPattern = /<[^>]+>/;
|
||||
return htmlTagPattern.test(content);
|
||||
};
|
||||
|
||||
// Render content with HTML or Markdown fallback
|
||||
const renderTextContent = useMemo(() => {
|
||||
return (content: string) => {
|
||||
// Try to render as HTML first
|
||||
if (isValidHTML(content)) {
|
||||
try {
|
||||
return (
|
||||
<div
|
||||
className='rb:prose rb:prose-sm rb:max-w-none'
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('HTML parsing failed, falling back to Markdown:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to Markdown rendering
|
||||
return <RbMarkdown content={content} showHtmlComments={true} />;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleItemClick = (e: React.MouseEvent, item: RecallTestData, index: number) => {
|
||||
// Check if the click is on an image or image-related element
|
||||
const target = e.target as HTMLElement;
|
||||
@@ -100,6 +131,20 @@ const RecallTestResult = ({
|
||||
}
|
||||
};
|
||||
|
||||
// Show skeleton when initial loading
|
||||
if (loading && data.length === 0) {
|
||||
return (
|
||||
<div className='rb:flex rb:flex-col'>
|
||||
<div className='rb:flex rb:items-center rb:justify-start rb:gap-2 rb:mb-4'>
|
||||
<span className='rb:text-lg rb:font-medium'>{t('knowledgeBase.recallResult')}</span>
|
||||
</div>
|
||||
<Skeleton active paragraph={{ rows: 3 }} />
|
||||
<Skeleton active paragraph={{ rows: 3 }} className='rb:mt-4' />
|
||||
<Skeleton active paragraph={{ rows: 3 }} className='rb:mt-4' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0 && showEmpty) {
|
||||
return (
|
||||
<NoData
|
||||
@@ -153,9 +198,9 @@ const RecallTestResult = ({
|
||||
const qaContent = parseQAContent(item.page_content);
|
||||
if (qaContent) {
|
||||
const formattedContent = formatQAContent(qaContent.question, qaContent.answer);
|
||||
return <RbMarkdown content={formattedContent} showHtmlComments={true} />;
|
||||
return renderTextContent(formattedContent);
|
||||
}
|
||||
return <RbMarkdown content={item.page_content} showHtmlComments={true} />;
|
||||
return renderTextContent(item.page_content);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @Author: yujiangping
|
||||
* @Date: 2025-11-10 18:52:55
|
||||
* @LastEditors: yujiangping
|
||||
* @LastEditTime: 2026-02-03 17:08:00
|
||||
* @LastEditTime: 2026-02-10 15:18:32
|
||||
*/
|
||||
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||
import { Switch } from 'antd';
|
||||
@@ -93,7 +93,7 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
|
||||
<>
|
||||
{contextHolder}
|
||||
<RbModal
|
||||
title={t('knowledgeBase.toWorkspace')}
|
||||
title={t('knowledgeBase.shareSpace')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('knowledgeBase.share')}
|
||||
@@ -101,8 +101,8 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<div className='rb:flex rb:flex-col rb:text-left'>
|
||||
<h4 className='rb:text-sm rb:font-medium rb:text-gray-800'>{t('knowledgeBase.shareTitle')}</h4>
|
||||
<span className='rb:text-xs rb:text-gray-500'>{t('knowledgeBase.shareNote')}</span>
|
||||
<h4 className='rb:text-sm rb:font-medium rb:text-gray-800'>{t('knowledgeBase.shareSpaceTitle')}</h4>
|
||||
<span className='rb:text-xs rb:text-gray-500'>{t('knowledgeBase.shareSpaceNote')}</span>
|
||||
<div className='rb:flex rb:flex-col rb:text-left rb:gap-4 rb:mt-4 '>
|
||||
{spaceList.length === 0 && (
|
||||
<NoData />
|
||||
|
||||
@@ -105,14 +105,14 @@ export interface ParserConfig {
|
||||
user_agent?: string; // 用户代理
|
||||
|
||||
// Third-party 类型特有字段
|
||||
third_party_platform?: 'yuque' | 'feishu'; // 第三方平台类型
|
||||
_third_party_platform?: 'yuque' | 'feishu'; // 第三方平台类型
|
||||
// 语雀字段
|
||||
yuque_user_id?: string; // 语雀用户ID
|
||||
yuque_token?: string; // 语雀Token
|
||||
// 飞书字段
|
||||
app_id?: string; // 飞书应用ID
|
||||
app_secret?: string; // 飞书应用密钥
|
||||
folder_token?: string; // 飞书文件夹Token
|
||||
feishu_app_id?: string; // 飞书应用ID
|
||||
feishu_app_secret?: string; // 飞书应用密钥
|
||||
feishu_folder_token?: string; // 飞书文件夹Token
|
||||
}
|
||||
// 文件数据
|
||||
export interface KnowledgeBaseDocumentData { // 知识库文档数据
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:30:11
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-04 10:08:49
|
||||
* @Last Modified time: 2026-02-09 21:04:14
|
||||
*/
|
||||
/**
|
||||
* Result Component
|
||||
@@ -21,7 +21,7 @@ import type { AnyObject } from 'antd/es/_util/type';
|
||||
import Card from './Card'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import RbAlert from '@/components/RbAlert'
|
||||
import type { TestResult } from '../types'
|
||||
import type { TestResult, OntologyCoverage } from '../types'
|
||||
import { pilotRunMemoryExtractionConfig } from '@/api/memory'
|
||||
import { type SSEMessage } from '@/utils/stream'
|
||||
import Tag, { type TagProps } from '@/components/Tag'
|
||||
@@ -78,6 +78,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
||||
const [knowledgeExtraction, setKnowledgeExtraction] = useState<ModuleItem>(initObj as ModuleItem)
|
||||
const [creatingNodesEdges, setCreatingNodesEdges] = useState<ModuleItem>(initObj as ModuleItem)
|
||||
const [deduplication, setDeduplication] = useState<ModuleItem>(initObj as ModuleItem)
|
||||
const [ontologyCoverage, setOntologyCoverage] = useState<OntologyCoverage>({} as OntologyCoverage)
|
||||
|
||||
const [runForm] = Form.useForm()
|
||||
|
||||
@@ -181,6 +182,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
||||
break
|
||||
case 'result': // Result
|
||||
setTestResult(data.data?.extracted_result)
|
||||
setOntologyCoverage(data.data?.ontology_coverage)
|
||||
break
|
||||
}
|
||||
})
|
||||
@@ -284,8 +286,8 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]!"
|
||||
>
|
||||
{knowledgeExtraction.data.map(vo =>
|
||||
<div key={vo.statement_index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{vo.statement}</div>
|
||||
{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)}
|
||||
{knowledgeExtraction.result && <RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
|
||||
@@ -450,6 +452,36 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
|
||||
</RbAlert>
|
||||
</RbCard>
|
||||
}
|
||||
{ontologyCoverage && Object.keys(ontologyCoverage).length > 0 &&
|
||||
<RbCard
|
||||
title={<>{t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})</>}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#369F21]!"
|
||||
>
|
||||
<div className="rb:grid rb:grid-cols-2 rb:gap-3">
|
||||
{(['scene_type_distribution', 'general_type_distribution', 'unmatched'] as const).map((key, idx) => {
|
||||
if (!ontologyCoverage[key]) return null
|
||||
return (
|
||||
<div key={idx} className="rb:text-[12px]">
|
||||
<div className="rb:text-[#369F21] rb:font-medium">{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})</div>
|
||||
<div>{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}</div>
|
||||
<div>
|
||||
{ontologyCoverage[key].types.map((type, index) => {
|
||||
if (!type.type || type.type === '') return null
|
||||
return (
|
||||
<div key={index} className="rb:text-[#5B6167] rb:font-regular rb:leading-4">
|
||||
-{type.type}({type.count})
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</RbCard>
|
||||
}
|
||||
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:29:55
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 17:29:55
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 20:56:31
|
||||
*/
|
||||
/**
|
||||
* Memory Extraction Engine Configuration Form Types
|
||||
@@ -106,4 +106,17 @@ export interface TestResult {
|
||||
predicate: string;
|
||||
object: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface OntologyCoverageItem {
|
||||
type_count: number;
|
||||
entity_total: number;
|
||||
types: Array<{ type: string; count: number; }>
|
||||
}
|
||||
export interface OntologyCoverage {
|
||||
scene_type_distribution: OntologyCoverageItem;
|
||||
general_type_distribution: OntologyCoverageItem;
|
||||
unmatched: OntologyCoverageItem;
|
||||
total_entities: number;
|
||||
time: number;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 14:10:24
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 14:10:56
|
||||
* @Last Modified time: 2026-02-09 18:02:13
|
||||
*/
|
||||
import { type FC, type ReactNode } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -45,7 +45,7 @@ const PageHeader: FC<ConfigHeaderProps> = ({
|
||||
}
|
||||
return (
|
||||
<Header className="rb:w-full rb:h-16 rb:flex rb:justify-between rb:p-[0_16px_0_24px]! rb:border-b rb:border-[#EAECEE] rb:leading-8">
|
||||
<div className="rb:flex rb:flex-col rb:justify-center rb:gap-1 rb:mr-4">
|
||||
<div className="rb:flex rb:flex-col rb:justify-center rb:gap-1 rb:mr-4 rb:max-w-[calc(100%-300px)]">
|
||||
<div className="rb:text-[16px] rb:leading-6 rb:font-medium">
|
||||
{name}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 14:10:20
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 14:10:20
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 17:56:35
|
||||
*/
|
||||
import { type FC, useEffect, useState, useRef } from 'react'
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -100,7 +100,7 @@ const Detail: FC = () => {
|
||||
<>
|
||||
<PageHeader
|
||||
name={data.scene_name}
|
||||
subTitle={<div>{data.scene_description}</div>}
|
||||
subTitle={<Tooltip title={data.scene_description}><div className="rb:h-4 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{data.scene_description}</div></Tooltip>}
|
||||
extra={<Space>
|
||||
<Button type="primary" ghost className="rb:h-6! rb:px-2! rb:leading-5.5!" onClick={handleAdd}>+ {t('ontology.addClass')}</Button>
|
||||
<Button className="rb:h-6! rb:px-2! rb:leading-5.5!" type="primary" onClick={handleExtract}>+ {t('ontology.extract')}</Button>
|
||||
|
||||
@@ -242,7 +242,7 @@ const EpisodicDetail: FC = () => {
|
||||
{detail.content_records.map((vo, index) => <div key={index} className="rb:text-[#5B6167] rb:leading-5">- {vo}</div>)}
|
||||
</div>
|
||||
<RbAlert>
|
||||
{t('episodicDetail.emotion')}: {t(`statementDetail.${detail.emotion}`)}
|
||||
{t('episodicDetail.emotion')}: {t(`episodicDetail.${detail.emotion || 'none'}`)}
|
||||
</RbAlert>
|
||||
</Space>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-06 21:10:56
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-06 21:10:56
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-10 12:17:41
|
||||
*/
|
||||
/**
|
||||
* Workflow Chat Component
|
||||
@@ -99,6 +99,8 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
||||
setChatList([])
|
||||
setVariables([])
|
||||
setConversationId(null)
|
||||
setMessage(undefined)
|
||||
setFileList([])
|
||||
}
|
||||
/**
|
||||
* Opens the variable configuration modal
|
||||
@@ -148,7 +150,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
||||
return
|
||||
}
|
||||
|
||||
// setLoading(true)
|
||||
setLoading(true)
|
||||
const message = msg
|
||||
setChatList(prev => [...prev, {
|
||||
role: 'user',
|
||||
@@ -284,6 +286,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
|
||||
return newList
|
||||
})
|
||||
setStreamLoading(false)
|
||||
setLoading(false)
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 15:39:59
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-05 14:21:45
|
||||
* @Last Modified time: 2026-02-09 19:56:42
|
||||
*/
|
||||
import { type FC, useEffect, useState, useMemo } from "react";
|
||||
import clsx from 'clsx'
|
||||
@@ -491,7 +491,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
|
||||
if (config.type === 'messageEditor') {
|
||||
return (
|
||||
<Form.Item key={key} name={key}>
|
||||
<Form.Item key={key} name={key} label={selectedNode?.data?.type === 'memory-write' ? t(`workflow.config.${selectedNode?.data?.type}.${key}`) : undefined }>
|
||||
<MessageEditor
|
||||
title={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||
isArray={!!config.isArray}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 15:06:18
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-09 17:48:46
|
||||
* @Last Modified time: 2026-02-09 20:08:03
|
||||
*/
|
||||
import LoopNode from './components/Nodes/LoopNode';
|
||||
import NormalNode from './components/Nodes/NormalNode';
|
||||
@@ -242,6 +242,12 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
type: 'editor',
|
||||
isArray: false
|
||||
},
|
||||
messages: {
|
||||
type: 'messageEditor',
|
||||
defaultValue: [],
|
||||
placeholder: 'workflow.config.llm.messagesPlaceholder',
|
||||
isArray: true
|
||||
},
|
||||
config_id: {
|
||||
type: 'customSelect',
|
||||
url: memoryConfigListUrl,
|
||||
|
||||
@@ -135,7 +135,10 @@ export const useWorkflowGraph = ({
|
||||
|
||||
if (nodeLibraryConfig?.config) {
|
||||
Object.keys(nodeLibraryConfig.config).forEach(key => {
|
||||
if (key === 'memory' && nodeLibraryConfig.config && nodeLibraryConfig.config[key]) {
|
||||
if (type === 'memory-write' && key === 'message' && nodeLibraryConfig.config) {
|
||||
nodeLibraryConfig.config['messages'].defaultValue = [{ role: 'USER', content: config[key] }]
|
||||
delete nodeLibraryConfig.config[key]
|
||||
} else if (key === 'memory' && nodeLibraryConfig.config && nodeLibraryConfig.config[key]) {
|
||||
const { memory, messages } = config as any;
|
||||
if (memory?.enable && messages && messages.length > 0) {
|
||||
const lastMessage = messages[messages.length - 1]
|
||||
|
||||
Reference in New Issue
Block a user