mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-06 02:25:05 +08:00
Compare commits
12 Commits
15fff5724e
...
af6eabad0e
| Author | SHA1 | Date | |
|---|---|---|---|
| af6eabad0e | |||
| 5fb5a51b2e | |||
| 37004ecfb3 | |||
| 6d333ec4bc | |||
| ac188b0486 | |||
| adeb9d87e2 | |||
| d121033208 | |||
| 494f84cd69 | |||
| f24d464a53 | |||
| 484c536f2e | |||
| f7112acd97 | |||
| de4f75dcd8 |
@ -70,6 +70,7 @@ def create():
|
|||||||
e, t = TenantService.get_by_id(current_user.id)
|
e, t = TenantService.get_by_id(current_user.id)
|
||||||
if not e:
|
if not e:
|
||||||
return get_data_error_result(message="Tenant not found.")
|
return get_data_error_result(message="Tenant not found.")
|
||||||
|
|
||||||
req["parser_config"] = {
|
req["parser_config"] = {
|
||||||
"layout_recognize": "DeepDOC",
|
"layout_recognize": "DeepDOC",
|
||||||
"chunk_token_num": 512,
|
"chunk_token_num": 512,
|
||||||
|
|||||||
@ -173,7 +173,7 @@ def filename_type(filename):
|
|||||||
if re.match(r".*\.(wav|flac|ape|alac|wavpack|wv|mp3|aac|ogg|vorbis|opus)$", filename):
|
if re.match(r".*\.(wav|flac|ape|alac|wavpack|wv|mp3|aac|ogg|vorbis|opus)$", filename):
|
||||||
return FileType.AURAL.value
|
return FileType.AURAL.value
|
||||||
|
|
||||||
if re.match(r".*\.(jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp|avif|apng|icon|ico|mpg|mpeg|avi|rm|rmvb|mov|wmv|asf|dat|asx|wvx|mpe|mpa|mp4)$", filename):
|
if re.match(r".*\.(jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp|avif|apng|icon|ico|mpg|mpeg|avi|rm|rmvb|mov|wmv|asf|dat|asx|wvx|mpe|mpa|mp4|avi|mkv)$", filename):
|
||||||
return FileType.VISUAL.value
|
return FileType.VISUAL.value
|
||||||
|
|
||||||
return FileType.OTHER.value
|
return FileType.OTHER.value
|
||||||
|
|||||||
@ -77,7 +77,7 @@ services:
|
|||||||
container_name: ragflow-infinity
|
container_name: ragflow-infinity
|
||||||
profiles:
|
profiles:
|
||||||
- infinity
|
- infinity
|
||||||
image: infiniflow/infinity:v0.6.0
|
image: infiniflow/infinity:v0.6.1
|
||||||
volumes:
|
volumes:
|
||||||
- infinity_data:/var/infinity
|
- infinity_data:/var/infinity
|
||||||
- ./infinity_conf.toml:/infinity_conf.toml
|
- ./infinity_conf.toml:/infinity_conf.toml
|
||||||
|
|||||||
@ -34,7 +34,7 @@ Click **+ Add** to add heading levels here or update the corresponding **Regular
|
|||||||
|
|
||||||
### Output
|
### Output
|
||||||
|
|
||||||
The global variable name for the output of the **Title chunkder** component, which can be referenced by subsequent components in the ingestion pipeline.
|
The global variable name for the output of the **Title chunker** component, which can be referenced by subsequent components in the ingestion pipeline.
|
||||||
|
|
||||||
- Default: `chunks`
|
- Default: `chunks`
|
||||||
- Type: `Array<Object>`
|
- Type: `Array<Object>`
|
||||||
@ -37,7 +37,7 @@ Defaults to `\n`. Click the right-hand **Recycle bin** button to remove it, or c
|
|||||||
|
|
||||||
### Output
|
### Output
|
||||||
|
|
||||||
The global variable name for the output of the **Token chunkder** component, which can be referenced by subsequent components in the ingestion pipeline.
|
The global variable name for the output of the **Token chunker** component, which can be referenced by subsequent components in the ingestion pipeline.
|
||||||
|
|
||||||
- Default: `chunks`
|
- Default: `chunks`
|
||||||
- Type: `Array<Object>`
|
- Type: `Array<Object>`
|
||||||
@ -22,6 +22,23 @@ The embedding models included in a full edition are:
|
|||||||
These two embedding models are optimized specifically for English and Chinese, so performance may be compromised if you use them to embed documents in other languages.
|
These two embedding models are optimized specifically for English and Chinese, so performance may be compromised if you use them to embed documents in other languages.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## v0.21.1
|
||||||
|
|
||||||
|
Released on October 23, 2025.
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
- Experimental: Adds support for PDF document parsing using MinerU.
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Enhances UI/UX for the dataset and personal center pages.
|
||||||
|
- Upgrades RAGFlow's document engine, Infinity, to v0.6.1.
|
||||||
|
|
||||||
|
### Fixed issues
|
||||||
|
|
||||||
|
- An issue with video parsing.
|
||||||
|
|
||||||
## v0.21.0
|
## v0.21.0
|
||||||
|
|
||||||
Released on October 15, 2025.
|
Released on October 15, 2025.
|
||||||
|
|||||||
@ -29,7 +29,7 @@ from rag.utils import clean_markdown_block
|
|||||||
ocr = OCR()
|
ocr = OCR()
|
||||||
|
|
||||||
# Gemini supported MIME types
|
# Gemini supported MIME types
|
||||||
VIDEO_EXTS = [".mp4", ".mov", ".avi", ".flv", ".mpeg", ".mpg", ".webm", ".wmv", ".3gp", ".3gpp"]
|
VIDEO_EXTS = [".mp4", ".mov", ".avi", ".flv", ".mpeg", ".mpg", ".webm", ".wmv", ".3gp", ".3gpp", ".mkv"]
|
||||||
|
|
||||||
|
|
||||||
def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs):
|
def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs):
|
||||||
|
|||||||
@ -29,6 +29,7 @@ from api.db.services.llm_service import LLMBundle
|
|||||||
from api.utils import get_uuid
|
from api.utils import get_uuid
|
||||||
from api.utils.base64_image import image2id
|
from api.utils.base64_image import image2id
|
||||||
from deepdoc.parser import ExcelParser
|
from deepdoc.parser import ExcelParser
|
||||||
|
from deepdoc.parser.mineru_parser import MinerUParser
|
||||||
from deepdoc.parser.pdf_parser import PlainParser, RAGFlowPdfParser, VisionParser
|
from deepdoc.parser.pdf_parser import PlainParser, RAGFlowPdfParser, VisionParser
|
||||||
from rag.app.naive import Docx
|
from rag.app.naive import Docx
|
||||||
from rag.flow.base import ProcessBase, ProcessParamBase
|
from rag.flow.base import ProcessBase, ProcessParamBase
|
||||||
@ -138,9 +139,16 @@ class ParserParam(ProcessParamBase):
|
|||||||
"oggvorbis",
|
"oggvorbis",
|
||||||
"ape"
|
"ape"
|
||||||
],
|
],
|
||||||
"output_format": "json",
|
"output_format": "text",
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"suffix":[
|
||||||
|
"mp4",
|
||||||
|
"avi",
|
||||||
|
"mkv"
|
||||||
|
],
|
||||||
|
"output_format": "text",
|
||||||
},
|
},
|
||||||
"video": {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
@ -149,7 +157,7 @@ class ParserParam(ProcessParamBase):
|
|||||||
pdf_parse_method = pdf_config.get("parse_method", "")
|
pdf_parse_method = pdf_config.get("parse_method", "")
|
||||||
self.check_empty(pdf_parse_method, "Parse method abnormal.")
|
self.check_empty(pdf_parse_method, "Parse method abnormal.")
|
||||||
|
|
||||||
if pdf_parse_method.lower() not in ["deepdoc", "plain_text"]:
|
if pdf_parse_method.lower() not in ["deepdoc", "plain_text", "mineru"]:
|
||||||
self.check_empty(pdf_config.get("lang", ""), "PDF VLM language")
|
self.check_empty(pdf_config.get("lang", ""), "PDF VLM language")
|
||||||
|
|
||||||
pdf_output_format = pdf_config.get("output_format", "")
|
pdf_output_format = pdf_config.get("output_format", "")
|
||||||
@ -185,6 +193,10 @@ class ParserParam(ProcessParamBase):
|
|||||||
if audio_config:
|
if audio_config:
|
||||||
self.check_empty(audio_config.get("llm_id"), "Audio VLM")
|
self.check_empty(audio_config.get("llm_id"), "Audio VLM")
|
||||||
|
|
||||||
|
video_config = self.setups.get("video", "")
|
||||||
|
if video_config:
|
||||||
|
self.check_empty(video_config.get("llm_id"), "Video VLM")
|
||||||
|
|
||||||
email_config = self.setups.get("email", "")
|
email_config = self.setups.get("email", "")
|
||||||
if email_config:
|
if email_config:
|
||||||
email_output_format = email_config.get("output_format", "")
|
email_output_format = email_config.get("output_format", "")
|
||||||
@ -207,13 +219,34 @@ class Parser(ProcessBase):
|
|||||||
elif conf.get("parse_method").lower() == "plain_text":
|
elif conf.get("parse_method").lower() == "plain_text":
|
||||||
lines, _ = PlainParser()(blob)
|
lines, _ = PlainParser()(blob)
|
||||||
bboxes = [{"text": t} for t, _ in lines]
|
bboxes = [{"text": t} for t, _ in lines]
|
||||||
|
elif conf.get("parse_method").lower() == "mineru":
|
||||||
|
mineru_executable = os.environ.get("MINERU_EXECUTABLE", "mineru")
|
||||||
|
pdf_parser = MinerUParser(mineru_path=mineru_executable)
|
||||||
|
if not pdf_parser.check_installation():
|
||||||
|
raise RuntimeError("MinerU not found. Please install it via: pip install -U 'mineru[core]'.")
|
||||||
|
|
||||||
|
lines, _ = pdf_parser.parse_pdf(
|
||||||
|
filepath=name,
|
||||||
|
binary=blob,
|
||||||
|
callback=self.callback,
|
||||||
|
output_dir=os.environ.get("MINERU_OUTPUT_DIR", ""),
|
||||||
|
delete_output=bool(int(os.environ.get("MINERU_DELETE_OUTPUT", 1))),
|
||||||
|
)
|
||||||
|
bboxes = []
|
||||||
|
for t, poss in lines:
|
||||||
|
box = {
|
||||||
|
"image": pdf_parser.crop(poss, 1),
|
||||||
|
"positions": [[pos[0][-1], *pos[1:]] for pos in pdf_parser.extract_positions(poss)],
|
||||||
|
"text": t,
|
||||||
|
}
|
||||||
|
bboxes.append(box)
|
||||||
else:
|
else:
|
||||||
vision_model = LLMBundle(self._canvas._tenant_id, LLMType.IMAGE2TEXT, llm_name=conf.get("parse_method"), lang=self._param.setups["pdf"].get("lang"))
|
vision_model = LLMBundle(self._canvas._tenant_id, LLMType.IMAGE2TEXT, llm_name=conf.get("parse_method"), lang=self._param.setups["pdf"].get("lang"))
|
||||||
lines, _ = VisionParser(vision_model=vision_model)(blob, callback=self.callback)
|
lines, _ = VisionParser(vision_model=vision_model)(blob, callback=self.callback)
|
||||||
bboxes = []
|
bboxes = []
|
||||||
for t, poss in lines:
|
for t, poss in lines:
|
||||||
pn, x0, x1, top, bott = poss.split(" ")
|
for pn, x0, x1, top, bott in RAGFlowPdfParser.extract_positions(poss):
|
||||||
bboxes.append({"page_number": int(pn), "x0": float(x0), "x1": float(x1), "top": float(top), "bottom": float(bott), "text": t})
|
bboxes.append({"page_number": int(pn[0]), "x0": float(x0), "x1": float(x1), "top": float(top), "bottom": float(bott), "text": t})
|
||||||
|
|
||||||
if conf.get("output_format") == "json":
|
if conf.get("output_format") == "json":
|
||||||
self.set_output("json", bboxes)
|
self.set_output("json", bboxes)
|
||||||
@ -357,6 +390,17 @@ class Parser(ProcessBase):
|
|||||||
|
|
||||||
self.set_output("text", txt)
|
self.set_output("text", txt)
|
||||||
|
|
||||||
|
def _video(self, name, blob):
|
||||||
|
self.callback(random.randint(1, 5) / 100.0, "Start to work on an video.")
|
||||||
|
|
||||||
|
conf = self._param.setups["video"]
|
||||||
|
self.set_output("output_format", conf["output_format"])
|
||||||
|
|
||||||
|
cv_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.IMAGE2TEXT)
|
||||||
|
txt = cv_mdl.chat(system="", history=[], gen_conf={}, video_bytes=blob, filename=name)
|
||||||
|
|
||||||
|
self.set_output("text", txt)
|
||||||
|
|
||||||
def _email(self, name, blob):
|
def _email(self, name, blob):
|
||||||
self.callback(random.randint(1, 5) / 100.0, "Start to work on an email.")
|
self.callback(random.randint(1, 5) / 100.0, "Start to work on an email.")
|
||||||
|
|
||||||
@ -483,6 +527,7 @@ class Parser(ProcessBase):
|
|||||||
"word": self._word,
|
"word": self._word,
|
||||||
"image": self._image,
|
"image": self._image,
|
||||||
"audio": self._audio,
|
"audio": self._audio,
|
||||||
|
"video": self._video,
|
||||||
"email": self._email,
|
"email": self._email,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -257,6 +257,7 @@ export const useSendMessageWithSse = (
|
|||||||
.getReader();
|
.getReader();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
try {
|
||||||
const x = await reader?.read();
|
const x = await reader?.read();
|
||||||
if (x) {
|
if (x) {
|
||||||
const { done, value } = x;
|
const { done, value } = x;
|
||||||
@ -278,6 +279,12 @@ export const useSendMessageWithSse = (
|
|||||||
// Swallow parse errors silently
|
// Swallow parse errors silently
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof DOMException && e.name === 'AbortError') {
|
||||||
|
console.log('Request was aborted by user or logic.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setDoneValue(body, true);
|
setDoneValue(body, true);
|
||||||
resetAnswer();
|
resetAnswer();
|
||||||
|
|||||||
@ -126,6 +126,7 @@ export const useSendMessageBySSE = (url: string = api.completeConversation) => {
|
|||||||
.getReader();
|
.getReader();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
try {
|
||||||
const x = await reader?.read();
|
const x = await reader?.read();
|
||||||
if (x) {
|
if (x) {
|
||||||
const { done, value } = x;
|
const { done, value } = x;
|
||||||
@ -151,6 +152,12 @@ export const useSendMessageBySSE = (url: string = api.completeConversation) => {
|
|||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof DOMException && e.name === 'AbortError') {
|
||||||
|
console.log('Request was aborted by user or logic.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.info('done?');
|
console.info('done?');
|
||||||
setDone(true);
|
setDone(true);
|
||||||
|
|||||||
@ -430,7 +430,7 @@ export default {
|
|||||||
`,
|
`,
|
||||||
useRaptor: 'RAPTOR',
|
useRaptor: 'RAPTOR',
|
||||||
useRaptorTip:
|
useRaptorTip:
|
||||||
'Enable RAPTOR for multi-hop question-answering tasks. See https://ragflow.io/docs/dev/enable_raptor for details.',
|
'RAPTOR can be used for multi-hop question-answering tasks. Navigate to the Files page, click Generate > RAPTOR to enable it. See https://ragflow.io/docs/dev/enable_raptor for details.',
|
||||||
prompt: 'Prompt',
|
prompt: 'Prompt',
|
||||||
promptTip:
|
promptTip:
|
||||||
'Use the system prompt to describe the task for the LLM, specify how it should respond, and outline other miscellaneous requirements. The system prompt is often used in conjunction with keys (variables), which serve as various data inputs for the LLM. Use a forward slash `/` or the (x) button to show the keys to use.',
|
'Use the system prompt to describe the task for the LLM, specify how it should respond, and outline other miscellaneous requirements. The system prompt is often used in conjunction with keys (variables), which serve as various data inputs for the LLM. Use a forward slash `/` or the (x) button to show the keys to use.',
|
||||||
|
|||||||
@ -425,7 +425,7 @@ export default {
|
|||||||
`,
|
`,
|
||||||
useRaptor: '使用召回增强 RAPTOR 策略',
|
useRaptor: '使用召回增强 RAPTOR 策略',
|
||||||
useRaptorTip:
|
useRaptorTip:
|
||||||
'为多跳问答任务启用 RAPTOR,详情请见 : https://ragflow.io/docs/dev/enable_raptor。',
|
'RAPTOR 常应用于复杂的多跳问答任务。如需打开,请跳转至知识库的文件页面,点击生成 > RAPTOR 开启。详见: https://ragflow.io/docs/dev/enable_raptor。',
|
||||||
prompt: '提示词',
|
prompt: '提示词',
|
||||||
promptMessage: '提示词是必填项',
|
promptMessage: '提示词是必填项',
|
||||||
promptText: `请总结以下段落。 小心数字,不要编造。 段落如下:
|
promptText: `请总结以下段落。 小心数字,不要编造。 段落如下:
|
||||||
|
|||||||
@ -2,10 +2,13 @@ import { Sheet, SheetContent, SheetTitle } from '@/components/ui/sheet';
|
|||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useIsTaskMode } from '../hooks/use-get-begin-query';
|
||||||
import AgentChatBox from './box';
|
import AgentChatBox from './box';
|
||||||
|
|
||||||
export function ChatSheet({ hideModal }: IModalProps<any>) {
|
export function ChatSheet({ hideModal }: IModalProps<any>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const isTaskMode = useIsTaskMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open modal={false} onOpenChange={hideModal}>
|
<Sheet open modal={false} onOpenChange={hideModal}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
@ -13,7 +16,9 @@ export function ChatSheet({ hideModal }: IModalProps<any>) {
|
|||||||
onInteractOutside={(e) => e.preventDefault()}
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<SheetTitle className="hidden"></SheetTitle>
|
<SheetTitle className="hidden"></SheetTitle>
|
||||||
<div className="pl-5 pt-2">{t('chat.chat')}</div>
|
<div className="pl-5 pt-2">
|
||||||
|
{t(isTaskMode ? 'flow.task' : 'chat.chat')}
|
||||||
|
</div>
|
||||||
<AgentChatBox></AgentChatBox>
|
<AgentChatBox></AgentChatBox>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
|||||||
@ -382,9 +382,9 @@ export const useSendAgentMessage = ({
|
|||||||
const { content, id } = findMessageFromList(answerList);
|
const { content, id } = findMessageFromList(answerList);
|
||||||
const inputAnswer = findInputFromList(answerList);
|
const inputAnswer = findInputFromList(answerList);
|
||||||
const answer = content || getLatestError(answerList);
|
const answer = content || getLatestError(answerList);
|
||||||
if (answerList.length > 0 && answer) {
|
if (answerList.length > 0) {
|
||||||
addNewestOneAnswer({
|
addNewestOneAnswer({
|
||||||
answer: answer,
|
answer: answer ?? '',
|
||||||
id: id,
|
id: id,
|
||||||
...inputAnswer,
|
...inputAnswer,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export enum PptOutputFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum VideoOutputFormat {
|
export enum VideoOutputFormat {
|
||||||
Json = 'json',
|
Text = 'text',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AudioOutputFormat {
|
export enum AudioOutputFormat {
|
||||||
@ -76,7 +76,7 @@ export const InitialOutputFormatMap = {
|
|||||||
[FileType.TextMarkdown]: TextMarkdownOutputFormat.Text,
|
[FileType.TextMarkdown]: TextMarkdownOutputFormat.Text,
|
||||||
[FileType.Docx]: DocxOutputFormat.Json,
|
[FileType.Docx]: DocxOutputFormat.Json,
|
||||||
[FileType.PowerPoint]: PptOutputFormat.Json,
|
[FileType.PowerPoint]: PptOutputFormat.Json,
|
||||||
[FileType.Video]: VideoOutputFormat.Json,
|
[FileType.Video]: VideoOutputFormat.Text,
|
||||||
[FileType.Audio]: AudioOutputFormat.Text,
|
[FileType.Audio]: AudioOutputFormat.Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ export const FileTypeSuffixMap = {
|
|||||||
[FileType.TextMarkdown]: ['md', 'markdown', 'mdx', 'txt'],
|
[FileType.TextMarkdown]: ['md', 'markdown', 'mdx', 'txt'],
|
||||||
[FileType.Docx]: ['doc', 'docx'],
|
[FileType.Docx]: ['doc', 'docx'],
|
||||||
[FileType.PowerPoint]: ['pptx'],
|
[FileType.PowerPoint]: ['pptx'],
|
||||||
[FileType.Video]: [],
|
[FileType.Video]: ['mp4', 'avi', 'mkv'],
|
||||||
[FileType.Audio]: [
|
[FileType.Audio]: [
|
||||||
'da',
|
'da',
|
||||||
'wave',
|
'wave',
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import message from '@/components/ui/message';
|
|||||||
import { Spin } from '@/components/ui/spin';
|
import { Spin } from '@/components/ui/spin';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface ImagePreviewerProps {
|
interface ImagePreviewerProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -17,7 +17,7 @@ export const ImagePreviewer: React.FC<ImagePreviewerProps> = ({
|
|||||||
const [imageSrc, setImageSrc] = useState<string | null>(null);
|
const [imageSrc, setImageSrc] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
const fetchImage = async () => {
|
const fetchImage = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const res = await request(url, {
|
const res = await request(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -30,12 +30,13 @@ export const ImagePreviewer: React.FC<ImagePreviewerProps> = ({
|
|||||||
const objectUrl = URL.createObjectURL(res.data);
|
const objectUrl = URL.createObjectURL(res.data);
|
||||||
setImageSrc(objectUrl);
|
setImageSrc(objectUrl);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
}, [url]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (url) {
|
if (url) {
|
||||||
fetchImage();
|
fetchImage();
|
||||||
}
|
}
|
||||||
}, [url]);
|
}, [url, fetchImage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import styles from './index.less';
|
|||||||
import PdfPreviewer, { IProps } from './pdf-preview';
|
import PdfPreviewer, { IProps } from './pdf-preview';
|
||||||
import { PptPreviewer } from './ppt-preview';
|
import { PptPreviewer } from './ppt-preview';
|
||||||
import { TxtPreviewer } from './txt-preview';
|
import { TxtPreviewer } from './txt-preview';
|
||||||
|
import { VideoPreviewer } from './video-preview';
|
||||||
|
|
||||||
type PreviewProps = {
|
type PreviewProps = {
|
||||||
fileType: string;
|
fileType: string;
|
||||||
@ -42,11 +43,30 @@ const Preview = ({
|
|||||||
<TxtPreviewer className={className} url={url} />
|
<TxtPreviewer className={className} url={url} />
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
{['visual'].indexOf(fileType) > -1 && (
|
{['jpg', 'png', 'gif', 'jpeg', 'svg', 'bmp', 'ico', 'tif'].indexOf(
|
||||||
|
fileType,
|
||||||
|
) > -1 && (
|
||||||
<section>
|
<section>
|
||||||
<ImagePreviewer className={className} url={url} />
|
<ImagePreviewer className={className} url={url} />
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
{[
|
||||||
|
'mp4',
|
||||||
|
'avi',
|
||||||
|
'mov',
|
||||||
|
'mkv',
|
||||||
|
'wmv',
|
||||||
|
'flv',
|
||||||
|
'mpeg',
|
||||||
|
'mpg',
|
||||||
|
'asf',
|
||||||
|
'rm',
|
||||||
|
'rmvb',
|
||||||
|
].indexOf(fileType) > -1 && (
|
||||||
|
<section>
|
||||||
|
<VideoPreviewer className={className} url={url} />
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
{['pptx'].indexOf(fileType) > -1 && (
|
{['pptx'].indexOf(fileType) > -1 && (
|
||||||
<section>
|
<section>
|
||||||
<PptPreviewer className={className} url={url} />
|
<PptPreviewer className={className} url={url} />
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
import message from '@/components/ui/message';
|
||||||
|
import { Spin } from '@/components/ui/spin';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
interface VideoPreviewerProps {
|
||||||
|
className?: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VideoPreviewer: React.FC<VideoPreviewerProps> = ({
|
||||||
|
className,
|
||||||
|
url,
|
||||||
|
}) => {
|
||||||
|
// const url = useGetDocumentUrl();
|
||||||
|
const [videoSrc, setVideoSrc] = useState<string | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const fetchVideo = useCallback(async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const res = await request(url, {
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
onError: () => {
|
||||||
|
message.error('Failed to load video');
|
||||||
|
setIsLoading(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const objectUrl = URL.createObjectURL(res.data);
|
||||||
|
setVideoSrc(objectUrl);
|
||||||
|
setIsLoading(false);
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (url) {
|
||||||
|
fetchVideo();
|
||||||
|
}
|
||||||
|
}, [url, fetchVideo]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (videoSrc) {
|
||||||
|
URL.revokeObjectURL(videoSrc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [videoSrc]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'relative w-full h-full p-4 bg-background-paper border border-border-normal rounded-md video-previewer',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && videoSrc && (
|
||||||
|
<div className="max-h-[80vh] overflow-auto p-2">
|
||||||
|
<video
|
||||||
|
src={videoSrc}
|
||||||
|
controls
|
||||||
|
className="w-full h-auto max-w-full object-contain"
|
||||||
|
onLoadedData={() => URL.revokeObjectURL(videoSrc!)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -166,6 +166,7 @@ const Chunk = () => {
|
|||||||
case 'doc':
|
case 'doc':
|
||||||
return documentInfo?.name.split('.').pop() || 'doc';
|
return documentInfo?.name.split('.').pop() || 'doc';
|
||||||
case 'visual':
|
case 'visual':
|
||||||
|
return documentInfo?.name.split('.').pop() || 'visual';
|
||||||
case 'docx':
|
case 'docx':
|
||||||
case 'txt':
|
case 'txt':
|
||||||
case 'md':
|
case 'md':
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import {
|
|||||||
} from '@/components/ui/breadcrumb';
|
} from '@/components/ui/breadcrumb';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Modal } from '@/components/ui/modal/modal';
|
import { Modal } from '@/components/ui/modal/modal';
|
||||||
|
import { AgentCategory } from '@/constants/agent';
|
||||||
import { Images } from '@/constants/common';
|
import { Images } from '@/constants/common';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||||
@ -178,8 +179,8 @@ const Chunk = () => {
|
|||||||
if (knowledgeId) {
|
if (knowledgeId) {
|
||||||
navigateToDatasetOverview(knowledgeId)();
|
navigateToDatasetOverview(knowledgeId)();
|
||||||
}
|
}
|
||||||
if (agentId) {
|
if (isAgent) {
|
||||||
navigateToAgent(agentId)();
|
navigateToAgent(agentId, AgentCategory.DataflowCanvas)();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
|||||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||||
import { Upload } from 'lucide-react';
|
import { Upload } from 'lucide-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DatasetTable } from './dataset-table';
|
import { DatasetTable } from './dataset-table';
|
||||||
import Generate from './generate-button/generate';
|
import Generate from './generate-button/generate';
|
||||||
@ -31,7 +32,6 @@ export default function Dataset() {
|
|||||||
onDocumentUploadOk,
|
onDocumentUploadOk,
|
||||||
documentUploadLoading,
|
documentUploadLoading,
|
||||||
} = useHandleUploadDocument();
|
} = useHandleUploadDocument();
|
||||||
const { data: dataSetData } = useFetchKnowledgeBaseConfiguration();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
searchString,
|
searchString,
|
||||||
@ -43,6 +43,14 @@ export default function Dataset() {
|
|||||||
handleFilterSubmit,
|
handleFilterSubmit,
|
||||||
loading,
|
loading,
|
||||||
} = useFetchDocumentList();
|
} = useFetchDocumentList();
|
||||||
|
|
||||||
|
const refreshCount = useMemo(() => {
|
||||||
|
return documents.findIndex((doc) => doc.run === '1') + documents.length;
|
||||||
|
}, [documents]);
|
||||||
|
|
||||||
|
const { data: dataSetData } = useFetchKnowledgeBaseConfiguration({
|
||||||
|
refreshCount,
|
||||||
|
});
|
||||||
const { filters, onOpenChange } = useSelectDatasetFilters();
|
const { filters, onOpenChange } = useSelectDatasetFilters();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export function useUploadFile() {
|
|||||||
if (Array.isArray(files) && files.length) {
|
if (Array.isArray(files) && files.length) {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
const ret = await uploadAndParseFile({ file, options, conversationId });
|
const ret = await uploadAndParseFile({ file, options, conversationId });
|
||||||
if (ret.code === 0 && Array.isArray(ret.data)) {
|
if (ret?.code === 0 && Array.isArray(ret?.data)) {
|
||||||
setFileIds((list) => [...list, ...ret.data]);
|
setFileIds((list) => [...list, ...ret.data]);
|
||||||
setFileMap((map) => {
|
setFileMap((map) => {
|
||||||
map.set(files[0], ret.data[0]);
|
map.set(files[0], ret.data[0]);
|
||||||
|
|||||||
Reference in New Issue
Block a user