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,44 +23,62 @@ 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"
} }
bxs = ocr(np.array(img))
txt = "\n".join([t[0] for _, t in bxs if t[0]])
eng = lang.lower() == "english" eng = lang.lower() == "english"
callback(0.4, "Finish OCR: (%s ...)" % txt[:12])
if (eng and len(txt.split()) > 32) or len(txt) > 32:
tokenize(doc, txt, eng)
callback(0.8, "OCR results is too long to use CV LLM.")
return [doc]
try: if any(filename.lower().endswith(ext) for ext in VIDEO_EXTS):
callback(0.4, "Use CV LLM to describe the picture.") try:
cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang) doc.update({"doc_type_kwd": "video"})
img_binary = io.BytesIO() cv_mdl = LLMBundle(tenant_id, llm_type=LLMType.IMAGE2TEXT, lang=lang)
img.save(img_binary, format='JPEG') ans = cv_mdl.chat(system="", history=[], gen_conf={}, video_bytes=binary, filename=filename)
img_binary.seek(0) callback(0.8, "CV LLM respond: %s ..." % ans[:32])
ans = cv_mdl.describe(img_binary.read()) ans += "\n" + ans
callback(0.8, "CV LLM respond: %s ..." % ans[:32]) tokenize(doc, ans, eng)
txt += "\n" + ans return [doc]
tokenize(doc, txt, eng) except Exception as e:
return [doc] callback(prog=-1, msg=str(e))
except Exception as e: else:
callback(prog=-1, msg=str(e)) img = Image.open(io.BytesIO(binary)).convert("RGB")
doc.update(
{
"image": img,
"doc_type_kwd": "image",
}
)
bxs = ocr(np.array(img))
txt = "\n".join([t[0] for _, t in bxs if t[0]])
callback(0.4, "Finish OCR: (%s ...)" % txt[:12])
if (eng and len(txt.split()) > 32) or len(txt) > 32:
tokenize(doc, txt, eng)
callback(0.8, "OCR results is too long to use CV LLM.")
return [doc]
try:
callback(0.4, "Use CV LLM to describe the picture.")
cv_mdl = LLMBundle(tenant_id, LLMType.IMAGE2TEXT, lang=lang)
img_binary = io.BytesIO()
img.save(img_binary, format="JPEG")
img_binary.seek(0)
ans = cv_mdl.describe(img_binary.read())
callback(0.8, "CV LLM respond: %s ..." % ans[:32])
txt += "\n" + ans
tokenize(doc, txt, eng)
return [doc]
except Exception as e:
callback(prog=-1, msg=str(e))
return [] return []
@ -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(
if (!documentId) return; (
if (!isPdf && documentUrl) { documentId: string,
window.open(documentUrl, '_blank'); chunk: IReferenceChunk,
} else if (clickDocumentButton) { isPdf: boolean,
clickDocumentButton(documentId, chunk); documentUrl?: string,
} ) =>
}, [clickDocumentButton]); () => {
if (!documentId) return;
if (!isPdf && documentUrl) {
window.open(documentUrl, '_blank');
} else if (clickDocumentButton) {
clickDocumentButton(documentId, chunk);
}
},
[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,90 +101,173 @@ const FloatingChatWidgetMarkdown = ({
}); });
}; };
const getReferenceInfo = useCallback((chunkIndex: number) => { const getReferenceInfo = useCallback(
const chunkItem = reference?.chunks?.[chunkIndex]; (chunkIndex: number) => {
if (!chunkItem) return null; const chunkItem = reference?.chunks?.[chunkIndex];
const docAggsArray = Array.isArray(reference?.doc_aggs) ? reference.doc_aggs : Object.values(reference?.doc_aggs ?? {}); if (!chunkItem) return null;
const document = docAggsArray.find((x: any) => x?.doc_id === chunkItem?.document_id) as any; const docAggsArray = Array.isArray(reference?.doc_aggs)
const documentId = document?.doc_id; ? reference.doc_aggs
const documentUrl = document?.url ?? (documentId ? getDocumentUrl(documentId) : undefined); : Object.values(reference?.doc_aggs ?? {});
const fileThumbnail = documentId ? fileThumbnails[documentId] : ''; const document = docAggsArray.find(
const fileExtension = documentId ? getExtension(document?.doc_name ?? '') : ''; (x: any) => x?.doc_id === chunkItem?.document_id,
return { documentUrl, fileThumbnail, fileExtension, imageId: chunkItem.image_id, chunkItem, documentId, document }; ) as any;
}, [fileThumbnails, reference, getDocumentUrl]); const documentId = document?.doc_id;
const documentUrl =
document?.url ?? (documentId ? getDocumentUrl(documentId) : undefined);
const fileThumbnail = documentId ? fileThumbnails[documentId] : '';
const fileExtension = documentId
? getExtension(document?.doc_name ?? '')
: '';
return {
documentUrl,
fileThumbnail,
fileExtension,
imageId: chunkItem.image_id,
chunkItem,
documentId,
document,
};
},
[fileThumbnails, reference, getDocumentUrl],
);
const getPopoverContent = useCallback((chunkIndex: number) => { const getPopoverContent = useCallback(
const info = getReferenceInfo(chunkIndex); (chunkIndex: number) => {
if (!info) {
return <div className="p-2 text-xs text-red-500">Error: Missing document information.</div>;
}
const { documentUrl, fileThumbnail, fileExtension, imageId, chunkItem, documentId, document } = info;
return (
<div key={`popover-content-${chunkItem.id}`} className="flex gap-2 widget-citation-content">
{imageId && (
<Popover 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>
)}
<div className="space-y-2 flex-1 min-w-0">
<div
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"
></div>
{documentId && (
<Flex gap={'small'} align="center">
{fileThumbnail ? (
<img src={fileThumbnail} alt={document?.doc_name} className="w-6 h-6 rounded" />
) : (
<SvgIcon name={`file-icon/${fileExtension}`} width={20} />
)}
<Tooltip title={!documentUrl && fileExtension !== 'pdf' ? 'Document link unavailable' : document.doc_name}>
<Button
type="link"
size="small"
className="p-0 text-xs break-words h-auto text-left flex-1"
onClick={handleDocumentButtonClick(documentId, chunkItem, fileExtension === 'pdf', documentUrl)}
disabled={!documentUrl && fileExtension !== 'pdf'}
style={{ whiteSpace: 'normal' }}
>
<span className="truncate">{document?.doc_name ?? 'Unnamed Document'}</span>
</Button>
</Tooltip>
</Flex>
)}
</div>
</div>
);
}, [getReferenceInfo, handleDocumentButtonClick]);
const renderReference = useCallback((text: string) => {
return reactStringReplace(text, currentReg, (match, i) => {
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 (
<div className="p-2 text-xs text-red-500">
Error: Missing document information.
</div>
);
} }
const { imageId, chunkItem, documentId, fileExtension, documentUrl } = info; const {
documentUrl,
if (showImage(chunkItem?.doc_type)) { fileThumbnail,
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)} />; fileExtension,
} imageId,
chunkItem,
documentId,
document,
} = info;
return ( return (
<Popover <div
content={getPopoverContent(chunkIndex)} key={`popover-content-${chunkItem.id}`}
key={`popover-${i}`} className="flex gap-2 widget-citation-content"
> >
<InfoCircleOutlined className={styles.referenceIcon} /> {imageId && (
</Popover> <Popover
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>
)}
<div className="space-y-2 flex-1 min-w-0">
<div
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"
></div>
{documentId && (
<Flex gap={'small'} align="center">
{fileThumbnail ? (
<img
src={fileThumbnail}
alt={document?.doc_name}
className="w-6 h-6 rounded"
/>
) : (
<SvgIcon name={`file-icon/${fileExtension}`} width={20} />
)}
<Tooltip
title={
!documentUrl && fileExtension !== 'pdf'
? 'Document link unavailable'
: document.doc_name
}
>
<Button
type="link"
size="small"
className="p-0 text-xs break-words h-auto text-left flex-1"
onClick={handleDocumentButtonClick(
documentId,
chunkItem,
fileExtension === 'pdf',
documentUrl,
)}
disabled={!documentUrl && fileExtension !== 'pdf'}
style={{ whiteSpace: 'normal' }}
>
<span className="truncate">
{document?.doc_name ?? 'Unnamed Document'}
</span>
</Button>
</Tooltip>
</Flex>
)}
</div>
</div>
); );
}); },
}, [getPopoverContent, getReferenceInfo, handleDocumentButtonClick]); [getReferenceInfo, handleDocumentButtonClick],
);
const renderReference = useCallback(
(text: string) => {
return reactStringReplace(text, currentReg, (match, i) => {
const chunkIndex = getChunkIndex(match);
const info = getReferenceInfo(chunkIndex);
if (!info) {
return (
<Tooltip key={`err-tooltip-${i}`} title="Reference unavailable">
<InfoCircleOutlined className={styles.referenceIcon} />
</Tooltip>
);
}
const { imageId, chunkItem, documentId, fileExtension, documentUrl } =
info;
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 (
<Popover content={getPopoverContent(chunkIndex)} key={`popover-${i}`}>
<InfoCircleOutlined className={styles.referenceIcon} />
</Popover>
);
});
},
[getPopoverContent, getReferenceInfo, handleDocumentButtonClick],
);
return ( return (
<div className="floating-chat-widget"> <div className="floating-chat-widget">
@ -167,28 +275,38 @@ 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), {
code(props: any) { 'custom-typography': ({ children }: { children: string }) =>
const { children, className, node, ...rest } = props; renderReference(children),
const match = /language-(\w+)/.exec(className || ''); code(props: any) {
return match ? ( // eslint-disable-next-line @typescript-eslint/no-unused-vars
<SyntaxHighlighter const { children, className, node, ...rest } = props;
{...omit(rest, 'inline')} const match = /language-(\w+)/.exec(className || '');
PreTag="div" return match ? (
language={match[1]} <SyntaxHighlighter
style={isDarkTheme ? oneDark : oneLight} {...omit(rest, 'inline')}
wrapLongLines PreTag="div"
> language={match[1]}
{String(children).replace(/\n$/, '')} style={isDarkTheme ? oneDark : oneLight}
</SyntaxHighlighter> wrapLongLines
) : ( >
<code {...rest} className={classNames(className, 'text-wrap text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded')}> {String(children).replace(/\n$/, '')}
{children} </SyntaxHighlighter>
</code> ) : (
); <code
}, {...rest}
} as any} className={classNames(
className,
'text-wrap text-xs bg-gray-200 dark:bg-gray-700 px-1 py-0.5 rounded',
)}
>
{children}
</code>
);
},
} 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 // Show just the button in master mode
if (mode === 'master') { 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
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,35 +40,39 @@ 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) => {
const saveData = { if (Array.isArray(content.value)) {
...content, const saveData = {
value: content.value?.map((item, index) => { ...content,
if (index === activeEditIndex) { value: content.value?.map((item, index) => {
return { if (index === activeEditIndex) {
...item, return {
[parserKey]: e.target.textContent || '', ...item,
}; [parserKey]: e.target.textContent || '',
} else { };
return item; } else {
} return item;
}), }
}; }),
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();
editDivRef.current.textContent = if (typeof content.value !== 'string') {
content.value[activeEditIndex][parserKey]; editDivRef.current.textContent =
content.value[activeEditIndex][parserKey];
}
} }
}, [editDivRef, activeEditIndex, content, parserKey]); }, [editDivRef, activeEditIndex, content, parserKey]);
@ -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 });
}; };