Compare commits

...

2 Commits

Author SHA1 Message Date
5b2e5dd334 Feat: Gemini supports video parsing (#10671)
### What problem does this PR solve?

Gemini supports video parsing.


![img_v3_02r8_adbd5adc-d665-4756-9a00-3ae0f12224fg](https://github.com/user-attachments/assets/30d8d296-c336-4b55-9823-803979e705ca)


![img_v3_02r8_ab60c046-1727-4029-ad2e-66097fd3ccbg](https://github.com/user-attachments/assets/441b1487-a970-427e-98b6-6e1e002f2bad)

Close: #10617

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-10-20 16:49:47 +08:00
de46b0d46e Fix: Optimize code and fix ts type errors #9869 (#10666)
### What problem does this PR solve?

Fix: Optimize code and fix ts type errors #9869

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-10-20 15:59:56 +08:00
22 changed files with 485 additions and 305 deletions

View File

@ -227,9 +227,9 @@ class LLMBundle(LLM4Tenant):
if self.langfuse: if self.langfuse:
generation = self.langfuse.start_generation(trace_context=self.trace_context, name="chat", model=self.llm_name, input={"system": system, "history": history}) generation = self.langfuse.start_generation(trace_context=self.trace_context, name="chat", model=self.llm_name, input={"system": system, "history": history})
chat_partial = partial(self.mdl.chat, system, history, gen_conf) chat_partial = partial(self.mdl.chat, system, history, gen_conf, **kwargs)
if self.is_tools and self.mdl.is_tools: if self.is_tools and self.mdl.is_tools:
chat_partial = partial(self.mdl.chat_with_tools, system, history, gen_conf) chat_partial = partial(self.mdl.chat_with_tools, system, history, gen_conf, **kwargs)
use_kwargs = self._clean_param(chat_partial, **kwargs) use_kwargs = self._clean_param(chat_partial, **kwargs)
txt, used_tokens = chat_partial(**use_kwargs) txt, used_tokens = chat_partial(**use_kwargs)

View File

@ -1345,35 +1345,35 @@
"llm_name": "gemini-2.5-flash", "llm_name": "gemini-2.5-flash",
"tags": "LLM,CHAT,1024K,IMAGE2TEXT", "tags": "LLM,CHAT,1024K,IMAGE2TEXT",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "chat", "model_type": "image2text",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-2.5-pro", "llm_name": "gemini-2.5-pro",
"tags": "LLM,CHAT,IMAGE2TEXT,1024K", "tags": "LLM,CHAT,IMAGE2TEXT,1024K",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "chat", "model_type": "image2text",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-2.5-flash-lite", "llm_name": "gemini-2.5-flash-lite",
"tags": "LLM,CHAT,1024K,IMAGE2TEXT", "tags": "LLM,CHAT,1024K,IMAGE2TEXT",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "chat", "model_type": "image2text",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-2.0-flash", "llm_name": "gemini-2.0-flash",
"tags": "LLM,CHAT,1024K", "tags": "LLM,CHAT,1024K",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "chat", "model_type": "image2text",
"is_tools": true "is_tools": true
}, },
{ {
"llm_name": "gemini-2.0-flash-lite", "llm_name": "gemini-2.0-flash-lite",
"tags": "LLM,CHAT,1024K", "tags": "LLM,CHAT,1024K",
"max_tokens": 1048576, "max_tokens": 1048576,
"model_type": "chat", "model_type": "image2text",
"is_tools": true "is_tools": true
}, },
{ {

View File

@ -23,25 +23,43 @@ from PIL import Image
from api.db import LLMType from api.db import LLMType
from api.db.services.llm_service import LLMBundle from api.db.services.llm_service import LLMBundle
from deepdoc.vision import OCR from deepdoc.vision import OCR
from rag.nlp import tokenize from rag.nlp import rag_tokenizer, tokenize
from rag.utils import clean_markdown_block from rag.utils import clean_markdown_block
from rag.nlp import rag_tokenizer
ocr = OCR() ocr = OCR()
# Gemini supported MIME types
VIDEO_EXTS = [".mp4", ".mov", ".avi", ".flv", ".mpeg", ".mpg", ".webm", ".wmv", ".3gp", ".3gpp"]
def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs): def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs):
img = Image.open(io.BytesIO(binary)).convert('RGB')
doc = { doc = {
"docnm_kwd": filename, "docnm_kwd": filename,
"title_tks": rag_tokenizer.tokenize(re.sub(r"\.[a-zA-Z]+$", "", filename)), "title_tks": rag_tokenizer.tokenize(re.sub(r"\.[a-zA-Z]+$", "", filename)),
"image": img,
"doc_type_kwd": "image"
} }
eng = lang.lower() == "english"
if any(filename.lower().endswith(ext) for ext in VIDEO_EXTS):
try:
doc.update({"doc_type_kwd": "video"})
cv_mdl = LLMBundle(tenant_id, llm_type=LLMType.IMAGE2TEXT, lang=lang)
ans = cv_mdl.chat(system="", history=[], gen_conf={}, video_bytes=binary, filename=filename)
callback(0.8, "CV LLM respond: %s ..." % ans[:32])
ans += "\n" + ans
tokenize(doc, ans, eng)
return [doc]
except Exception as e:
callback(prog=-1, msg=str(e))
else:
img = Image.open(io.BytesIO(binary)).convert("RGB")
doc.update(
{
"image": img,
"doc_type_kwd": "image",
}
)
bxs = ocr(np.array(img)) bxs = ocr(np.array(img))
txt = "\n".join([t[0] for _, t in bxs if t[0]]) txt = "\n".join([t[0] for _, t in bxs if t[0]])
eng = lang.lower() == "english"
callback(0.4, "Finish OCR: (%s ...)" % txt[:12]) callback(0.4, "Finish OCR: (%s ...)" % txt[:12])
if (eng and len(txt.split()) > 32) or len(txt) > 32: if (eng and len(txt.split()) > 32) or len(txt) > 32:
tokenize(doc, txt, eng) tokenize(doc, txt, eng)
@ -52,7 +70,7 @@ def chunk(filename, binary, tenant_id, lang, callback=None, **kwargs):
callback(0.4, "Use CV LLM to describe the picture.") callback(0.4, "Use CV LLM to describe the picture.")
cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang) cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang)
img_binary = io.BytesIO() img_binary = io.BytesIO()
img.save(img_binary, format='JPEG') img.save(img_binary, format="JPEG")
img_binary.seek(0) img_binary.seek(0)
ans = cv_mdl.describe(img_binary.read()) ans = cv_mdl.describe(img_binary.read())
callback(0.8, "CV LLM respond: %s ..." % ans[:32]) callback(0.8, "CV LLM respond: %s ..." % ans[:32])
@ -79,7 +97,7 @@ def vision_llm_chunk(binary, vision_model, prompt=None, callback=None):
try: try:
with io.BytesIO() as img_binary: with io.BytesIO() as img_binary:
img.save(img_binary, format='JPEG') img.save(img_binary, format="JPEG")
img_binary.seek(0) img_binary.seek(0)
ans = clean_markdown_block(vision_model.describe_with_prompt(img_binary.read(), prompt)) ans = clean_markdown_block(vision_model.describe_with_prompt(img_binary.read(), prompt))
txt += "\n" + ans txt += "\n" + ans

View File

@ -16,6 +16,7 @@
import base64 import base64
import json import json
import os import os
import logging
from abc import ABC from abc import ABC
from copy import deepcopy from copy import deepcopy
from io import BytesIO from io import BytesIO
@ -529,6 +530,7 @@ class GeminiCV(Base):
client.configure(api_key=key) client.configure(api_key=key)
_client = client.get_default_generative_client() _client = client.get_default_generative_client()
self.api_key=key
self.model_name = model_name self.model_name = model_name
self.model = GenerativeModel(model_name=self.model_name) self.model = GenerativeModel(model_name=self.model_name)
self.model._client = _client self.model._client = _client
@ -571,7 +573,15 @@ class GeminiCV(Base):
res = self.model.generate_content(input) res = self.model.generate_content(input)
return res.text, total_token_count_from_response(res) return res.text, total_token_count_from_response(res)
def chat(self, system, history, gen_conf, images=[]):
def chat(self, system, history, gen_conf, images=[], video_bytes=None, filename=""):
if video_bytes:
try:
summary, summary_num_tokens = self._process_video(video_bytes, filename)
return summary, summary_num_tokens
except Exception as e:
return "**ERROR**: " + str(e), 0
generation_config = dict(temperature=gen_conf.get("temperature", 0.3), top_p=gen_conf.get("top_p", 0.7)) generation_config = dict(temperature=gen_conf.get("temperature", 0.3), top_p=gen_conf.get("top_p", 0.7))
try: try:
response = self.model.generate_content( response = self.model.generate_content(
@ -603,6 +613,48 @@ class GeminiCV(Base):
yield total_token_count_from_response(response) yield total_token_count_from_response(response)
def _process_video(self, video_bytes, filename):
from google import genai
from google.genai import types
import tempfile
from pathlib import Path
video_size_mb = len(video_bytes) / (1024 * 1024)
client = genai.Client(api_key=self.api_key)
tmp_path = None
try:
if video_size_mb <= 20:
response = client.models.generate_content(
model="models/gemini-2.5-flash",
contents=types.Content(parts=[
types.Part(inline_data=types.Blob(data=video_bytes, mime_type="video/mp4")),
types.Part(text="Please summarize the video in proper sentences.")
])
)
else:
logging.info(f"Video size {video_size_mb:.2f}MB exceeds 20MB. Using Files API...")
video_suffix = Path(filename).suffix or ".mp4"
with tempfile.NamedTemporaryFile(delete=False, suffix=video_suffix) as tmp:
tmp.write(video_bytes)
tmp_path = Path(tmp.name)
uploaded_file = client.files.upload(file=tmp_path)
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[uploaded_file, "Please summarize this video in proper sentences."]
)
summary = response.text or ""
logging.info(f"Video summarized: {summary[:32]}...")
return summary, num_tokens_from_string(summary)
except Exception as e:
logging.error(f"Video processing failed: {e}")
raise
finally:
if tmp_path and tmp_path.exists():
tmp_path.unlink()
class NvidiaCV(Base): class NvidiaCV(Base):
_FACTORY_NAME = "NVIDIA" _FACTORY_NAME = "NVIDIA"

View File

@ -16,7 +16,7 @@ interface EditTagsProps {
} }
const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>( const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
({ value = [], onChange }: EditTagsProps, ref) => { ({ value = [], onChange }: EditTagsProps) => {
const [inputVisible, setInputVisible] = useState(false); const [inputVisible, setInputVisible] = useState(false);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);

View File

@ -1,22 +1,32 @@
import Image from '@/components/image'; import Image from '@/components/image';
import SvgIcon from '@/components/svg-icon'; import SvgIcon from '@/components/svg-icon';
import { useFetchDocumentThumbnailsByIds, useGetDocumentUrl } from '@/hooks/document-hooks'; import {
useFetchDocumentThumbnailsByIds,
useGetDocumentUrl,
} from '@/hooks/document-hooks';
import { IReference, IReferenceChunk } from '@/interfaces/database/chat'; import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
import { preprocessLaTeX, replaceThinkToSection, showImage } from '@/utils/chat'; import {
preprocessLaTeX,
replaceThinkToSection,
showImage,
} from '@/utils/chat';
import { getExtension } from '@/utils/document-util'; import { getExtension } from '@/utils/document-util';
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Flex, Popover, Tooltip } from 'antd'; import { Button, Flex, Popover, Tooltip } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import 'katex/dist/katex.min.css';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { pipe } from 'lodash/fp'; import { pipe } from 'lodash/fp';
import 'katex/dist/katex.min.css';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Markdown from 'react-markdown'; import Markdown from 'react-markdown';
import reactStringReplace from 'react-string-replace'; import reactStringReplace from 'react-string-replace';
import SyntaxHighlighter from 'react-syntax-highlighter'; import SyntaxHighlighter from 'react-syntax-highlighter';
import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; import {
oneDark,
oneLight,
} from 'react-syntax-highlighter/dist/esm/styles/prism';
import rehypeKatex from 'rehype-katex'; import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw'; import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
@ -39,7 +49,8 @@ const FloatingChatWidgetMarkdown = ({
clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void; clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { setDocumentIds, data: fileThumbnails } = useFetchDocumentThumbnailsByIds(); const { setDocumentIds, data: fileThumbnails } =
useFetchDocumentThumbnailsByIds();
const getDocumentUrl = useGetDocumentUrl(); const getDocumentUrl = useGetDocumentUrl();
const isDarkTheme = useIsDarkTheme(); const isDarkTheme = useIsDarkTheme();
@ -51,23 +62,37 @@ const FloatingChatWidgetMarkdown = ({
useEffect(() => { useEffect(() => {
const docAggs = reference?.doc_aggs; const docAggs = reference?.doc_aggs;
const docList = Array.isArray(docAggs) ? docAggs : Object.values(docAggs ?? {}); const docList = Array.isArray(docAggs)
? docAggs
: Object.values(docAggs ?? {});
setDocumentIds(docList.map((x: any) => x.doc_id).filter(Boolean)); setDocumentIds(docList.map((x: any) => x.doc_id).filter(Boolean));
}, [reference, setDocumentIds]); }, [reference, setDocumentIds]);
const handleDocumentButtonClick = useCallback((documentId: string, chunk: IReferenceChunk, isPdf: boolean, documentUrl?: string) => () => { const handleDocumentButtonClick = useCallback(
(
documentId: string,
chunk: IReferenceChunk,
isPdf: boolean,
documentUrl?: string,
) =>
() => {
if (!documentId) return; if (!documentId) return;
if (!isPdf && documentUrl) { if (!isPdf && documentUrl) {
window.open(documentUrl, '_blank'); window.open(documentUrl, '_blank');
} else if (clickDocumentButton) { } else if (clickDocumentButton) {
clickDocumentButton(documentId, chunk); clickDocumentButton(documentId, chunk);
} }
}, [clickDocumentButton]); },
[clickDocumentButton],
);
const rehypeWrapReference = () => (tree: any) => { const rehypeWrapReference = () => (tree: any) => {
visitParents(tree, 'text', (node, ancestors) => { visitParents(tree, 'text', (node, ancestors) => {
const latestAncestor = ancestors[ancestors.length - 1]; const latestAncestor = ancestors[ancestors.length - 1];
if (latestAncestor.tagName !== 'custom-typography' && latestAncestor.tagName !== 'code') { if (
latestAncestor.tagName !== 'custom-typography' &&
latestAncestor.tagName !== 'code'
) {
node.type = 'element'; node.type = 'element';
node.tagName = 'custom-typography'; node.tagName = 'custom-typography';
node.properties = {}; node.properties = {};
@ -76,56 +101,120 @@ const FloatingChatWidgetMarkdown = ({
}); });
}; };
const getReferenceInfo = useCallback((chunkIndex: number) => { const getReferenceInfo = useCallback(
(chunkIndex: number) => {
const chunkItem = reference?.chunks?.[chunkIndex]; const chunkItem = reference?.chunks?.[chunkIndex];
if (!chunkItem) return null; if (!chunkItem) return null;
const docAggsArray = Array.isArray(reference?.doc_aggs) ? reference.doc_aggs : Object.values(reference?.doc_aggs ?? {}); const docAggsArray = Array.isArray(reference?.doc_aggs)
const document = docAggsArray.find((x: any) => x?.doc_id === chunkItem?.document_id) as any; ? reference.doc_aggs
: Object.values(reference?.doc_aggs ?? {});
const document = docAggsArray.find(
(x: any) => x?.doc_id === chunkItem?.document_id,
) as any;
const documentId = document?.doc_id; const documentId = document?.doc_id;
const documentUrl = document?.url ?? (documentId ? getDocumentUrl(documentId) : undefined); const documentUrl =
document?.url ?? (documentId ? getDocumentUrl(documentId) : undefined);
const fileThumbnail = documentId ? fileThumbnails[documentId] : ''; const fileThumbnail = documentId ? fileThumbnails[documentId] : '';
const fileExtension = documentId ? getExtension(document?.doc_name ?? '') : ''; const fileExtension = documentId
return { documentUrl, fileThumbnail, fileExtension, imageId: chunkItem.image_id, chunkItem, documentId, document }; ? getExtension(document?.doc_name ?? '')
}, [fileThumbnails, reference, getDocumentUrl]); : '';
return {
documentUrl,
fileThumbnail,
fileExtension,
imageId: chunkItem.image_id,
chunkItem,
documentId,
document,
};
},
[fileThumbnails, reference, getDocumentUrl],
);
const getPopoverContent = useCallback((chunkIndex: number) => { const getPopoverContent = useCallback(
(chunkIndex: number) => {
const info = getReferenceInfo(chunkIndex); const info = getReferenceInfo(chunkIndex);
if (!info) { if (!info) {
return <div className="p-2 text-xs text-red-500">Error: Missing document information.</div>; return (
<div className="p-2 text-xs text-red-500">
Error: Missing document information.
</div>
);
} }
const { documentUrl, fileThumbnail, fileExtension, imageId, chunkItem, documentId, document } = info; const {
documentUrl,
fileThumbnail,
fileExtension,
imageId,
chunkItem,
documentId,
document,
} = info;
return ( return (
<div key={`popover-content-${chunkItem.id}`} className="flex gap-2 widget-citation-content"> <div
key={`popover-content-${chunkItem.id}`}
className="flex gap-2 widget-citation-content"
>
{imageId && ( {imageId && (
<Popover placement="left" content={<Image id={imageId} className="max-w-[80vw] max-h-[60vh] rounded" />}> <Popover
<Image id={imageId} className="w-24 h-24 object-contain rounded m-1 cursor-pointer" /> placement="left"
content={
<Image
id={imageId}
className="max-w-[80vw] max-h-[60vh] rounded"
/>
}
>
<Image
id={imageId}
className="w-24 h-24 object-contain rounded m-1 cursor-pointer"
/>
</Popover> </Popover>
)} )}
<div className="space-y-2 flex-1 min-w-0"> <div className="space-y-2 flex-1 min-w-0">
<div <div
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(chunkItem?.content ?? '') }} dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(chunkItem?.content ?? ''),
}}
className="max-h-[250px] overflow-y-auto text-xs leading-relaxed p-2 bg-gray-50 dark:bg-gray-800 rounded prose-sm" className="max-h-[250px] overflow-y-auto text-xs leading-relaxed p-2 bg-gray-50 dark:bg-gray-800 rounded prose-sm"
></div> ></div>
{documentId && ( {documentId && (
<Flex gap={'small'} align="center"> <Flex gap={'small'} align="center">
{fileThumbnail ? ( {fileThumbnail ? (
<img src={fileThumbnail} alt={document?.doc_name} className="w-6 h-6 rounded" /> <img
src={fileThumbnail}
alt={document?.doc_name}
className="w-6 h-6 rounded"
/>
) : ( ) : (
<SvgIcon name={`file-icon/${fileExtension}`} width={20} /> <SvgIcon name={`file-icon/${fileExtension}`} width={20} />
)} )}
<Tooltip title={!documentUrl && fileExtension !== 'pdf' ? 'Document link unavailable' : document.doc_name}> <Tooltip
title={
!documentUrl && fileExtension !== 'pdf'
? 'Document link unavailable'
: document.doc_name
}
>
<Button <Button
type="link" type="link"
size="small" size="small"
className="p-0 text-xs break-words h-auto text-left flex-1" className="p-0 text-xs break-words h-auto text-left flex-1"
onClick={handleDocumentButtonClick(documentId, chunkItem, fileExtension === 'pdf', documentUrl)} onClick={handleDocumentButtonClick(
documentId,
chunkItem,
fileExtension === 'pdf',
documentUrl,
)}
disabled={!documentUrl && fileExtension !== 'pdf'} disabled={!documentUrl && fileExtension !== 'pdf'}
style={{ whiteSpace: 'normal' }} style={{ whiteSpace: 'normal' }}
> >
<span className="truncate">{document?.doc_name ?? 'Unnamed Document'}</span> <span className="truncate">
{document?.doc_name ?? 'Unnamed Document'}
</span>
</Button> </Button>
</Tooltip> </Tooltip>
</Flex> </Flex>
@ -133,33 +222,52 @@ const FloatingChatWidgetMarkdown = ({
</div> </div>
</div> </div>
); );
}, [getReferenceInfo, handleDocumentButtonClick]); },
[getReferenceInfo, handleDocumentButtonClick],
);
const renderReference = useCallback((text: string) => { const renderReference = useCallback(
(text: string) => {
return reactStringReplace(text, currentReg, (match, i) => { return reactStringReplace(text, currentReg, (match, i) => {
const chunkIndex = getChunkIndex(match); const chunkIndex = getChunkIndex(match);
const info = getReferenceInfo(chunkIndex); const info = getReferenceInfo(chunkIndex);
if (!info) { if (!info) {
return <Tooltip key={`err-tooltip-${i}`} title="Reference unavailable"><InfoCircleOutlined className={styles.referenceIcon} /></Tooltip>; return (
<Tooltip key={`err-tooltip-${i}`} title="Reference unavailable">
<InfoCircleOutlined className={styles.referenceIcon} />
</Tooltip>
);
} }
const { imageId, chunkItem, documentId, fileExtension, documentUrl } = info; const { imageId, chunkItem, documentId, fileExtension, documentUrl } =
info;
if (showImage(chunkItem?.doc_type)) { if (showImage(chunkItem?.doc_type)) {
return <Image key={`img-${i}`} id={imageId} className="block object-contain max-w-full max-h-48 rounded my-2 cursor-pointer" onClick={handleDocumentButtonClick(documentId, chunkItem, fileExtension === 'pdf', documentUrl)} />; return (
<Image
key={`img-${i}`}
id={imageId}
className="block object-contain max-w-full max-h-48 rounded my-2 cursor-pointer"
onClick={handleDocumentButtonClick(
documentId,
chunkItem,
fileExtension === 'pdf',
documentUrl,
)}
/>
);
} }
return ( return (
<Popover <Popover content={getPopoverContent(chunkIndex)} key={`popover-${i}`}>
content={getPopoverContent(chunkIndex)}
key={`popover-${i}`}
>
<InfoCircleOutlined className={styles.referenceIcon} /> <InfoCircleOutlined className={styles.referenceIcon} />
</Popover> </Popover>
); );
}); });
}, [getPopoverContent, getReferenceInfo, handleDocumentButtonClick]); },
[getPopoverContent, getReferenceInfo, handleDocumentButtonClick],
);
return ( return (
<div className="floating-chat-widget"> <div className="floating-chat-widget">
@ -167,9 +275,12 @@ const FloatingChatWidgetMarkdown = ({
rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]} rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]}
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
className="text-sm leading-relaxed space-y-2 prose-sm max-w-full" className="text-sm leading-relaxed space-y-2 prose-sm max-w-full"
components={{ components={
'custom-typography': ({ children }: { children: string }) => renderReference(children), {
'custom-typography': ({ children }: { children: string }) =>
renderReference(children),
code(props: any) { code(props: any) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, className, node, ...rest } = props; const { children, className, node, ...rest } = props;
const match = /language-(\w+)/.exec(className || ''); const match = /language-(\w+)/.exec(className || '');
return match ? ( return match ? (
@ -183,12 +294,19 @@ const FloatingChatWidgetMarkdown = ({
{String(children).replace(/\n$/, '')} {String(children).replace(/\n$/, '')}
</SyntaxHighlighter> </SyntaxHighlighter>
) : ( ) : (
<code {...rest} className={classNames(className, 'text-wrap text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded')}> <code
{...rest}
className={classNames(
className,
'text-wrap text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded',
)}
>
{children} {children}
</code> </code>
); );
}, },
} as any} } as any
}
> >
{contentWithCursor} {contentWithCursor}
</Markdown> </Markdown>

View File

@ -1,18 +1,10 @@
import PdfDrawer from '@/components/pdf-drawer'; import PdfDrawer from '@/components/pdf-drawer';
import { useClickDrawer } from '@/components/pdf-drawer/hooks'; import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { MessageType, SharedFrom } from '@/constants/chat'; import { MessageType } from '@/constants/chat';
import { useFetchNextConversationSSE } from '@/hooks/chat-hooks';
import { useFetchFlowSSE } from '@/hooks/flow-hooks';
import { useFetchExternalChatInfo } from '@/hooks/use-chat-request'; import { useFetchExternalChatInfo } from '@/hooks/use-chat-request';
import i18n from '@/locales/config'; import i18n from '@/locales/config';
import { MessageCircle, Minimize2, Send, X } from 'lucide-react'; import { MessageCircle, Minimize2, Send, X } from 'lucide-react';
import React, { import React, { useCallback, useEffect, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { import {
useGetSharedChatSearchParams, useGetSharedChatSearchParams,
useSendSharedMessage, useSendSharedMessage,
@ -28,12 +20,7 @@ const FloatingChatWidget = () => {
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const { const { sharedId: conversationId, locale } = useGetSharedChatSearchParams();
sharedId: conversationId,
from,
locale,
visibleAvatar,
} = useGetSharedChatSearchParams();
// Check if we're in button-only mode or window-only mode // Check if we're in button-only mode or window-only mode
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
@ -58,14 +45,6 @@ const FloatingChatWidget = () => {
const { data: chatInfo } = useFetchExternalChatInfo(); const { data: chatInfo } = useFetchExternalChatInfo();
const useFetchAvatar = useMemo(() => {
return from === SharedFrom.Agent
? useFetchFlowSSE
: useFetchNextConversationSSE;
}, [from]);
const { data: avatarData } = useFetchAvatar();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer(); useClickDrawer();
@ -181,6 +160,40 @@ const FloatingChatWidget = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [displayMessages]); }, [displayMessages]);
// Render different content based on mode
// Master mode - handles everything and creates second iframe dynamically
useEffect(() => {
if (mode !== 'master') return;
// Create the chat window iframe dynamically when needed
const createChatWindow = () => {
// Check if iframe already exists in parent document
window.parent.postMessage(
{
type: 'CREATE_CHAT_WINDOW',
src: window.location.href.replace('mode=master', 'mode=window'),
},
'*',
);
};
createChatWindow();
// Listen for our own toggle events to show/hide the dynamic iframe
const handleToggle = (e: MessageEvent) => {
if (e.source === window) return; // Ignore our own messages
const chatWindow = document.getElementById(
'dynamic-chat-window',
) as HTMLIFrameElement;
if (chatWindow && e.data.type === 'TOGGLE_CHAT') {
chatWindow.style.display = e.data.isOpen ? 'block' : 'none';
}
};
window.addEventListener('message', handleToggle);
return () => window.removeEventListener('message', handleToggle);
}, [mode]);
// Play sound only when AI response is complete (not streaming chunks) // Play sound only when AI response is complete (not streaming chunks)
useEffect(() => { useEffect(() => {
if (derivedMessages && derivedMessages.length > 0 && !sendLoading) { if (derivedMessages && derivedMessages.length > 0 && !sendLoading) {
@ -271,41 +284,8 @@ const FloatingChatWidget = () => {
const messageCount = displayMessages?.length || 0; const messageCount = displayMessages?.length || 0;
// Render different content based on mode
if (mode === 'master') {
// Master mode - handles everything and creates second iframe dynamically
useEffect(() => {
// Create the chat window iframe dynamically when needed
const createChatWindow = () => {
// Check if iframe already exists in parent document
window.parent.postMessage(
{
type: 'CREATE_CHAT_WINDOW',
src: window.location.href.replace('mode=master', 'mode=window'),
},
'*',
);
};
createChatWindow();
// Listen for our own toggle events to show/hide the dynamic iframe
const handleToggle = (e: MessageEvent) => {
if (e.source === window) return; // Ignore our own messages
const chatWindow = document.getElementById(
'dynamic-chat-window',
) as HTMLIFrameElement;
if (chatWindow && e.data.type === 'TOGGLE_CHAT') {
chatWindow.style.display = e.data.isOpen ? 'block' : 'none';
}
};
window.addEventListener('message', handleToggle);
return () => window.removeEventListener('message', handleToggle);
}, []);
// Show just the button in master mode // Show just the button in master mode
if (mode === 'master') {
return ( return (
<div <div
className={`fixed bottom-6 right-6 z-50 transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`} className={`fixed bottom-6 right-6 z-50 transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
@ -678,6 +658,7 @@ const FloatingChatWidget = () => {
/> />
</div> </div>
<button <button
type="button"
onClick={handleSendMessage} onClick={handleSendMessage}
disabled={!inputValue.trim() || sendLoading} disabled={!inputValue.trim() || sendLoading}
className="p-3 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="p-3 bg-blue-600 text-white rounded-full hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"

View File

@ -70,7 +70,7 @@ export function UseGraphRagFormField({
<FormField <FormField
control={form.control} control={form.control}
name="parser_config.graphrag.use_graphrag" name="parser_config.graphrag.use_graphrag"
render={({ field }) => ( render={() => (
<FormItem defaultChecked={false} className=" items-center space-y-0 "> <FormItem defaultChecked={false} className=" items-center space-y-0 ">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FormLabel <FormLabel

View File

@ -5,7 +5,7 @@ import { Plus } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
interface ChunkResultBarProps { interface ChunkResultBarProps {
changeChunkTextMode: React.Dispatch<React.SetStateAction<string | number>>; changeChunkTextMode: (mode: ChunkTextMode) => void;
createChunk: (text: string) => void; createChunk: (text: string) => void;
isReadonly: boolean; isReadonly: boolean;
} }
@ -15,7 +15,7 @@ export default ({
isReadonly, isReadonly,
}: ChunkResultBarProps) => { }: ChunkResultBarProps) => {
const { t } = useTranslate('chunk'); const { t } = useTranslate('chunk');
const [textSelectValue, setTextSelectValue] = useState<string | number>( const [textSelectValue, setTextSelectValue] = useState<ChunkTextMode>(
ChunkTextMode.Full, ChunkTextMode.Full,
); );
const textSelectOptions = [ const textSelectOptions = [
@ -23,7 +23,7 @@ export default ({
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse }, { label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
]; ];
const changeTextSelectValue = (value: string | number) => { const changeTextSelectValue = (value: ChunkTextMode) => {
setTextSelectValue(value); setTextSelectValue(value);
changeChunkTextMode(value); changeChunkTextMode(value);
}; };

View File

@ -8,6 +8,7 @@ export interface FormatPreserveEditorProps {
key: keyof typeof parserKeyMap | 'text' | 'html'; key: keyof typeof parserKeyMap | 'text' | 'html';
type: string; type: string;
value: Array<{ [key: string]: string }>; value: Array<{ [key: string]: string }>;
params: ComponentParams;
}; };
onSave: (value: any) => void; onSave: (value: any) => void;
className?: string; className?: string;

View File

@ -4,6 +4,7 @@ import { isArray } from 'lodash';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import styles from '../../index.less'; import styles from '../../index.less';
import { IChunk } from '../../interface';
import { useParserInit } from './hook'; import { useParserInit } from './hook';
import { IJsonContainerProps } from './interface'; import { IJsonContainerProps } from './interface';
export const parserKeyMap = { export const parserKeyMap = {
@ -17,8 +18,6 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
isChunck, isChunck,
handleCheck, handleCheck,
selectedChunkIds, selectedChunkIds,
unescapeNewlines,
escapeNewlines,
onSave, onSave,
className, className,
textMode, textMode,
@ -26,13 +25,8 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
isReadonly, isReadonly,
} = props; } = props;
const { const { content, activeEditIndex, setActiveEditIndex, editDivRef } =
content, useParserInit({ initialValue });
setContent,
activeEditIndex,
setActiveEditIndex,
editDivRef,
} = useParserInit({ initialValue });
const parserKey = useMemo(() => { const parserKey = useMemo(() => {
const key = const key =
@ -46,11 +40,12 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
(e?: any, index?: number) => { (e?: any, index?: number) => {
setActiveEditIndex(index); setActiveEditIndex(index);
}, },
[setContent, setActiveEditIndex], [setActiveEditIndex],
); );
const handleSave = useCallback( const handleSave = useCallback(
(e: any) => { (e: any) => {
if (Array.isArray(content.value)) {
const saveData = { const saveData = {
...content, ...content,
value: content.value?.map((item, index) => { value: content.value?.map((item, index) => {
@ -64,18 +59,21 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
} }
}), }),
}; };
onSave(saveData); onSave(saveData as any);
}
setActiveEditIndex(undefined); setActiveEditIndex(undefined);
}, },
[content, onSave], [content, onSave, activeEditIndex, parserKey, setActiveEditIndex],
); );
useEffect(() => { useEffect(() => {
if (activeEditIndex !== undefined && editDivRef.current) { if (activeEditIndex !== undefined && editDivRef.current) {
editDivRef.current.focus(); editDivRef.current.focus();
if (typeof content.value !== 'string') {
editDivRef.current.textContent = editDivRef.current.textContent =
content.value[activeEditIndex][parserKey]; content.value[activeEditIndex][parserKey];
} }
}
}, [editDivRef, activeEditIndex, content, parserKey]); }, [editDivRef, activeEditIndex, content, parserKey]);
return ( return (
@ -122,7 +120,7 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
{activeEditIndex !== index && ( {activeEditIndex !== index && (
<div <div
className={cn( className={cn(
'text-text-secondary overflow-auto scrollbar-auto w-full', 'text-text-secondary overflow-auto scrollbar-auto w-full min-h-3',
{ {
[styles.contentEllipsis]: [styles.contentEllipsis]:
textMode === ChunkTextMode.Ellipse, textMode === ChunkTextMode.Ellipse,
@ -130,7 +128,8 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
)} )}
key={index} key={index}
onClick={(e) => { onClick={(e) => {
clickChunk(item); clickChunk(item as unknown as IChunk);
console.log('clickChunk', item, index);
if (!isReadonly) { if (!isReadonly) {
handleEdit(e, index); handleEdit(e, index);
} }

View File

@ -2,14 +2,13 @@ import { cn } from '@/lib/utils';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { ChunkTextMode } from '../../constant'; import { ChunkTextMode } from '../../constant';
import styles from '../../index.less'; import styles from '../../index.less';
import { IChunk } from '../../interface';
import { useParserInit } from './hook'; import { useParserInit } from './hook';
import { IObjContainerProps } from './interface'; import { IObjContainerProps } from './interface';
export const ObjectContainer = (props: IObjContainerProps) => { export const ObjectContainer = (props: IObjContainerProps) => {
const { const {
initialValue, initialValue,
isChunck, isChunck,
unescapeNewlines,
escapeNewlines,
onSave, onSave,
className, className,
textMode, textMode,
@ -19,7 +18,7 @@ export const ObjectContainer = (props: IObjContainerProps) => {
const { const {
content, content,
setContent, // setContent,
activeEditIndex, activeEditIndex,
setActiveEditIndex, setActiveEditIndex,
editDivRef, editDivRef,
@ -31,7 +30,7 @@ export const ObjectContainer = (props: IObjContainerProps) => {
// value: escapeNewlines(e.target.innerText), // value: escapeNewlines(e.target.innerText),
// })); // }));
setActiveEditIndex(1); setActiveEditIndex(1);
}, [setContent, setActiveEditIndex]); }, [setActiveEditIndex]);
const handleSave = useCallback( const handleSave = useCallback(
(e: any) => { (e: any) => {
@ -42,7 +41,7 @@ export const ObjectContainer = (props: IObjContainerProps) => {
onSave(saveData); onSave(saveData);
setActiveEditIndex(undefined); setActiveEditIndex(undefined);
}, },
[content, onSave], [content, onSave, setActiveEditIndex],
); );
useEffect(() => { useEffect(() => {
@ -81,14 +80,14 @@ export const ObjectContainer = (props: IObjContainerProps) => {
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse, [styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
}, },
)} )}
onClick={(e) => { onClick={() => {
clickChunk(content); clickChunk(content as unknown as IChunk);
if (!isReadonly) { if (!isReadonly) {
handleEdit(e); handleEdit();
} }
}} }}
> >
{content.value} {content.value as string}
</div> </div>
)} )}
</section> </section>

View File

@ -4,7 +4,6 @@ import { useCreateChunk, useDeleteChunk } from '@/hooks/chunk-hooks';
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks'; import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { useFetchMessageTrace } from '@/hooks/use-agent-request'; import { useFetchMessageTrace } from '@/hooks/use-agent-request';
import { IChunk } from '@/interfaces/database/knowledge';
import kbService from '@/services/knowledge-service'; import kbService from '@/services/knowledge-service';
import { formatSecondsToHumanReadable } from '@/utils/date'; import { formatSecondsToHumanReadable } from '@/utils/date';
import { buildChunkHighlights } from '@/utils/document-util'; import { buildChunkHighlights } from '@/utils/document-util';
@ -20,7 +19,7 @@ import {
PipelineResultSearchParams, PipelineResultSearchParams,
TimelineNodeType, TimelineNodeType,
} from './constant'; } from './constant';
import { IDslComponent, IPipelineFileLogDetail } from './interface'; import { IChunk, IDslComponent, IPipelineFileLogDetail } from './interface';
export const useFetchPipelineFileLogDetail = ({ export const useFetchPipelineFileLogDetail = ({
isAgent = false, isAgent = false,
@ -64,7 +63,6 @@ export const useHandleChunkCardClick = () => {
const [selectedChunk, setSelectedChunk] = useState<IChunk>(); const [selectedChunk, setSelectedChunk] = useState<IChunk>();
const handleChunkCardClick = useCallback((chunk: IChunk) => { const handleChunkCardClick = useCallback((chunk: IChunk) => {
console.log('click-chunk-->', chunk);
setSelectedChunk(chunk); setSelectedChunk(chunk);
}, []); }, []);
@ -75,7 +73,9 @@ export const useGetChunkHighlights = (selectedChunk?: IChunk) => {
const [size, setSize] = useState({ width: 849, height: 1200 }); const [size, setSize] = useState({ width: 849, height: 1200 });
const highlights: IHighlight[] = useMemo(() => { const highlights: IHighlight[] = useMemo(() => {
return selectedChunk ? buildChunkHighlights(selectedChunk, size) : []; return selectedChunk
? buildChunkHighlights(selectedChunk as any, size)
: [];
}, [selectedChunk, size]); }, [selectedChunk, size]);
const setWidthAndHeight = useCallback((width: number, height: number) => { const setWidthAndHeight = useCallback((width: number, height: number) => {

View File

@ -126,8 +126,17 @@ export interface IPipelineFileLogDetail {
} }
export interface IChunk { export interface IChunk {
available_int?: number; // Whether to enable, 0: not enabled, 1: enabled
chunk_id?: string;
content_with_weight?: string;
doc_id?: string;
doc_name?: string;
image_id?: string;
important_kwd?: string[];
question_kwd?: string[]; // keywords
tag_kwd?: string[];
positions: number[][]; positions: number[][];
image_id: string; tag_feas?: Record<string, number>;
text: string; text: string;
} }

View File

@ -40,12 +40,17 @@ const ParserContainer = (props: IProps) => {
const initialValue = useMemo(() => { const initialValue = useMemo(() => {
const outputs = data?.value?.obj?.params?.outputs; const outputs = data?.value?.obj?.params?.outputs;
const key = outputs?.output_format?.value; const key = outputs?.output_format?.value;
if (!outputs || !key) return { key: '', type: '', value: [] }; if (!outputs || !key)
const value = outputs[key]?.value; return {
const type = outputs[key]?.type; key: '' as 'text' | 'html' | 'json' | 'chunks',
type: '',
value: [],
};
const value = outputs[key as keyof typeof outputs]?.value;
const type = outputs[key as keyof typeof outputs]?.type;
console.log('outputs-->', outputs, data, key, value); console.log('outputs-->', outputs, data, key, value);
return { return {
key, key: key as 'text' | 'html' | 'json' | 'chunks',
type, type,
value, value,
params: data?.value?.obj?.params, params: data?.value?.obj?.params,
@ -95,7 +100,7 @@ const ParserContainer = (props: IProps) => {
const handleRemoveChunk = useCallback(async () => { const handleRemoveChunk = useCallback(async () => {
if (selectedChunkIds.length > 0) { if (selectedChunkIds.length > 0) {
initialText.value = initialText.value.filter( initialText.value = initialText.value.filter(
(item: any, index: number) => !selectedChunkIds.includes(index + ''), (_item: any, index: number) => !selectedChunkIds.includes(index + ''),
); );
setIsChange(true); setIsChange(true);
setSelectedChunkIds([]); setSelectedChunkIds([]);
@ -118,7 +123,7 @@ const ParserContainer = (props: IProps) => {
const selectAllChunk = useCallback( const selectAllChunk = useCallback(
(checked: boolean) => { (checked: boolean) => {
setSelectedChunkIds( setSelectedChunkIds(
checked ? initialText.value.map((x, index: number) => index) : [], checked ? initialText.value.map((_x: any, index: number) => index) : [],
); );
}, },
[initialText.value], [initialText.value],

View File

@ -12,6 +12,7 @@ import { RunningStatus } from '../dataset/constant';
import { LogTabs } from './dataset-common'; import { LogTabs } from './dataset-common';
import { DatasetFilter } from './dataset-filter'; import { DatasetFilter } from './dataset-filter';
import { useFetchFileLogList, useFetchOverviewTital } from './hook'; import { useFetchFileLogList, useFetchOverviewTital } from './hook';
import { DocumentLog, IFileLogItem } from './interface';
import FileLogsTable from './overview-table'; import FileLogsTable from './overview-table';
interface StatCardProps { interface StatCardProps {
@ -212,18 +213,25 @@ const FileLogsPage: FC = () => {
return tableOriginData.logs.map((item) => { return tableOriginData.logs.map((item) => {
return { return {
...item, ...item,
fileName: item.document_name, status: item.operation_status as RunningStatus,
statusName: item.operation_status, statusName: RunningStatusMap[item.operation_status as RunningStatus],
}; } as unknown as IFileLogItem & DocumentLog;
}); });
} }
return [];
}, [tableOriginData]); }, [tableOriginData]);
const changeActiveLogs = (active: (typeof LogTabs)[keyof typeof LogTabs]) => { const changeActiveLogs = (active: (typeof LogTabs)[keyof typeof LogTabs]) => {
setFilterValue({}); setFilterValue({});
setActive(active); setActive(active);
}; };
const handlePaginationChange = (page: number, pageSize: number) => { const handlePaginationChange = ({
page,
pageSize,
}: {
page: number;
pageSize: number;
}) => {
console.log('Pagination changed:', { page, pageSize }); console.log('Pagination changed:', { page, pageSize });
setPagination({ setPagination({
...pagination, ...pagination,

View File

@ -57,6 +57,6 @@ export interface IFileLogItem {
update_time: number; update_time: number;
} }
export interface IFileLogList { export interface IFileLogList {
logs: IFileLogItem[]; logs: Array<IFileLogItem & DocumentLog>;
total: number; total: number;
} }

View File

@ -1,7 +1,4 @@
import { import { DataFlowSelect } from '@/components/data-pipeline-select';
DataFlowSelect,
IDataPipelineSelectNode,
} from '@/components/data-pipeline-select';
import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields';
import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -22,7 +19,6 @@ import {
} from '../dataset/generate-button/generate'; } from '../dataset/generate-button/generate';
import { ChunkMethodForm } from './chunk-method-form'; import { ChunkMethodForm } from './chunk-method-form';
import ChunkMethodLearnMore from './chunk-method-learn-more'; import ChunkMethodLearnMore from './chunk-method-learn-more';
import { IDataPipelineNodeProps } from './components/link-data-pipeline';
import { MainContainer } from './configuration-form-container'; import { MainContainer } from './configuration-form-container';
import { ChunkMethodItem, ParseTypeItem } from './configuration/common-item'; import { ChunkMethodItem, ParseTypeItem } from './configuration/common-item';
import { formSchema } from './form-schema'; import { formSchema } from './form-schema';
@ -85,7 +81,7 @@ export default function DatasetSettings() {
}, },
}); });
const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form); const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form);
const [pipelineData, setPipelineData] = useState<IDataPipelineNodeProps>(); // const [pipelineData, setPipelineData] = useState<IDataPipelineNodeProps>();
const [graphRagGenerateData, setGraphRagGenerateData] = const [graphRagGenerateData, setGraphRagGenerateData] =
useState<IGenerateLogButtonProps>(); useState<IGenerateLogButtonProps>();
const [raptorGenerateData, setRaptorGenerateData] = const [raptorGenerateData, setRaptorGenerateData] =
@ -94,13 +90,13 @@ export default function DatasetSettings() {
useEffect(() => { useEffect(() => {
console.log('🚀 ~ DatasetSettings ~ knowledgeDetails:', knowledgeDetails); console.log('🚀 ~ DatasetSettings ~ knowledgeDetails:', knowledgeDetails);
if (knowledgeDetails) { if (knowledgeDetails) {
const data: IDataPipelineNodeProps = { // const data: IDataPipelineNodeProps = {
id: knowledgeDetails.pipeline_id, // id: knowledgeDetails.pipeline_id,
name: knowledgeDetails.pipeline_name, // name: knowledgeDetails.pipeline_name,
avatar: knowledgeDetails.pipeline_avatar, // avatar: knowledgeDetails.pipeline_avatar,
linked: true, // linked: true,
}; // };
setPipelineData(data); // setPipelineData(data);
setGraphRagGenerateData({ setGraphRagGenerateData({
finish_at: knowledgeDetails.graphrag_task_finish_at, finish_at: knowledgeDetails.graphrag_task_finish_at,
task_id: knowledgeDetails.graphrag_task_id, task_id: knowledgeDetails.graphrag_task_id,
@ -121,17 +117,17 @@ export default function DatasetSettings() {
console.error('An error occurred during submission:', error); console.error('An error occurred during submission:', error);
} }
} }
const handleLinkOrEditSubmit = ( // const handleLinkOrEditSubmit = (
data: IDataPipelineSelectNode | undefined, // data: IDataPipelineSelectNode | undefined,
) => { // ) => {
console.log('🚀 ~ DatasetSettings ~ data:', data); // console.log('🚀 ~ DatasetSettings ~ data:', data);
if (data) { // if (data) {
setPipelineData(data); // setPipelineData(data);
form.setValue('pipeline_id', data.id || ''); // form.setValue('pipeline_id', data.id || '');
// form.setValue('pipeline_name', data.name || ''); // // form.setValue('pipeline_name', data.name || '');
// form.setValue('pipeline_avatar', data.avatar || ''); // // form.setValue('pipeline_avatar', data.avatar || '');
} // }
}; // };
const handleDeletePipelineTask = (type: GenerateType) => { const handleDeletePipelineTask = (type: GenerateType) => {
if (type === GenerateType.KnowledgeGraph) { if (type === GenerateType.KnowledgeGraph) {
@ -203,11 +199,7 @@ export default function DatasetSettings() {
)} )}
<Divider /> <Divider />
{parseType === 1 && ( {parseType === 1 && <ChunkMethodForm />}
<ChunkMethodForm
selectedTag={selectedTag as DocumentParserType}
/>
)}
{/* <LinkDataPipeline {/* <LinkDataPipeline
data={pipelineData} data={pipelineData}

View File

@ -9,7 +9,7 @@ interface IProps extends IModalProps<any> {
data: any; data: any;
} }
const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => { const MindMapDrawer = ({ data, hideModal, loading }: IProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const percent = usePendingMindMap(); const percent = usePendingMindMap();
return ( return (

View File

@ -14,15 +14,14 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from '@/components/ui/popover'; } from '@/components/ui/popover';
import { Separator } from '@/components/ui/separator';
import { import {
useAllTestingResult, useAllTestingResult,
useSelectTestingResult, useSelectTestingResult,
} from '@/hooks/knowledge-hooks'; } from '@/hooks/knowledge-hooks';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Separator } from '@radix-ui/react-select';
import { CheckIcon, ChevronDown, Files, XIcon } from 'lucide-react'; import { CheckIcon, ChevronDown, Files, XIcon } from 'lucide-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
interface IProps { interface IProps {
onTesting(documentIds: string[]): void; onTesting(documentIds: string[]): void;
@ -35,11 +34,9 @@ const RetrievalDocuments = ({
selectedDocumentIds, selectedDocumentIds,
setSelectedDocumentIds, setSelectedDocumentIds,
}: IProps) => { }: IProps) => {
const { t } = useTranslation();
const { documents: documentsAll } = useAllTestingResult(); const { documents: documentsAll } = useAllTestingResult();
const { documents } = useSelectTestingResult(); const { documents } = useSelectTestingResult();
const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const maxCount = 3;
const { documents: useDocuments } = { const { documents: useDocuments } = {
documents: documents:
documentsAll?.length > documents?.length ? documentsAll : documents, documentsAll?.length > documents?.length ? documentsAll : documents,

View File

@ -337,6 +337,8 @@ export const useRenameSearch = () => {
}); });
const detail = reponse.data?.data; const detail = reponse.data?.data;
console.log('detail-->', detail); console.log('detail-->', detail);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, created_by, update_time, ...searchDataTemp } = detail; const { id, created_by, update_time, ...searchDataTemp } = detail;
res = await updateSearch({ res = await updateSearch({
...searchDataTemp, ...searchDataTemp,

View File

@ -5,14 +5,13 @@ import { Button } from '@/components/ui/button';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { useState } from 'react';
import { useFetchSearchList, useRenameSearch } from './hooks'; import { useFetchSearchList, useRenameSearch } from './hooks';
import { SearchCard } from './search-card'; import { SearchCard } from './search-card';
export default function SearchList() { export default function SearchList() {
// const { data } = useFetchFlowList(); // const { data } = useFetchFlowList();
const { t } = useTranslate('search'); const { t } = useTranslate('search');
const [isEdit, setIsEdit] = useState(false); // const [isEdit, setIsEdit] = useState(false);
const { const {
data: list, data: list,
searchParams, searchParams,
@ -36,11 +35,11 @@ export default function SearchList() {
}); });
}; };
const openCreateModalFun = () => { const openCreateModalFun = () => {
setIsEdit(false); // setIsEdit(false);
showSearchRenameModal(); showSearchRenameModal();
}; };
const handlePageChange = (page: number, pageSize: number) => { const handlePageChange = (page: number, pageSize: number) => {
setIsEdit(false); // setIsEdit(false);
setSearchListParams({ ...searchParams, page, page_size: pageSize }); setSearchListParams({ ...searchParams, page, page_size: pageSize });
}; };