Merge pull request #628 from SuanmoSuanyangTechnology/fix/v0.2.8_zy
Fix/v0.2.8 zy
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 14:00:06
|
* @Date: 2026-02-03 14:00:06
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-13 10:48:41
|
* @Last Modified time: 2026-03-19 18:35:10
|
||||||
*/
|
*/
|
||||||
import { request } from '@/utils/request'
|
import { request } from '@/utils/request'
|
||||||
import type { AxiosRequestConfig } from 'axios'
|
import type { AxiosRequestConfig } from 'axios'
|
||||||
@@ -218,8 +218,8 @@ export const getExplicitMemory = (end_user_id: string) => {
|
|||||||
export const getExplicitMemoryDetails = (data: { end_user_id: string, memory_id: string; }) => {
|
export const getExplicitMemoryDetails = (data: { end_user_id: string, memory_id: string; }) => {
|
||||||
return request.post(`/memory/explicit-memory/details`, data)
|
return request.post(`/memory/explicit-memory/details`, data)
|
||||||
}
|
}
|
||||||
export const getConversations = (end_user_id: string) => {
|
export const getConversations = (end_user_id: string, page = 1, pagesize = 20) => {
|
||||||
return request.get(`/memory/work/${end_user_id}/conversations`)
|
return request.get(`/memory/work/${end_user_id}/conversations`, { page, pagesize })
|
||||||
}
|
}
|
||||||
export const getConversationMessages = (end_user_id: string, conversation_id: string) => {
|
export const getConversationMessages = (end_user_id: string, conversation_id: string) => {
|
||||||
return request.get(`/memory/work/${end_user_id}/messages`, { conversation_id })
|
return request.get(`/memory/work/${end_user_id}/messages`, { conversation_id })
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2025-12-10 16:46:14
|
* @Date: 2025-12-10 16:46:14
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-19 16:05:56
|
* @Last Modified time: 2026-03-19 18:44:51
|
||||||
*/
|
*/
|
||||||
import { type FC, useEffect, useMemo } from 'react'
|
import { type FC, useEffect, useMemo } from 'react'
|
||||||
import { Flex, Input, Form } from 'antd'
|
import { Flex, Input, Form, Spin } from 'antd'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import SendIcon from '@/assets/images/conversation/send.svg'
|
import SendIcon from '@/assets/images/conversation/send.svg'
|
||||||
import SendDisabledIcon from '@/assets/images/conversation/sendDisabled.svg'
|
import SendDisabledIcon from '@/assets/images/conversation/sendDisabled.svg'
|
||||||
@@ -69,6 +70,8 @@ const ChatInput: FC<ChatInputProps> = ({
|
|||||||
onSend(values.message)
|
onSend(values.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('previewFileList', previewFileList)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`rb:absolute rb:bottom-3 rb:left-0 rb:right-0 rb:w-full ${className}`}>
|
<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">
|
<Flex vertical justify="space-between" className="rb:border rb:border-[#DFE4ED] rb:rounded-xl rb:min-h-30">
|
||||||
@@ -76,62 +79,78 @@ const ChatInput: FC<ChatInputProps> = ({
|
|||||||
{previewFileList.map((file) => {
|
{previewFileList.map((file) => {
|
||||||
if (file.type.includes('image')) {
|
if (file.type.includes('image')) {
|
||||||
return (
|
return (
|
||||||
<div key={file.url || file.uid} className="rb:inline-block rb:group rb:relative rb:rounded-lg">
|
<Spin key={`${file.url || file.uid}_${file.status}`} spinning={file.status === 'uploading'}>
|
||||||
<img src={file.url} alt={file.name} className="rb:size-12! rb:rounded-lg rb:object-cover rb:cursor-pointer" />
|
<div key={file.url || file.uid} className={clsx("rb:inline-block rb:group rb:relative rb:rounded-lg", {
|
||||||
<div
|
'rb:border rb:border-[#FF5D34]': file.status === 'error'
|
||||||
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
})}>
|
||||||
onClick={() => handleDelete(file)}
|
<img src={file.url} alt={file.name} className="rb:size-12! rb:rounded-lg rb:object-cover rb:cursor-pointer" />
|
||||||
></div>
|
<div
|
||||||
</div>
|
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
||||||
|
onClick={() => handleDelete(file)}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (file.type.includes('video')) {
|
if (file.type.includes('video')) {
|
||||||
return (
|
return (
|
||||||
<div key={file.url || file.uid} className="rb:w-45 rb:h-16 rb:inline-block rb:group rb:relative rb:rounded-lg">
|
<Spin key={`${file.url || file.uid}_${file.status}`} spinning={file.status === 'uploading'}>
|
||||||
<video src={file.url} controls className="rb:w-45 rb:h-16 rb:rounded-lg rb:object-cover rb:cursor-pointer" />
|
<div key={file.url || file.uid} className={clsx("rb:w-45 rb:h-16 rb:inline-block rb:group rb:relative rb:rounded-lg", {
|
||||||
<div
|
'rb:border rb:border-[#FF5D34]': file.status === 'error'
|
||||||
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
})}>
|
||||||
onClick={() => handleDelete(file)}
|
<video src={file.url} controls className="rb:w-45 rb:h-15.5 rb:rounded-lg rb:object-cover rb:cursor-pointer" />
|
||||||
></div>
|
<div
|
||||||
</div>
|
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
||||||
|
onClick={() => handleDelete(file)}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (file.type.includes('audio')) {
|
if (file.type.includes('audio')) {
|
||||||
return (
|
return (
|
||||||
<div key={file.url || file.uid} className="rb:w-45 rb:h-16 rb:inline-flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5 rb:gap-2">
|
<Spin key={`${file.url || file.uid}_${file.status}`} spinning={file.status === 'uploading'}>
|
||||||
<audio src={file.url} controls className="rb:w-45 rb:h-16" />
|
<div key={file.url || file.uid} className={clsx("rb:w-45 rb:h-16 rb:inline-flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5 rb:gap-2", {
|
||||||
|
'rb:border rb:border-[#FF5D34]': file.status === 'error'
|
||||||
|
})}>
|
||||||
|
<audio src={file.url} controls className="rb:w-45 rb:h-15.5" />
|
||||||
|
<div
|
||||||
|
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
||||||
|
onClick={() => handleDelete(file)}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Spin key={`${file.url || file.uid}_${file.status}`} spinning={file.status === 'uploading'}>
|
||||||
|
<div key={file.url || file.uid} className={clsx("rb:w-45 rb:text-[12px] rb:gap-2.5 rb:flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5", {
|
||||||
|
'rb:border rb:border-[#FF5D34]': file.status === 'error'
|
||||||
|
})}>
|
||||||
|
{file.type.includes('pdf')
|
||||||
|
? <div
|
||||||
|
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/pdf_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/pdf.svg')]"
|
||||||
|
></div>
|
||||||
|
: (file.type.includes('excel') || file.type.includes('spreadsheetml.sheet') || file.type.includes('csv'))
|
||||||
|
? <div
|
||||||
|
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/excel_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/excel.svg')]"
|
||||||
|
></div>
|
||||||
|
: (file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document'))
|
||||||
|
? <div
|
||||||
|
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/word_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/word.svg')]"
|
||||||
|
></div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div className="rb:flex-1 rb:w-32.5">
|
||||||
|
<div className="rb:leading-4 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.name}</div>
|
||||||
|
<div className="rb:leading-3.5 rb:mt-0.5 rb:text-[#5B6167] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.type} · {file.size}</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
||||||
onClick={() => handleDelete(file)}
|
onClick={() => handleDelete(file)}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
)
|
</Spin>
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div key={file.url || file.uid} className="rb:w-45 rb:text-[12px] rb:gap-2.5 rb:flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5">
|
|
||||||
{file.type.includes('pdf')
|
|
||||||
? <div
|
|
||||||
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/pdf_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/pdf.svg')]"
|
|
||||||
></div>
|
|
||||||
: (file.type.includes('excel') || file.type.includes('spreadsheetml.sheet') || file.type.includes('csv'))
|
|
||||||
? <div
|
|
||||||
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/excel_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/excel.svg')]"
|
|
||||||
></div>
|
|
||||||
: (file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document'))
|
|
||||||
? <div
|
|
||||||
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/word_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/word.svg')]"
|
|
||||||
></div>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
<div className="rb:flex-1 rb:w-32.5">
|
|
||||||
<div className="rb:leading-4 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.name}</div>
|
|
||||||
<div className="rb:leading-3.5 rb:mt-0.5 rb:text-[#5B6167] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{file.type} · {file.size}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
|
||||||
onClick={() => handleDelete(file)}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Flex></div>}
|
</Flex></div>}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-03-17 14:22:25
|
* @Date: 2026-03-17 14:22:25
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-18 15:55:13
|
* @Last Modified time: 2026-03-19 18:59:37
|
||||||
*/
|
*/
|
||||||
// Toolbar component for chat input area, supporting file upload, audio recording, and variable configuration
|
// Toolbar component for chat input area, supporting file upload, audio recording, and variable configuration
|
||||||
import { useRef, forwardRef, useImperativeHandle, type ReactNode, useEffect } from 'react'
|
import { useRef, forwardRef, useImperativeHandle, type ReactNode, useEffect } from 'react'
|
||||||
@@ -85,10 +85,18 @@ const ChatToolbar = forwardRef<ChatToolbarRef, ChatToolbarProps>(({
|
|||||||
|
|
||||||
// Append newly uploaded file to the file list when upload is complete
|
// Append newly uploaded file to the file list when upload is complete
|
||||||
const fileChange = (file?: any) => {
|
const fileChange = (file?: any) => {
|
||||||
if (file?.status !== 'done') return
|
console.log('file', file)
|
||||||
const files = [...(queryValues?.files || []), file]
|
const lastFiles = form.getFieldValue('files') || [];
|
||||||
form.setFieldValue('files', files)
|
const index = lastFiles.findIndex((item: any) => item.uid === file.uid)
|
||||||
onFilesChange?.(files)
|
if (index > -1) {
|
||||||
|
lastFiles[index] = file
|
||||||
|
} else {
|
||||||
|
lastFiles.push(file)
|
||||||
|
}
|
||||||
|
form.setFieldValue('files', [...lastFiles])
|
||||||
|
onFilesChange?.([...lastFiles])
|
||||||
|
|
||||||
|
console.log('lastFiles', lastFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append recorded audio file to the file list and notify parent
|
// Append recorded audio file to the file list and notify parent
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ const TestChat: FC<TestChatProps> = ({
|
|||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (loading || !application || !message || !message?.trim()) return
|
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 variables = toolbarRef.current?.getVariables() || []
|
||||||
const { isCanSend, params } = buildVariableParams(variables)
|
const { isCanSend, params } = buildVariableParams(variables)
|
||||||
if (!isCanSend) return
|
if (!isCanSend) return
|
||||||
@@ -235,7 +235,7 @@ const TestChat: FC<TestChatProps> = ({
|
|||||||
|
|
||||||
const handleWorkflowSend = () => {
|
const handleWorkflowSend = () => {
|
||||||
if (loading || !application || !message || !message?.trim()) return
|
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 variables = toolbarRef.current?.getVariables() || []
|
||||||
const { isCanSend, params } = buildVariableParams(variables)
|
const { isCanSend, params } = buildVariableParams(variables)
|
||||||
if (!isCanSend) return
|
if (!isCanSend) return
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ const Chat: FC<ChatProps> = ({
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
const message = msg
|
const message = msg
|
||||||
if (!message?.trim()) return
|
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
|
// Validate required variables before sending
|
||||||
let isCanSend = true
|
let isCanSend = true
|
||||||
const params: Record<string, any> = {}
|
const params: Record<string, any> = {}
|
||||||
@@ -352,7 +352,7 @@ const Chat: FC<ChatProps> = ({
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
const message = msg
|
const message = msg
|
||||||
if (!message || message.trim() === '') return
|
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)
|
addUserMessage(message, files)
|
||||||
setMessage(undefined)
|
setMessage(undefined)
|
||||||
toolbarRef.current?.setFiles([])
|
toolbarRef.current?.setFiles([])
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-06 21:09:42
|
* @Date: 2026-02-06 21:09:42
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-18 20:32:54
|
* @Last Modified time: 2026-03-19 18:38:41
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* File Upload Component
|
* File Upload Component
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
import { useState, useEffect, forwardRef, useImperativeHandle, useMemo } from 'react';
|
import { useState, useEffect, forwardRef, useImperativeHandle, useMemo } from 'react';
|
||||||
import { Upload, Progress, App } from 'antd';
|
import { Upload, Progress, App } from 'antd';
|
||||||
import type { UploadProps, UploadFile } 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 { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { request } from '@/utils/request'
|
import { request } from '@/utils/request'
|
||||||
@@ -221,17 +221,29 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
|||||||
*/
|
*/
|
||||||
const handleCustomRequest: RcUploadProps['customRequest'] = async (options) => {
|
const handleCustomRequest: RcUploadProps['customRequest'] = async (options) => {
|
||||||
const { file, onSuccess, onError } = options;
|
const { file, onSuccess, onError } = options;
|
||||||
|
if (typeof file === 'string') return;
|
||||||
try {
|
const rcFile = file as RcFile;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', rcFile);
|
||||||
|
const fileVo: UploadFile = {
|
||||||
const response = await request.uploadFile(action, formData, requestConfig);
|
uid: rcFile.uid,
|
||||||
|
name: rcFile.name,
|
||||||
onSuccess?.({data: response});
|
status: 'uploading' as UploadFileStatus,
|
||||||
} catch (error) {
|
percent: 0,
|
||||||
onError?.(error as Error);
|
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)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ const Conversation: FC = () => {
|
|||||||
/** Send message and handle streaming response */
|
/** Send message and handle streaming response */
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (!token || !shareToken) return
|
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() || []
|
const variables = toolbarRef.current?.getVariables() || []
|
||||||
let isCanSend = true
|
let isCanSend = true
|
||||||
const params: Record<string, any> = {}
|
const params: Record<string, any> = {}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { type FC, useEffect, useState, useMemo } from 'react'
|
import { type FC, useEffect, useState, useMemo, useRef } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { Row, Col, Skeleton, Button, Divider, Tooltip } from 'antd'
|
import { Row, Col, Skeleton, Button, Divider, Tooltip } from 'antd'
|
||||||
|
|
||||||
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import {
|
import {
|
||||||
getConversations,
|
getConversations,
|
||||||
@@ -34,6 +36,8 @@ const WorkingDetail: FC = () => {
|
|||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [data, setData] = useState<Conversation[]>([])
|
const [data, setData] = useState<Conversation[]>([])
|
||||||
|
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||||
|
const pageRef = useRef<number>(1)
|
||||||
const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
|
const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
|
||||||
const [messages, setMessages] = useState<ChatItem[]>([])
|
const [messages, setMessages] = useState<ChatItem[]>([])
|
||||||
const [detailLoading, setDetailLoading] = useState<boolean>(false)
|
const [detailLoading, setDetailLoading] = useState<boolean>(false)
|
||||||
@@ -51,16 +55,30 @@ const WorkingDetail: FC = () => {
|
|||||||
setSelected(null)
|
setSelected(null)
|
||||||
setDetail(null)
|
setDetail(null)
|
||||||
setData([])
|
setData([])
|
||||||
getConversations(id).then((res) => {
|
setHasMore(true)
|
||||||
const response = res as Conversation[]
|
pageRef.current = 1
|
||||||
setData(response)
|
getConversations(id, 1).then((res) => {
|
||||||
setSelected(response[0] || null)
|
const response = res as { items: Conversation[], page: { hasnext: boolean } }
|
||||||
|
setData(response.items)
|
||||||
|
setSelected(response.items[0] || null)
|
||||||
|
setHasMore(response.page.hasnext)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (!id || !selected || !selected.id) return
|
if (!id || !selected || !selected.id) return
|
||||||
getDetail(selected.id)
|
getDetail(selected.id)
|
||||||
@@ -103,22 +121,30 @@ const WorkingDetail: FC = () => {
|
|||||||
: data.length === 0
|
: data.length === 0
|
||||||
? <Empty />
|
? <Empty />
|
||||||
:(
|
:(
|
||||||
<Row gutter={16} className="rb:h-full">
|
<Row gutter={16}>
|
||||||
<Col span={5}>
|
<Col span={5}>
|
||||||
<div className="rb:h-full! rb:border-r rb:border-[#EAECEE] rb:py-3 rb:px-4">
|
<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">
|
||||||
{data.map(item => (
|
<InfiniteScroll
|
||||||
<div key={item.id} className="rb:mb-3">
|
dataLength={data.length}
|
||||||
<Tooltip title={item.title}>
|
next={loadMore}
|
||||||
<div className={clsx("rb:p-[8px_13px] rb:rounded-lg rb:leading-5 rb:cursor-pointer rb:hover:bg-[#F0F3F8] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap", {
|
hasMore={hasMore}
|
||||||
'rb:bg-[#FFFFFF] rb:shadow-[0px_2px_4px_0px_rgba(0,0,0,0.15)] rb:font-medium rb:hover:bg-[#FFFFFF]!': item.id === selected?.id,
|
loader={null}
|
||||||
})}
|
scrollableTarget="conversation-list"
|
||||||
onClick={() => setSelected(item)}
|
>
|
||||||
>
|
{data.map(item => (
|
||||||
{item.title}
|
<div key={item.id} className="rb:mb-3">
|
||||||
</div>
|
<Tooltip title={item.title}>
|
||||||
</Tooltip>
|
<div className={clsx("rb:p-[8px_13px] rb:rounded-lg rb:leading-5 rb:cursor-pointer rb:hover:bg-[#F0F3F8] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap", {
|
||||||
</div>
|
'rb:bg-[#FFFFFF] rb:shadow-[0px_2px_4px_0px_rgba(0,0,0,0.15)] rb:font-medium rb:hover:bg-[#FFFFFF]!': item.id === selected?.id,
|
||||||
))}
|
})}
|
||||||
|
onClick={() => setSelected(item)}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</InfiniteScroll>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
{selected && <>
|
{selected && <>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-06 21:10:56
|
* @Date: 2026-02-06 21:10:56
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-18 20:46:35
|
* @Last Modified time: 2026-03-19 18:41:07
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Workflow Chat Component
|
* Workflow Chat Component
|
||||||
@@ -151,7 +151,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef; data: Work
|
|||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const message = msg
|
const message = msg
|
||||||
const files = toolbarRef.current?.getFiles() || []
|
const files = (toolbarRef.current?.getFiles() || []).filter(item => !['uploading', 'error'].includes(item.status))
|
||||||
setChatList(prev => [...prev, {
|
setChatList(prev => [...prev, {
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: message,
|
content: message,
|
||||||
|
|||||||
Reference in New Issue
Block a user