Merge branch 'develop' into feature/ui_upgrade_zy
This commit is contained in:
@@ -183,7 +183,7 @@ const TestChat: FC<TestChatProps> = ({
|
||||
|
||||
const handleSend = () => {
|
||||
if (loading || !application || !message || !message?.trim()) return
|
||||
const files = toolbarRef.current?.getFiles() || []
|
||||
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
|
||||
const variables = toolbarRef.current?.getVariables() || []
|
||||
const { isCanSend, params } = buildVariableParams(variables)
|
||||
if (!isCanSend) return
|
||||
@@ -235,7 +235,7 @@ const TestChat: FC<TestChatProps> = ({
|
||||
|
||||
const handleWorkflowSend = () => {
|
||||
if (loading || !application || !message || !message?.trim()) return
|
||||
const files = toolbarRef.current?.getFiles() || []
|
||||
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
|
||||
const variables = toolbarRef.current?.getVariables() || []
|
||||
const { isCanSend, params } = buildVariableParams(variables)
|
||||
if (!isCanSend) return
|
||||
|
||||
@@ -189,7 +189,7 @@ const Chat: FC<ChatProps> = ({
|
||||
.then(() => {
|
||||
const message = msg
|
||||
if (!message?.trim()) return
|
||||
const files = toolbarRef.current?.getFiles() || []
|
||||
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
|
||||
// Validate required variables before sending
|
||||
let isCanSend = true
|
||||
const params: Record<string, any> = {}
|
||||
@@ -350,7 +350,7 @@ const Chat: FC<ChatProps> = ({
|
||||
.then(() => {
|
||||
const message = msg
|
||||
if (!message || message.trim() === '') return
|
||||
const files = toolbarRef.current?.getFiles() || []
|
||||
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
|
||||
addUserMessage(message, files)
|
||||
setMessage(undefined)
|
||||
toolbarRef.current?.setFiles([])
|
||||
|
||||
@@ -24,7 +24,7 @@ interface FeaturesConfigModalProps {
|
||||
refresh: (value: FeaturesConfigForm) => void;
|
||||
source?: Application['type'];
|
||||
}
|
||||
|
||||
const max_file_count = 1;
|
||||
/**
|
||||
* Modal for copying applications
|
||||
*/
|
||||
@@ -133,7 +133,7 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
|
||||
</div>
|
||||
<div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:py-1">{t('application.maxCount')}</div>
|
||||
{fu.max_file_count} {t('application.unix')}
|
||||
{max_file_count} {t('application.unix')}
|
||||
</div>
|
||||
</Flex>
|
||||
<Button block onClick={handleOpenSettings}>{t('application.setting')}</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-03-05
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-19 15:18:20
|
||||
* @Last Modified time: 2026-03-19 20:19:14
|
||||
*/
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd';
|
||||
@@ -82,28 +82,27 @@ const defaultValues: FileUpload = {
|
||||
"mp3",
|
||||
"wav",
|
||||
"m4a",
|
||||
"ogg",
|
||||
"flac"
|
||||
],
|
||||
document_enabled: false,
|
||||
document_max_size_mb: 100,
|
||||
document_allowed_extensions: [
|
||||
"pdf",
|
||||
"docx",
|
||||
"doc",
|
||||
"xlsx",
|
||||
"xls",
|
||||
"txt",
|
||||
"csv",
|
||||
"json"
|
||||
"json",
|
||||
"md",
|
||||
],
|
||||
video_enabled: false,
|
||||
video_max_size_mb: 100,
|
||||
video_allowed_extensions: [
|
||||
"mp4",
|
||||
"mov",
|
||||
"avi",
|
||||
"webm"
|
||||
],
|
||||
max_file_count: 5,
|
||||
max_file_count: 1,
|
||||
allowed_transfer_methods: 'both'
|
||||
}
|
||||
|
||||
@@ -168,8 +167,8 @@ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadS
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mb-1">{t('application.maxCount')}</div>
|
||||
<Form.Item label={t('application.maxCount')} name="max_file_count">
|
||||
{/* <div className="rb:text-[12px] rb:text-[#5B6167] rb:mb-1">{t('application.maxCount')}</div> */}
|
||||
<Form.Item label={t('application.maxCount')} name="max_file_count" hidden>
|
||||
<InputNumber min={1} max={20} precision={0} className="rb:w-full!" placeholder={t('common.pleaseEnter')} />
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { useState, useEffect, forwardRef, useImperativeHandle, useMemo } from 'react';
|
||||
import { Upload, Progress, App, Flex } from 'antd';
|
||||
import type { UploadProps, UploadFile } from 'antd';
|
||||
import type { UploadProps as RcUploadProps } from 'antd/es/upload/interface';
|
||||
import type { UploadProps as RcUploadProps, RcFile, UploadFileStatus } from 'antd/es/upload/interface';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { request } from '@/utils/request'
|
||||
@@ -221,17 +221,29 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
*/
|
||||
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);
|
||||
if (typeof file === 'string') return;
|
||||
const rcFile = file as RcFile;
|
||||
const formData = new FormData();
|
||||
formData.append('file', rcFile);
|
||||
const fileVo: UploadFile = {
|
||||
uid: rcFile.uid,
|
||||
name: rcFile.name,
|
||||
status: 'uploading' as UploadFileStatus,
|
||||
percent: 0,
|
||||
type: rcFile.type,
|
||||
originFileObj: rcFile,
|
||||
thumbUrl: URL.createObjectURL(rcFile)
|
||||
}
|
||||
onChange?.(fileVo)
|
||||
request.uploadFile(action, formData, requestConfig)
|
||||
.then(res => {
|
||||
onSuccess?.({ data: res });
|
||||
})
|
||||
.catch((error) => {
|
||||
onError?.(error as Error);
|
||||
fileVo.status = 'error'
|
||||
onChange?.(fileVo)
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-06 21:09:47
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-18 21:10:01
|
||||
* @Last Modified time: 2026-03-19 20:32:32
|
||||
*/
|
||||
/**
|
||||
* Upload File List Modal Component
|
||||
@@ -19,7 +19,10 @@
|
||||
* @component
|
||||
*/
|
||||
import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';
|
||||
import { Form, Input, Select, Button, Flex } from 'antd';
|
||||
import { Form, Input, Select,
|
||||
// Button,
|
||||
Flex
|
||||
} from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { UploadFileListModalRef } from '../types'
|
||||
@@ -105,9 +108,11 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form form={form} layout="vertical" initialValues={{ files: [{ type: undefined, url: undefined }] }}>
|
||||
<Form.List name="files">
|
||||
{(fields, { add, remove }) => (
|
||||
{(fields,
|
||||
// { add, remove }
|
||||
) => (
|
||||
<>
|
||||
{/* Render each file entry with type selector and URL input */}
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
@@ -116,6 +121,9 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
|
||||
{...restField}
|
||||
name={[name, 'type']}
|
||||
className="rb:mb-0!"
|
||||
rules={[
|
||||
{ required: true, message: t('common.pleaseSelect') }
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
placeholder={t('memoryConversation.fileType')}
|
||||
@@ -126,22 +134,25 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
|
||||
<FormItem
|
||||
{...restField}
|
||||
name={[name, 'url']}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
rules={[
|
||||
{ required: true, message: t('common.pleaseEnter') },
|
||||
{ type: 'url', message: t('common.callbackUrlInvalid') },
|
||||
]}
|
||||
className="rb:mb-0! rb:flex-1!"
|
||||
>
|
||||
<Input placeholder={t('memoryConversation.fileUrl')} />
|
||||
</FormItem>
|
||||
<div
|
||||
{/* <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>
|
||||
></div> */}
|
||||
</Flex>
|
||||
))}
|
||||
<Form.Item noStyle>
|
||||
{/* <Form.Item noStyle>
|
||||
<Button type="dashed" onClick={() => add()} block>
|
||||
+ {t('common.add')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
|
||||
@@ -194,7 +194,7 @@ const Conversation: FC = () => {
|
||||
/** Send message and handle streaming response */
|
||||
const handleSend = () => {
|
||||
if (!token || !shareToken) return
|
||||
const files = toolbarRef.current?.getFiles() || []
|
||||
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
|
||||
const variables = toolbarRef.current?.getVariables() || []
|
||||
let isCanSend = true
|
||||
const params: Record<string, any> = {}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager';
|
||||
import { Button, Spin, message, Switch } from 'antd';
|
||||
import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '@/api/knowledgeBase';
|
||||
import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk, getFileUrl } from '@/api/knowledgeBase';
|
||||
import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types';
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
import InfoPanel, { type InfoItem } from '../components/InfoPanel';
|
||||
@@ -138,7 +138,7 @@ const DocumentDetails: FC = () => {
|
||||
const response = await getDocumentDetail(documentId);
|
||||
setDocument(response);
|
||||
setInfoItems(formatDocumentInfo(response));
|
||||
const url = `${imagePath}/api/files/${response.file_id}`
|
||||
const url = `${window.location.origin}/api/files/${response.file_id}`;
|
||||
setFileUrl(url);
|
||||
setParserMode(response?.parser_config?.auto_questions || 0)
|
||||
// ChunkList will be called automatically in useEffect based on document.progress
|
||||
|
||||
@@ -191,24 +191,28 @@ const RelationshipNetwork: FC = () => {
|
||||
})}>
|
||||
{(selectedNode as RawCommunityNode).properties.community_id
|
||||
? <div>
|
||||
<div className="rb:font-medium rb:text-[#212332] rb:text-[16px] rb:leading-5.5 rb:pl-1">
|
||||
{(selectedNode as RawCommunityNode).properties.name}
|
||||
</div>
|
||||
<div className="rb:mt-3 rb:font-medium rb:leading-5 rb:pl-1">{t('userMemory.summary')}</div>
|
||||
<div className="rb:bg-[#F6F6F6] rb:rounded-xl rb:px-3 rb:py-2.5 rb:mt-2">
|
||||
{(selectedNode as RawCommunityNode).properties.summary}
|
||||
</div>
|
||||
<Flex align="center" justify="space-between" className="rb:mt-5!">
|
||||
<span className="rb:text-[#5B6167] rb:font-regular rb:pl-1">{t('userMemory.member_count')}</span>
|
||||
<span className="rb:font-medium">{(selectedNode as RawCommunityNode).properties.member_count}{t('userMemory.member_count_desc')}</span>
|
||||
</Flex>
|
||||
<div className="rb:font-medium rb:text-[#212332] rb:text-[16px] rb:leading-5.5 rb:pl-1">
|
||||
{(selectedNode as RawCommunityNode).properties.name || selectedNode.id}
|
||||
</div>
|
||||
{(selectedNode as RawCommunityNode).properties.summary && <>
|
||||
<div className="rb:mt-3 rb:font-medium rb:leading-5 rb:pl-1">{t('userMemory.summary')}</div>
|
||||
<div className="rb:bg-[#F6F6F6] rb:rounded-xl rb:px-3 rb:py-2.5 rb:mt-2">
|
||||
{(selectedNode as RawCommunityNode).properties.summary}
|
||||
</div>
|
||||
</>}
|
||||
<Flex align="center" justify="space-between" className="rb:mt-5!">
|
||||
<span className="rb:text-[#5B6167] rb:font-regular rb:pl-1">{t('userMemory.member_count')}</span>
|
||||
<span className="rb:font-medium">{(selectedNode as RawCommunityNode).properties.member_count}{t('userMemory.member_count_desc')}</span>
|
||||
</Flex>
|
||||
|
||||
<Divider className='rb:my-2.5!' />
|
||||
<div className="rb:font-medium rb:leading-5 rb:pl-1">{t('userMemory.core_entities')}</div>
|
||||
<ul className="rb:list-disc rb:pl-4 rb:text-[#5B6167] rb:mt-2">
|
||||
{(selectedNode as RawCommunityNode).properties.core_entities.map((entity, index) => <li key={index}>{entity}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
{(selectedNode as RawCommunityNode).properties.core_entities && <>
|
||||
<Divider className='rb:my-2.5!' />
|
||||
<div className="rb:font-medium rb:leading-5 rb:pl-1">{t('userMemory.core_entities')}</div>
|
||||
<ul className="rb:list-disc rb:pl-4 rb:text-[#5B6167] rb:mt-2">
|
||||
{(selectedNode as RawCommunityNode).properties.core_entities?.map((entity, index) => <li key={index}>{entity}</li>)}
|
||||
</ul>
|
||||
</>}
|
||||
</div>
|
||||
: <>
|
||||
{(selectedNode as Node).name &&
|
||||
<div className="rb:font-medium rb:text-[16px] rb:text-[#212332] rb:leading-5.5 rb:mb-3">
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-16 15:10:17
|
||||
*/
|
||||
import { type FC, useEffect, useState, useMemo } from 'react'
|
||||
import { type FC, useEffect, useState, useMemo, useRef } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Row, Col, Skeleton, Button, Divider, Tooltip, Flex } from 'antd'
|
||||
|
||||
|
||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import {
|
||||
getConversations,
|
||||
@@ -61,6 +63,8 @@ const WorkingDetail: FC = () => {
|
||||
const { id } = useParams()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [data, setData] = useState<Conversation[]>([])
|
||||
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||
const pageRef = useRef<number>(1)
|
||||
const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
|
||||
const [messages, setMessages] = useState<ChatItem[]>([])
|
||||
const [detailLoading, setDetailLoading] = useState<boolean>(false)
|
||||
@@ -80,17 +84,30 @@ const WorkingDetail: FC = () => {
|
||||
setSelected(null)
|
||||
setDetail(null)
|
||||
setData([])
|
||||
getConversations(id).then((res) => {
|
||||
const response = res as Conversation[]
|
||||
setData(response)
|
||||
setSelected(response[0] || null)
|
||||
setHasMore(true)
|
||||
pageRef.current = 1
|
||||
getConversations(id, 1).then((res) => {
|
||||
const response = res as { items: Conversation[], page: { hasnext: boolean } }
|
||||
setData(response.items)
|
||||
setSelected(response.items[0] || null)
|
||||
setHasMore(response.page.hasnext)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
/* Load messages and AI insight whenever the selected conversation changes. */
|
||||
const loadMore = () => {
|
||||
if (!id) return
|
||||
const nextPage = pageRef.current + 1
|
||||
getConversations(id, nextPage).then((res) => {
|
||||
const response = res as {items: Conversation[], page: { hasnext: boolean }}
|
||||
setData(prev => [...prev, ...response.items])
|
||||
pageRef.current = nextPage
|
||||
setHasMore(response.page.hasnext)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!id || !selected || !selected.id) return
|
||||
getDetail(selected.id)
|
||||
@@ -138,16 +155,16 @@ const WorkingDetail: FC = () => {
|
||||
: data.length === 0
|
||||
? <Empty />
|
||||
:(
|
||||
<Row gutter={16} className="rb:h-full">
|
||||
<Col flex='360px' className="rb:h-full">
|
||||
<RbCard
|
||||
title={t('workingDetail.conversation')}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold"
|
||||
bodyClassName='rb:p-3! rb:pt-0! rb:h-[calc(100%-54px)]'
|
||||
className="rb:h-full!"
|
||||
>
|
||||
<Flex gap={8} vertical>
|
||||
<Row gutter={16}>
|
||||
<Col span={5}>
|
||||
<div id="conversation-list" className="rb:h-[calc(100vh-76px)]! rb:border-r rb:border-[#EAECEE] rb:py-3 rb:px-4 rb:overflow-y-auto">
|
||||
<InfiniteScroll
|
||||
dataLength={data.length}
|
||||
next={loadMore}
|
||||
hasMore={hasMore}
|
||||
loader={null}
|
||||
scrollableTarget="conversation-list"
|
||||
>
|
||||
{data.map(item => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
@@ -166,8 +183,8 @@ const WorkingDetail: FC = () => {
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</RbCard>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</Col>
|
||||
{selected && <>
|
||||
<Col flex="auto" className="rb:h-full">
|
||||
|
||||
@@ -151,7 +151,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef; data: Work
|
||||
|
||||
setLoading(true)
|
||||
const message = msg
|
||||
const files = toolbarRef.current?.getFiles() || []
|
||||
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
|
||||
setChatList(prev => [...prev, {
|
||||
role: 'user',
|
||||
content: message,
|
||||
|
||||
@@ -18,8 +18,8 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
|
||||
const isUserInputRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// 监听编辑器变化,标记是否为用户输入
|
||||
const removeListener = editor.registerUpdateListener(({ editorState }) => {
|
||||
const removeListener = editor.registerUpdateListener(({ editorState, tags }) => {
|
||||
if (tags.has('programmatic')) return;
|
||||
editorState.read(() => {
|
||||
const root = $getRoot();
|
||||
const textContent = root.getTextContent();
|
||||
@@ -107,7 +107,7 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
|
||||
});
|
||||
root.append(paragraph);
|
||||
}
|
||||
}, { discrete: true });
|
||||
}, { discrete: true, tag: 'programmatic' });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user