Compare commits

...

5 Commits

Author SHA1 Message Date
ycz
370c8bc25b Update llm_factories.json (#9714)
### What problem does this PR solve?

add ZhipuAI GLM-4.5 model series

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-26 11:49:01 +08:00
e90a959b4d Fix: Chunk error when re-parsing created file #9665 (#9711)
### What problem does this PR solve?

Fix: Chunk error when re-parsing created file

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-26 10:50:30 +08:00
ca320a8c30 Refactor: for total_token_count method use if to check first. (#9707)
### What problem does this PR solve?

for total_token_count method use if to check first, to improve the
performance when we need to handle exception cases

### Type of change

- [x] Refactoring
2025-08-26 10:47:20 +08:00
ae505e6165 Fix: Optimize table style #3221 (#9703)
### What problem does this PR solve?

Fix: Optimize table style
-Modify the style of the table scrollbar and remove unnecessary
scrollbars
-Adjust the header style of the table, add background color and
hierarchy
-Optimize the style of datasets and file tables
-Add a new background color variable

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-26 10:46:54 +08:00
63b5c2292d Fix: Delete the uploaded file in the chat input box, the corresponding file ID is not deleted #9701 (#9702)
### What problem does this PR solve?

Fix: Delete the uploaded file in the chat input box, the corresponding
file ID is not deleted #9701
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-26 09:27:49 +08:00
17 changed files with 295 additions and 123 deletions

View File

@ -532,6 +532,48 @@
"tags": "LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION",
"status": "1",
"llm": [
{
"llm_name": "glm-4.5v",
"tags": "LLM,CHAT,IMAGE2TEXT,",
"max_tokens": 128000,
"model_type": "image2text",
"is_tools": true
},
{
"llm_name": "glm-4.5",
"tags": "LLM,CHAT,128K,",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-x",
"tags": "LLM,CHAT,128K,",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-air",
"tags": "LLM,CHAT,128K,",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-airx",
"tags": "LLM,CHAT,128K,",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-flash",
"tags": "LLM,CHAT,128K,",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4-plus",
"tags": "LLM,CHAT,",

View File

@ -44,14 +44,17 @@ class Base(ABC):
raise NotImplementedError("Please implement encode method!")
def total_token_count(self, resp):
try:
return resp.usage.total_tokens
except Exception:
pass
try:
return resp["usage"]["total_tokens"]
except Exception:
pass
if hasattr(resp, "usage") and hasattr(resp.usage, "total_tokens"):
try:
return resp.usage.total_tokens
except Exception:
pass
if 'usage' in resp and 'total_tokens' in resp['usage']:
try:
return resp["usage"]["total_tokens"]
except Exception:
pass
return 0

View File

@ -34,6 +34,7 @@ interface IProps {
createConversationBeforeUploadDocument?(message: string): Promise<any>;
stopOutputMessage?(): void;
onUpload?: NonNullable<FileUploadProps['onUpload']>;
removeFile?(file: File): void;
}
export function NextMessageInput({
@ -47,6 +48,7 @@ export function NextMessageInput({
onInputChange,
stopOutputMessage,
onPressEnter,
removeFile,
}: IProps) {
const [files, setFiles] = React.useState<File[]>([]);
@ -77,6 +79,13 @@ export function NextMessageInput({
[submit],
);
const handleRemoveFile = React.useCallback(
(file: File) => () => {
removeFile?.(file);
},
[removeFile],
);
return (
<FileUpload
value={files}
@ -121,6 +130,7 @@ export function NextMessageInput({
variant="secondary"
size="icon"
className="-top-1 -right-1 absolute size-4 shrink-0 cursor-pointer rounded-full"
onClick={handleRemoveFile(file)}
>
<X className="size-2.5" />
</Button>

View File

@ -8,7 +8,7 @@ const Table = React.forwardRef<
>(({ className, rootClassName, ...props }, ref) => (
<div
className={cn(
'relative w-full overflow-auto rounded-2xl bg-bg-card',
'relative w-full overflow-auto rounded-2xl bg-bg-card scrollbar-none',
rootClassName,
)}
>
@ -27,7 +27,7 @@ const TableHeader = React.forwardRef<
>(({ className, ...props }, ref) => (
<thead
ref={ref}
className={cn('[&_tr]:border-b top-0 sticky', className)}
className={cn('[&_tr]:border-b top-0 sticky bg-bg-title z-10', className)}
{...props}
/>
));
@ -67,7 +67,7 @@ const TableRow = React.forwardRef<
<tr
ref={ref}
className={cn(
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
'border-b border-border-button transition-colors hover:bg-bg-card data-[state=selected]:bg-bg-card',
className,
)}
{...props}
@ -82,7 +82,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
'h-12 px-4 text-left align-middle font-normal text-text-secondary [&:has([role=checkbox])]:pr-0',
'h-12 px-4 text-left align-middle font-normal text-text-secondary [&:has([role=checkbox])]:pr-0 ',
className,
)}
{...props}

View File

@ -390,7 +390,7 @@ export const useUploadCanvasFileWithProgress = (
files.forEach((file) => {
onError(file, error as Error);
});
message.error('error', error?.message);
message.error(error?.message);
}
},
});

View File

@ -1,3 +1,4 @@
import { FileUploadProps } from '@/components/file-upload';
import message from '@/components/ui/message';
import { ChatSearchParams } from '@/constants/chat';
import {
@ -14,7 +15,7 @@ import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { has } from 'lodash';
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams, useSearchParams } from 'umi';
import {
@ -395,9 +396,14 @@ export const useDeleteMessage = () => {
return { data, loading, deleteMessage: mutateAsync };
};
type UploadParameters = Parameters<NonNullable<FileUploadProps['onUpload']>>;
type X = { file: UploadParameters[0][0]; options: UploadParameters[1] };
export function useUploadAndParseFile() {
const { conversationId } = useGetChatSearchParams();
const { t } = useTranslation();
const controller = useRef(new AbortController());
const {
data,
@ -405,22 +411,48 @@ export function useUploadAndParseFile() {
mutateAsync,
} = useMutation({
mutationKey: [ChatApiAction.UploadAndParse],
mutationFn: async (file: File) => {
const formData = new FormData();
formData.append('file', file);
formData.append('conversation_id', conversationId);
mutationFn: async ({
file,
options: { onProgress, onSuccess, onError },
}: X) => {
try {
const formData = new FormData();
formData.append('file', file);
formData.append('conversation_id', conversationId);
const { data } = await chatService.uploadAndParse(formData);
const { data } = await chatService.uploadAndParse(
{
signal: controller.current.signal,
data: formData,
onUploadProgress: ({ progress }) => {
onProgress(file, (progress || 0) * 100 - 1);
},
},
true,
);
if (data.code === 0) {
message.success(t(`message.uploaded`));
onProgress(file, 100);
if (data.code === 0) {
onSuccess(file);
message.success(t(`message.uploaded`));
} else {
onError(file, new Error(data.message));
}
return data;
} catch (error) {
onError(file, error as Error);
}
return data;
},
});
return { data, loading, uploadAndParseFile: mutateAsync };
const cancel = useCallback(() => {
controller.current.abort();
controller.current = new AbortController();
}, [controller]);
return { data, loading, uploadAndParseFile: mutateAsync, cancel };
}
export const useFetchExternalChatInfo = () => {

View File

@ -837,7 +837,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
fileManager: {
name: 'Name',
uploadDate: 'Upload Date',
knowledgeBase: 'Knowledge Base',
knowledgeBase: 'Dataset',
size: 'Size',
action: 'Action',
addToKnowledge: 'Link to Knowledge Base',

View File

@ -50,9 +50,10 @@ export function DatasetActionCell({
}, [record, showRenameModal]);
return (
<section className="flex gap-4 items-center text-text-sub-title-invert">
<section className="flex gap-4 items-center text-text-sub-title-invert opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant={'ghost'}
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
disabled={isRunning}
onClick={handleRename}
@ -61,7 +62,12 @@ export function DatasetActionCell({
</Button>
<HoverCard>
<HoverCardTrigger>
<Button variant="ghost" disabled={isRunning} size={'sm'}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
disabled={isRunning}
size={'sm'}
>
<Eye />
</Button>
</HoverCardTrigger>
@ -88,7 +94,8 @@ export function DatasetActionCell({
{isVirtualDocument || (
<Button
variant={'ghost'}
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
onClick={onDownloadDocument}
disabled={isRunning}
size={'sm'}
@ -97,7 +104,12 @@ export function DatasetActionCell({
</Button>
)}
<ConfirmDeleteDialog onOk={handleRemove}>
<Button variant={'ghost'} size={'sm'} disabled={isRunning}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
disabled={isRunning}
>
<Trash2 />
</Button>
</ConfirmDeleteDialog>

View File

@ -119,7 +119,7 @@ export function DatasetTable({
return (
<div className="w-full">
<Table rootClassName="max-h-[82vh]">
<Table rootClassName="max-h-[calc(100vh-222px)]">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
@ -144,6 +144,7 @@ export function DatasetTable({
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className="group"
>
{row.getVisibleCells().map((cell) => (
<TableCell

View File

@ -15,9 +15,9 @@ import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator';
import { IDocumentInfo } from '@/interfaces/database/document';
import { CircleX, RefreshCw } from 'lucide-react';
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { RunningStatus } from './constant';
import { DocumentType, RunningStatus } from './constant';
import { ParsingCard, PopoverContent } from './parsing-card';
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
import { useHandleRunDocumentByIds } from './use-run-document';
@ -61,6 +61,10 @@ export function ParsingStatusCell({
showSetMetaModal(record);
}, [record, showSetMetaModal]);
const showParse = useMemo(() => {
return record.type !== DocumentType.Virtual;
}, [record]);
return (
<section className="flex gap-8 items-center">
<div className="w-fit flex items-center justify-between">
@ -80,38 +84,42 @@ export function ParsingStatusCell({
</DropdownMenuContent>
</DropdownMenu>
</div>
<ConfirmDeleteDialog
title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })}
hidden={isZeroChunk || isRunning}
onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)}
>
<div
className="cursor-pointer flex items-center gap-3"
onClick={
isZeroChunk || isRunning
? handleOperationIconClick(false)
: () => {}
}
>
<Separator orientation="vertical" className="h-2.5" />
{operationIcon}
</div>
</ConfirmDeleteDialog>
{isParserRunning(run) ? (
<HoverCard>
<HoverCardTrigger asChild>
<div className="flex items-center gap-1">
<Progress value={p} className="h-1 flex-1 min-w-10" />
{p}%
{showParse && (
<>
<ConfirmDeleteDialog
title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })}
hidden={isZeroChunk || isRunning}
onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)}
>
<div
className="cursor-pointer flex items-center gap-3"
onClick={
isZeroChunk || isRunning
? handleOperationIconClick(false)
: () => {}
}
>
<Separator orientation="vertical" className="h-2.5" />
{operationIcon}
</div>
</HoverCardTrigger>
<HoverCardContent className="w-[40vw]">
<PopoverContent record={record}></PopoverContent>
</HoverCardContent>
</HoverCard>
) : (
<ParsingCard record={record}></ParsingCard>
</ConfirmDeleteDialog>
{isParserRunning(run) ? (
<HoverCard>
<HoverCardTrigger asChild>
<div className="flex items-center gap-1">
<Progress value={p} className="h-1 flex-1 min-w-10" />
{p}%
</div>
</HoverCardTrigger>
<HoverCardContent className="w-[40vw]">
<PopoverContent record={record}></PopoverContent>
</HoverCardContent>
</HoverCard>
) : (
<ParsingCard record={record}></ParsingCard>
)}
</>
)}
</section>
);

View File

@ -61,24 +61,40 @@ export function ActionCell({
}, [record, showMoveFileModal]);
return (
<section className="flex gap-4 items-center text-text-sub-title-invert">
<section className="flex gap-4 items-center text-text-sub-title-invert opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="ghost"
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={handleShowConnectToKnowledgeModal}
>
<Link2 />
</Button>
<Button variant="ghost" size={'sm'} onClick={handleShowMoveFileModal}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={handleShowMoveFileModal}
>
<FolderInput />
</Button>
<Button variant="ghost" size={'sm'} onClick={handleShowFileRenameModal}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={handleShowFileRenameModal}
>
<FolderPen />
</Button>
{isFolder || (
<Button variant={'ghost'} size={'sm'} onClick={onDownloadDocument}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={onDownloadDocument}
>
<ArrowDownToLine />
</Button>
)}
@ -89,7 +105,11 @@ export function ActionCell({
documentName={record.name}
className="text-text-sub-title-invert"
>
<Button variant={'ghost'} size={'sm'}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
>
<Eye />
</Button>
</NewDocumentLink>
@ -97,7 +117,8 @@ export function ActionCell({
{/* <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size={'sm'}>
<Button variant="transparent"
className="border-none" size={'sm'}>
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
@ -118,7 +139,11 @@ export function ActionCell({
</DropdownMenuContent>
</DropdownMenu> */}
<ConfirmDeleteDialog>
<Button variant="ghost" size={'sm'}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
>
<Trash2 />
</Button>
</ConfirmDeleteDialog>

View File

@ -213,6 +213,7 @@ export function FilesTable({
id: 'actions',
header: t('action'),
enableHiding: false,
enablePinning: true,
cell: ({ row }) => {
return (
<ActionCell
@ -259,51 +260,56 @@ export function FilesTable({
});
return (
<div className="w-full">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{loading ? (
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
<>
<div className="w-full">
<Table rootClassName="max-h-[calc(100vh-242px)] overflow-auto">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))
) : (
<TableEmpty columnsLength={columns.length}></TableEmpty>
)}
</TableBody>
</Table>
))}
</TableHeader>
<TableBody className="max-h-96 overflow-y-auto">
{loading ? (
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className="group"
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableEmpty columnsLength={columns.length}></TableEmpty>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end py-4">
<div className="space-x-2">
<RAGFlowPagination
@ -331,6 +337,6 @@ export function FilesTable({
loading={fileRenameLoading}
></RenameDialog>
)}
</div>
</>
);
}

View File

@ -36,6 +36,7 @@ export function SingleChatBox({ controller }: IProps) {
removeMessageById,
stopOutputMessage,
handleUploadFile,
removeFile,
} = useSendMessage(controller);
const { data: userInfo } = useFetchUserInfo();
const { data: currentDialog } = useFetchDialog();
@ -97,6 +98,7 @@ export function SingleChatBox({ controller }: IProps) {
stopOutputMessage={stopOutputMessage}
onUpload={handleUploadFile}
isUploading={isUploading}
removeFile={removeFile}
/>
{visible && (
<PdfDrawer

View File

@ -138,7 +138,7 @@ export const useSendMessage = (controller: AbortController) => {
const { conversationId, isNew } = useGetChatSearchParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { handleUploadFile, fileIds, clearFileIds, isUploading } =
const { handleUploadFile, fileIds, clearFileIds, isUploading, removeFile } =
useUploadFile();
const { send, answer, done } = useSendMessageWithSse(
@ -287,5 +287,6 @@ export const useSendMessage = (controller: AbortController) => {
stopOutputMessage,
handleUploadFile,
isUploading,
removeFile,
};
};

View File

@ -3,16 +3,21 @@ import { useUploadAndParseFile } from '@/hooks/use-chat-request';
import { useCallback, useState } from 'react';
export function useUploadFile() {
const { uploadAndParseFile, loading } = useUploadAndParseFile();
const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile();
const [fileIds, setFileIds] = useState<string[]>([]);
const [fileMap, setFileMap] = useState<Map<File, string>>(new Map());
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
useCallback(
async (files) => {
async (files, options) => {
if (Array.isArray(files) && files.length) {
const ret = await uploadAndParseFile(files[0]);
const ret = await uploadAndParseFile({ file: files[0], options });
if (ret.code === 0 && Array.isArray(ret.data)) {
setFileIds((list) => [...list, ...ret.data]);
setFileMap((map) => {
map.set(files[0], ret.data[0]);
return map;
});
}
}
},
@ -21,7 +26,28 @@ export function useUploadFile() {
const clearFileIds = useCallback(() => {
setFileIds([]);
setFileMap(new Map());
}, []);
return { handleUploadFile, clearFileIds, fileIds, isUploading: loading };
const removeFile = useCallback(
(file: File) => {
if (loading) {
cancel();
return;
}
const id = fileMap.get(file);
if (id) {
setFileIds((list) => list.filter((item) => item !== id));
}
},
[cancel, fileMap, loading],
);
return {
handleUploadFile,
clearFileIds,
fileIds,
isUploading: loading,
removeFile,
};
}

View File

@ -55,7 +55,7 @@ module.exports = {
'input-border': 'var(--input-border)',
/* design colors */
'bg-title': 'var(--bg-title)',
'bg-base': 'var(--bg-base)',
'bg-card': 'var(--bg-card)',
'bg-component': 'var(--bg-component)',

View File

@ -91,6 +91,8 @@
--input-border: rgba(22, 22, 24, 0.2);
--metallic: #46464a;
--bg-title: #f6f6f7;
/* design colors */
--bg-base: #ffffff;
@ -235,6 +237,8 @@
--input-border: rgba(255, 255, 255, 0.2);
--metallic: #fafafa;
--bg-title: #38383a;
/* design colors */
--bg-base: #161618;