mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-04 01:25:07 +08:00
Compare commits
7 Commits
bd94b5dfb5
...
ccb1c269e8
| Author | SHA1 | Date | |
|---|---|---|---|
| ccb1c269e8 | |||
| 6dfb0c245c | |||
| 72d1047a8f | |||
| bece37e6c8 | |||
| 59cb0eb8bc | |||
| fc56217eb3 | |||
| 723cf9443e |
@ -21,7 +21,6 @@ import pandas as pd
|
||||
import pymysql
|
||||
import psycopg2
|
||||
import pyodbc
|
||||
import ibm_db
|
||||
from agent.tools.base import ToolParamBase, ToolBase, ToolMeta
|
||||
from api.utils.api_utils import timeout
|
||||
|
||||
@ -125,6 +124,7 @@ class ExeSQL(ToolBase, ABC):
|
||||
)
|
||||
db = pyodbc.connect(conn_str)
|
||||
elif self._param.db_type == 'IBM DB2':
|
||||
import ibm_db
|
||||
conn_str = (
|
||||
f"DATABASE={self._param.database};"
|
||||
f"HOSTNAME={self._param.host};"
|
||||
@ -162,6 +162,8 @@ class ExeSQL(ToolBase, ABC):
|
||||
if pd.api.types.is_datetime64_any_dtype(df[col]):
|
||||
df[col] = df[col].dt.strftime("%Y-%m-%d")
|
||||
|
||||
df = df.where(pd.notnull(df), None)
|
||||
|
||||
sql_res.append(convert_decimals(df.to_dict(orient="records")))
|
||||
formalized_content.append(df.to_markdown(index=False, floatfmt=".6f"))
|
||||
|
||||
@ -197,6 +199,8 @@ class ExeSQL(ToolBase, ABC):
|
||||
if pd.api.types.is_datetime64_any_dtype(single_res[col]):
|
||||
single_res[col] = single_res[col].dt.strftime('%Y-%m-%d')
|
||||
|
||||
single_res = single_res.where(pd.notnull(single_res), None)
|
||||
|
||||
sql_res.append(convert_decimals(single_res.to_dict(orient='records')))
|
||||
formalized_content.append(single_res.to_markdown(index=False, floatfmt=".6f"))
|
||||
|
||||
|
||||
@ -98,6 +98,15 @@ def login():
|
||||
return get_json_result(data=False, code=settings.RetCode.SERVER_ERROR, message="Fail to crypt password")
|
||||
|
||||
user = UserService.query_user(email, password)
|
||||
|
||||
if user and hasattr(user, 'is_active') and user.is_active == "0":
|
||||
return get_json_result(
|
||||
data=False,
|
||||
code=settings.RetCode.FORBIDDEN,
|
||||
message="This account has been disabled, please contact the administrator!",
|
||||
)
|
||||
|
||||
|
||||
if user:
|
||||
response_data = user.to_json()
|
||||
user.access_token = get_uuid()
|
||||
|
||||
@ -132,7 +132,6 @@ dependencies = [
|
||||
"litellm>=1.74.15.post1",
|
||||
"flask-mail>=0.10.0",
|
||||
"lark>=1.2.2",
|
||||
"ibm-db>=3.2.7",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
28
uv.lock
generated
28
uv.lock
generated
@ -2504,32 +2504,6 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/34/87/7940713f929d0280cff1bde207479cb588a0d3a4dd49a0e2e69bfff46363/hyppo-0.4.0-py3-none-any.whl", hash = "sha256:4e75565b8deb601485cd7bc1b5c3f44e6ddf329136fc81e65d011f9b4e95132f" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ibm-db"
|
||||
version = "3.2.7"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/dd/a5/182413f7fe28ee7a67d8be5afa1139ccc60cfb5c13c0e9be81e2205bddbb/ibm_db-3.2.7.tar.gz", hash = "sha256:b3c3b4550364a43bf1daa4519b668e6e00e7c3935291f8c444c4ec989417e861" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/50/cd/a6549ed35424875f07ea89dbd44093b7d9dc4a03d9e29e56c5167bd7d568/ibm_db-3.2.7-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:7c2b451ffe67be602e93d94b2d2042dd051ec0757cfd6e4d7344cb594f2d3508" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/50/5a/be83503ec6ef9b2a47175b38b1099595a8d06237ac3fcc82d967b834672a/ibm_db-3.2.7-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:9a1b139a9c21ff7216aac83ba29dceb6c8a9df3d6aee44ff1fe845cb60d3caed" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/cc/fa/379405785d27d10110992ee17f150c8ef1ee0c3eadca2d1451c8c03ff075/ibm_db-3.2.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e5e60297b4680cc566caa67f513aa68883ef48b0c612028a38883620807b09c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/52/e1/c18aee07888666b3249b4f54d3cb67ae5041600b5fc3ed281817e868eaa8/ibm_db-3.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf1c30e67e9e573e33524c393a1426e0dffa2da34ba42a0ec510e0f75766976f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/26/01/f80a407192351aa304206504d6b15ccbb061f45dc9fc3e37c8c00c7e222b/ibm_db-3.2.7-cp310-cp310-win32.whl", hash = "sha256:171014c2caa0419055943ff3badae5118cc3a191360f03b80c8366ef374d5c28" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/98/1b/29f98a0d4d9896635d7b7fa53a51f8753214f0deed23ac7987d726299b12/ibm_db-3.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:3425c158a65dd43e4b09dc968c18042a656ed6ef2e1db0164f032e97681823b7" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c8/04/18e42549f498569db7a437453db51ae3d61105ad4da7b1fe64e9e59aedee/ibm_db-3.2.7-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:ba493d07e1845b8b1169ad27ace92f0ff540cc9a623f2753b8c68dc66c59d7df" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c5/f3/85c8963ee8047183c435059abaf40771d40c6e9268ca32d66be0b66b7a6c/ibm_db-3.2.7-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:abed0a7c644b9ddf2c49bf5c0938f936f0b2dffd1703c9819440021be141716e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/6d/fb/8d2ff4b6bcd6b05d13af855bedc47e597430a6c3372e0ab7579659cad9bb/ibm_db-3.2.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cabd3d3e8c879ef60d91e1fe1356cf8603f8b4b69cc7dda39d4a8698a055044" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/77/26/c43e02bb4cf62b1e93bf617440f5da6d3888935ef09d4fbd86fe07f3f920/ibm_db-3.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aab5dceec45d69b0bbd333be66597dbaedf663c6c56a0fbd6196ecd1836e4095" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/05/d0/787d7a9864d3782238ca3b14a4484c642931e1363a760b0828c9ee26ad58/ibm_db-3.2.7-cp311-cp311-win32.whl", hash = "sha256:16272ad07912051d9ab5cbe3a9e2d3d888365d071334f9620d8e0b2ed69ee4f9" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1e/7c/998c663d0a65984c76c2c2c8d01cdbdd174bd817359e0e4024b9b316a9cc/ibm_db-3.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:4b479e92b6954ab7f65c9d247a65fb0cde6a48899f71a8881b58023c0ace1f49" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/4d/0f/048679ca8516b73f3547c64f968b9654181d79f6cd2706914f37c2486da3/ibm_db-3.2.7-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:24e8a538475997f20569f221247808507b63349df51119fe9b2f8e48a0bf6f9b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f0/36/4cc64c70ebc74b2765005cd5357df18512c358cc6f5e87c6b0e70cff4053/ibm_db-3.2.7-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:24a53fb8e3c200bf2a55095f1ae4c065f2136f8be87ca1db89a874bd82d88ea5" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/09/d7/ae63f1257736c5e6a06cd2be133b4bc38f68893504f046b4c850144b65cd/ibm_db-3.2.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91f68be7bd0d2940023da43d0a94f196fe267ca825df7874b8174583c8678ea0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d9/24/af465d93f549b0dbc944f256cdb2b470574018285e1478b6c50305a609ac/ibm_db-3.2.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d39fe5001c078f2b824d1805ca9737060203a00a9dd9a8fe4b6f6b32b271cb5" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/48/04/c512eed2c701e8762e90f000fd1a768dd749ffb070a62b8cd92722c16327/ibm_db-3.2.7-cp312-cp312-win32.whl", hash = "sha256:20388753f52050e07e845b74146dbbe3f892dcfdfb015638e8f57c2fb2e056b8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/07/cc/ae978e6d020f3b17f2e68cc2c60fe8381fffd1271608e871b5b4ee6434f2/ibm_db-3.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:6e507ddf93b8406b0b88ff6bf07658a3100ce98cb1e735e5ec8e0a56e30ea856" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
@ -5350,7 +5324,6 @@ dependencies = [
|
||||
{ name = "html-text" },
|
||||
{ name = "httpx", extra = ["socks"] },
|
||||
{ name = "huggingface-hub" },
|
||||
{ name = "ibm-db" },
|
||||
{ name = "infinity-emb" },
|
||||
{ name = "infinity-sdk" },
|
||||
{ name = "itsdangerous" },
|
||||
@ -5507,7 +5480,6 @@ requires-dist = [
|
||||
{ name = "html-text", specifier = "==0.6.2" },
|
||||
{ name = "httpx", extras = ["socks"], specifier = "==0.27.2" },
|
||||
{ name = "huggingface-hub", specifier = ">=0.25.0,<0.26.0" },
|
||||
{ name = "ibm-db", specifier = ">=3.2.7" },
|
||||
{ name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" },
|
||||
{ name = "infinity-sdk", specifier = "==0.6.0.dev5" },
|
||||
{ name = "itsdangerous", specifier = "==2.1.2" },
|
||||
|
||||
58
web/src/components/floating-chat-widget-markdown.less
Normal file
58
web/src/components/floating-chat-widget-markdown.less
Normal file
@ -0,0 +1,58 @@
|
||||
/* floating-chat-widget-markdown.less */
|
||||
|
||||
.widget-citation-popover {
|
||||
max-width: 90vw;
|
||||
/* Use viewport width for better responsiveness */
|
||||
width: max-content;
|
||||
|
||||
.ant-popover-inner {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ant-popover-inner-content {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive breakpoints for popover width */
|
||||
@media (min-width: 480px) {
|
||||
.widget-citation-popover {
|
||||
max-width: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
.widget-citation-content {
|
||||
|
||||
p,
|
||||
div,
|
||||
span,
|
||||
button {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.floating-chat-widget {
|
||||
|
||||
/* General styles for markdown content within the widget */
|
||||
p,
|
||||
div,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Enhanced image styles */
|
||||
img,
|
||||
.ant-image,
|
||||
.ant-image-img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
border-radius: 8px;
|
||||
margin: 8px 0 !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
199
web/src/components/floating-chat-widget-markdown.tsx
Normal file
199
web/src/components/floating-chat-widget-markdown.tsx
Normal file
@ -0,0 +1,199 @@
|
||||
import Image from '@/components/image';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useFetchDocumentThumbnailsByIds, useGetDocumentUrl } from '@/hooks/document-hooks';
|
||||
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
|
||||
import { preprocessLaTeX, replaceThinkToSection, showImage } from '@/utils/chat';
|
||||
import { getExtension } from '@/utils/document-util';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Popover, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { omit } from 'lodash';
|
||||
import { pipe } from 'lodash/fp';
|
||||
import 'katex/dist/katex.min.css';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Markdown from 'react-markdown';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkMath from 'remark-math';
|
||||
import { visitParents } from 'unist-util-visit-parents';
|
||||
import { currentReg, replaceTextByOldReg } from '../pages/next-chats/utils';
|
||||
import styles from './floating-chat-widget-markdown.less';
|
||||
import { useIsDarkTheme } from './theme-provider';
|
||||
|
||||
const getChunkIndex = (match: string) => Number(match.replace(/\[|\]/g, ''));
|
||||
|
||||
const FloatingChatWidgetMarkdown = ({
|
||||
reference,
|
||||
clickDocumentButton,
|
||||
content,
|
||||
}: {
|
||||
content: string;
|
||||
loading: boolean;
|
||||
reference: IReference;
|
||||
clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { setDocumentIds, data: fileThumbnails } = useFetchDocumentThumbnailsByIds();
|
||||
const getDocumentUrl = useGetDocumentUrl();
|
||||
const isDarkTheme = useIsDarkTheme();
|
||||
|
||||
const contentWithCursor = useMemo(() => {
|
||||
let text = content === '' ? t('chat.searching') : content;
|
||||
const nextText = replaceTextByOldReg(text);
|
||||
return pipe(replaceThinkToSection, preprocessLaTeX)(nextText);
|
||||
}, [content, t]);
|
||||
|
||||
useEffect(() => {
|
||||
const docAggs = reference?.doc_aggs;
|
||||
const docList = Array.isArray(docAggs) ? docAggs : Object.values(docAggs ?? {});
|
||||
setDocumentIds(docList.map((x: any) => x.doc_id).filter(Boolean));
|
||||
}, [reference, setDocumentIds]);
|
||||
|
||||
const handleDocumentButtonClick = useCallback((documentId: string, chunk: IReferenceChunk, isPdf: boolean, documentUrl?: string) => () => {
|
||||
if (!documentId) return;
|
||||
if (!isPdf && documentUrl) {
|
||||
window.open(documentUrl, '_blank');
|
||||
} else if (clickDocumentButton) {
|
||||
clickDocumentButton(documentId, chunk);
|
||||
}
|
||||
}, [clickDocumentButton]);
|
||||
|
||||
const rehypeWrapReference = () => (tree: any) => {
|
||||
visitParents(tree, 'text', (node, ancestors) => {
|
||||
const latestAncestor = ancestors[ancestors.length - 1];
|
||||
if (latestAncestor.tagName !== 'custom-typography' && latestAncestor.tagName !== 'code') {
|
||||
node.type = 'element';
|
||||
node.tagName = 'custom-typography';
|
||||
node.properties = {};
|
||||
node.children = [{ type: 'text', value: node.value }];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getReferenceInfo = useCallback((chunkIndex: number) => {
|
||||
const chunkItem = reference?.chunks?.[chunkIndex];
|
||||
if (!chunkItem) return null;
|
||||
const docAggsArray = Array.isArray(reference?.doc_aggs) ? 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 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 info = getReferenceInfo(chunkIndex);
|
||||
|
||||
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);
|
||||
|
||||
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 (
|
||||
<div className="floating-chat-widget">
|
||||
<Markdown
|
||||
rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]}
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
className="text-sm leading-relaxed space-y-2 prose-sm max-w-full"
|
||||
components={{
|
||||
'custom-typography': ({ children }: { children: string }) => renderReference(children),
|
||||
code(props: any) {
|
||||
const { children, className, node, ...rest } = props;
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
{...omit(rest, 'inline')}
|
||||
PreTag="div"
|
||||
language={match[1]}
|
||||
style={isDarkTheme ? oneDark : oneLight}
|
||||
wrapLongLines
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code {...rest} 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}
|
||||
</Markdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FloatingChatWidgetMarkdown;
|
||||
@ -2,8 +2,10 @@ import { MessageType, SharedFrom } from '@/constants/chat';
|
||||
import { useFetchNextConversationSSE } from '@/hooks/chat-hooks';
|
||||
import { useFetchFlowSSE } from '@/hooks/flow-hooks';
|
||||
import { useFetchExternalChatInfo } from '@/hooks/use-chat-request';
|
||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||
import i18n from '@/locales/config';
|
||||
import { MessageCircle, Minimize2, Send, X } from 'lucide-react';
|
||||
import PdfDrawer from '@/components/pdf-drawer';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -15,6 +17,7 @@ import {
|
||||
useGetSharedChatSearchParams,
|
||||
useSendSharedMessage,
|
||||
} from '../pages/next-chats/hooks/use-send-shared-message';
|
||||
import FloatingChatWidgetMarkdown from './floating-chat-widget-markdown';
|
||||
|
||||
const FloatingChatWidget = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -63,6 +66,14 @@ const FloatingChatWidget = () => {
|
||||
|
||||
const { data: avatarData } = useFetchAvatar();
|
||||
|
||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||
useClickDrawer();
|
||||
|
||||
// PDF drawer state tracking
|
||||
useEffect(() => {
|
||||
// Drawer state management
|
||||
}, [visible, documentId, selectedChunk]);
|
||||
|
||||
// Play sound when opening
|
||||
const playNotificationSound = useCallback(() => {
|
||||
try {
|
||||
@ -223,7 +234,7 @@ const FloatingChatWidget = () => {
|
||||
const syntheticEvent = {
|
||||
target: { value: inputValue },
|
||||
currentTarget: { value: inputValue },
|
||||
preventDefault: () => {},
|
||||
preventDefault: () => { },
|
||||
} as any;
|
||||
|
||||
handleInputChange(syntheticEvent);
|
||||
@ -314,9 +325,8 @@ const FloatingChatWidget = () => {
|
||||
'*',
|
||||
);
|
||||
}}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${
|
||||
isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`transition-transform duration-300 ${isOpen ? 'rotate-45' : 'rotate-0'}`}
|
||||
@ -343,9 +353,8 @@ const FloatingChatWidget = () => {
|
||||
>
|
||||
<button
|
||||
onClick={toggleChat}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${
|
||||
isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`transition-transform duration-300 ${isOpen ? 'rotate-45' : 'rotate-0'}`}
|
||||
@ -367,127 +376,143 @@ const FloatingChatWidget = () => {
|
||||
if (mode === 'window') {
|
||||
// Only render the chat window (always open)
|
||||
return (
|
||||
<div
|
||||
className={`fixed top-0 left-0 z-50 bg-blue-600 rounded-2xl transition-all duration-300 ease-out h-[500px] w-[380px] overflow-hidden ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-t-2xl">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
|
||||
<MessageCircle size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm">
|
||||
{chatInfo?.title || 'Chat Support'}
|
||||
</h3>
|
||||
<p className="text-xs text-blue-100">
|
||||
We typically reply instantly
|
||||
</p>
|
||||
<>
|
||||
<div
|
||||
className={`fixed top-0 left-0 z-50 bg-blue-600 rounded-2xl transition-all duration-300 ease-out h-[500px] w-[380px] overflow-hidden ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-t-2xl">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
|
||||
<MessageCircle size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-sm">
|
||||
{chatInfo?.title || 'Chat Support'}
|
||||
</h3>
|
||||
<p className="text-xs text-blue-100">
|
||||
We typically reply instantly
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Messages and Input */}
|
||||
<div
|
||||
className="flex flex-col h-[436px] bg-white"
|
||||
style={{ borderRadius: '0 0 16px 16px' }}
|
||||
>
|
||||
{/* Messages and Input */}
|
||||
<div
|
||||
className="flex-1 overflow-y-auto p-4 space-y-4"
|
||||
onWheel={(e) => {
|
||||
const element = e.currentTarget;
|
||||
const isAtTop = element.scrollTop === 0;
|
||||
const isAtBottom =
|
||||
element.scrollTop + element.clientHeight >=
|
||||
element.scrollHeight - 1;
|
||||
|
||||
// Allow scroll to pass through to parent when at boundaries
|
||||
if ((isAtTop && e.deltaY < 0) || (isAtBottom && e.deltaY > 0)) {
|
||||
e.preventDefault();
|
||||
// Let the parent handle the scroll
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'SCROLL_PASSTHROUGH',
|
||||
deltaY: e.deltaY,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="flex flex-col h-[436px] bg-white"
|
||||
style={{ borderRadius: '0 0 16px 16px' }}
|
||||
>
|
||||
{displayMessages?.map((message, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex ${message.role === MessageType.User ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className="flex-1 overflow-y-auto p-4 space-y-4"
|
||||
onWheel={(e) => {
|
||||
const element = e.currentTarget;
|
||||
const isAtTop = element.scrollTop === 0;
|
||||
const isAtBottom =
|
||||
element.scrollTop + element.clientHeight >=
|
||||
element.scrollHeight - 1;
|
||||
|
||||
// Allow scroll to pass through to parent when at boundaries
|
||||
if ((isAtTop && e.deltaY < 0) || (isAtBottom && e.deltaY > 0)) {
|
||||
e.preventDefault();
|
||||
// Let the parent handle the scroll
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'SCROLL_PASSTHROUGH',
|
||||
deltaY: e.deltaY,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{displayMessages?.map((message, index) => (
|
||||
<div
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${
|
||||
message.role === MessageType.User
|
||||
key={index}
|
||||
className={`flex ${message.role === MessageType.User ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${message.role === MessageType.User
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{message.role === MessageType.User ? (
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
{message.content}
|
||||
</p>
|
||||
) : (
|
||||
<FloatingChatWidgetMarkdown
|
||||
loading={false}
|
||||
content={message.content}
|
||||
reference={message.reference || { doc_aggs: [], chunks: [], total: 0 }}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Clean Typing Indicator */}
|
||||
{sendLoading && !enableStreaming && (
|
||||
<div className="flex justify-start pl-4">
|
||||
<div className="flex space-x-1">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
|
||||
<div
|
||||
className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
|
||||
style={{ animationDelay: '0.1s' }}
|
||||
></div>
|
||||
<div
|
||||
className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
|
||||
style={{ animationDelay: '0.2s' }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="border-t border-gray-200 p-4">
|
||||
<div className="flex items-end space-x-3">
|
||||
<div className="flex-1">
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setInputValue(newValue);
|
||||
handleInputChange(e);
|
||||
}}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Type your message..."
|
||||
rows={1}
|
||||
className="w-full resize-none border border-gray-300 rounded-2xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
style={{ minHeight: '44px', maxHeight: '120px' }}
|
||||
disabled={hasError || sendLoading}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
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"
|
||||
>
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
{message.content}
|
||||
</p>
|
||||
</div>
|
||||
<Send size={18} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Clean Typing Indicator */}
|
||||
{sendLoading && !enableStreaming && (
|
||||
<div className="flex justify-start pl-4">
|
||||
<div className="flex space-x-1">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
|
||||
<div
|
||||
className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
|
||||
style={{ animationDelay: '0.1s' }}
|
||||
></div>
|
||||
<div
|
||||
className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
|
||||
style={{ animationDelay: '0.2s' }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="border-t border-gray-200 p-4">
|
||||
<div className="flex items-end space-x-3">
|
||||
<div className="flex-1">
|
||||
<textarea
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setInputValue(newValue);
|
||||
handleInputChange(e);
|
||||
}}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Type your message..."
|
||||
rows={1}
|
||||
className="w-full resize-none border border-gray-300 rounded-2xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
style={{ minHeight: '44px', maxHeight: '120px' }}
|
||||
disabled={hasError || sendLoading}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
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"
|
||||
>
|
||||
<Send size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PdfDrawer
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
width={'100vw'}
|
||||
height={'100vh'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Full mode - render everything together (original behavior)
|
||||
} // Full mode - render everything together (original behavior)
|
||||
return (
|
||||
<div
|
||||
className={`transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
|
||||
@ -495,9 +520,8 @@ const FloatingChatWidget = () => {
|
||||
{/* Chat Widget Container */}
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`fixed bottom-24 right-6 z-50 bg-blue-600 rounded-2xl transition-all duration-300 ease-out ${
|
||||
isMinimized ? 'h-16' : 'h-[500px]'
|
||||
} w-[380px] overflow-hidden`}
|
||||
className={`fixed bottom-24 right-6 z-50 bg-blue-600 rounded-2xl transition-all duration-300 ease-out ${isMinimized ? 'h-16' : 'h-[500px]'
|
||||
} w-[380px] overflow-hidden`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-t-2xl">
|
||||
@ -568,15 +592,23 @@ const FloatingChatWidget = () => {
|
||||
className={`flex ${message.role === MessageType.User ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${
|
||||
message.role === MessageType.User
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||
}`}
|
||||
className={`max-w-[280px] px-4 py-2 rounded-2xl ${message.role === MessageType.User
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||
}`}
|
||||
>
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
{message.content}
|
||||
</p>
|
||||
{message.role === MessageType.User ? (
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
{message.content}
|
||||
</p>
|
||||
) : (
|
||||
<FloatingChatWidgetMarkdown
|
||||
loading={false}
|
||||
content={message.content}
|
||||
reference={message.reference || { doc_aggs: [], chunks: [], total: 0 }}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -641,9 +673,8 @@ const FloatingChatWidget = () => {
|
||||
<div className="fixed bottom-6 right-6 z-50">
|
||||
<button
|
||||
onClick={toggleChat}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${
|
||||
isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
className={`w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full transition-all duration-300 flex items-center justify-center group ${isOpen ? 'scale-95' : 'scale-100 hover:scale-105'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`transition-transform duration-300 ${isOpen ? 'rotate-45' : 'rotate-0'}`}
|
||||
@ -659,6 +690,12 @@ const FloatingChatWidget = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<PdfDrawer
|
||||
visible={visible}
|
||||
hideModal={hideModal}
|
||||
documentId={documentId}
|
||||
chunk={selectedChunk}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,6 +7,8 @@ import DocumentPreviewer from '../pdf-previewer';
|
||||
interface IProps extends IModalProps<any> {
|
||||
documentId: string;
|
||||
chunk: IChunk | IReferenceChunk;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
}
|
||||
|
||||
export const PdfDrawer = ({
|
||||
@ -14,13 +16,16 @@ export const PdfDrawer = ({
|
||||
hideModal,
|
||||
documentId,
|
||||
chunk,
|
||||
width = '50vw',
|
||||
height,
|
||||
}: IProps) => {
|
||||
return (
|
||||
<Drawer
|
||||
title="Document Previewer"
|
||||
onClose={hideModal}
|
||||
open={visible}
|
||||
width={'50vw'}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<DocumentPreviewer
|
||||
documentId={documentId}
|
||||
@ -31,4 +36,4 @@ export const PdfDrawer = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default PdfDrawer;
|
||||
export default PdfDrawer;
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import { ITestRetrievalRequestBody } from '@/interfaces/request/knowledge';
|
||||
import i18n from '@/locales/config';
|
||||
import kbService, {
|
||||
deleteKnowledgeGraph,
|
||||
getKnowledgeGraph,
|
||||
listDataset,
|
||||
} from '@/services/knowledge-service';
|
||||
@ -30,6 +31,7 @@ export const enum KnowledgeApiAction {
|
||||
FetchKnowledgeDetail = 'fetchKnowledgeDetail',
|
||||
FetchKnowledgeGraph = 'fetchKnowledgeGraph',
|
||||
FetchMetadata = 'fetchMetadata',
|
||||
RemoveKnowledgeGraph = 'removeKnowledgeGraph',
|
||||
}
|
||||
|
||||
export const useKnowledgeBaseId = (): string => {
|
||||
@ -296,3 +298,28 @@ export function useFetchKnowledgeMetadata(kbIds: string[] = []) {
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export const useRemoveKnowledgeGraph = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [KnowledgeApiAction.RemoveKnowledgeGraph],
|
||||
mutationFn: async () => {
|
||||
const { data } = await deleteKnowledgeGraph(knowledgeBaseId);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.deleted`));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['fetchKnowledgeGraph'],
|
||||
});
|
||||
}
|
||||
return data?.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, removeKnowledgeGraph: mutateAsync };
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export default {
|
||||
translation: {
|
||||
common: {
|
||||
selectPlaceholder: '選択してください',
|
||||
delete: '削除',
|
||||
deleteModalTitle: 'この項目を削除してよろしいですか?',
|
||||
ok: 'はい',
|
||||
@ -174,6 +175,7 @@ export default {
|
||||
'ナレッジベースの設定、特にチャンク方法をここで更新してください。',
|
||||
name: 'ナレッジベース名',
|
||||
photo: 'ナレッジベース写真',
|
||||
photoTip: '4MBのファイルをアップロードできます',
|
||||
description: '説明',
|
||||
language: '言語',
|
||||
languageMessage: '言語を入力してください',
|
||||
@ -333,6 +335,11 @@ export default {
|
||||
},
|
||||
chat: {
|
||||
messagePlaceholder: 'メッセージを入力してください...',
|
||||
exit: '終了',
|
||||
multipleModels: '複数モデル',
|
||||
applyModelConfigs: 'モデル設定を適用',
|
||||
conversations: '会話一覧',
|
||||
chatApps: 'チャットアプリ',
|
||||
newConversation: '新しい会話',
|
||||
createAssistant: 'アシスタントを作成',
|
||||
assistantSetting: 'アシスタント設定',
|
||||
@ -352,6 +359,7 @@ export default {
|
||||
language: '言語',
|
||||
emptyResponse: '空の応答',
|
||||
emptyResponseTip: `ナレッジベースに該当する内容がない場合、この応答が使用されます。`,
|
||||
emptyResponseMessage: `ナレッジベースから関連する情報が取得できなかった場合に発動します。ナレッジベースが選択されていない場合、このフィールドをクリアしてください。`,
|
||||
setAnOpener: 'オープニングメッセージを設定',
|
||||
setAnOpenerInitial: `こんにちは! 私はあなたのアシスタントです。何をお手伝いしましょうか?`,
|
||||
setAnOpenerTip: 'お客様をどのように歓迎しますか?',
|
||||
@ -378,10 +386,14 @@ export default {
|
||||
model: 'モデル',
|
||||
modelTip: '大規模言語チャットモデル',
|
||||
modelMessage: '選択してください!',
|
||||
modelEnabledTools: '有効化されたツール',
|
||||
modelEnabledToolsTip:
|
||||
'モデルで使用するツールを1つ以上選択してください。ツール呼び出しをサポートしていないモデルでは効果がありません。',
|
||||
freedom: '自由度',
|
||||
improvise: '自由に',
|
||||
precise: '正確に',
|
||||
balance: 'バランス',
|
||||
custom: 'カスタム',
|
||||
freedomTip: `'正確に'は、LLMが慎重に質問に答えることを意味します。'自由に'は、LLMが多く話し、自由に答えることを望むことを意味します。'バランス'は、慎重さと自由さの間のバランスを取ることを意味します。`,
|
||||
temperature: '温度',
|
||||
temperatureMessage: '温度は必須です',
|
||||
@ -435,6 +447,7 @@ export default {
|
||||
partialTitle: '部分埋め込み',
|
||||
extensionTitle: 'Chrome拡張機能',
|
||||
tokenError: 'まずAPIトークンを作成してください!',
|
||||
betaError: 'システム設定ページからRAGFlow APIキーを取得してください。',
|
||||
searching: '検索中...',
|
||||
parsing: '解析中',
|
||||
uploading: 'アップロード中',
|
||||
@ -451,6 +464,38 @@ export default {
|
||||
'マルチラウンドの会話では、ナレッジベースへのクエリが最適化されます。大規模モデルが呼び出され、追加のトークンが消費されます。',
|
||||
howUseId: 'チャットIDの使い方?',
|
||||
description: 'アシスタントの説明',
|
||||
descriptionPlaceholder: '例: 履歴書用のチャットアシスタント',
|
||||
useKnowledgeGraph: 'ナレッジグラフを使用',
|
||||
useKnowledgeGraphTip:
|
||||
'ナレッジグラフを利用してエンティティや関係をまたいだ検索を行います。マルチホップ質問応答が可能になりますが、検索時間が大幅に増加します。',
|
||||
keyword: 'キーワード解析',
|
||||
keywordTip: `LLMでユーザーの質問を解析し、重要キーワードを抽出して関連性計算で強調します。長文クエリに有効ですが応答速度が遅くなります。`,
|
||||
languageTip:
|
||||
'選択した言語で文をリライトできます。未選択の場合は直近の質問の言語が使われます。',
|
||||
avatarHidden: 'アバターを非表示',
|
||||
locale: 'ロケール',
|
||||
selectLanguage: '言語を選択',
|
||||
reasoning: '推論',
|
||||
reasoningTip: `Deepseek-R1 や OpenAI o1 のように、推論ワークフローを有効にできます。ステップごとの論理展開で複雑な質問への正確さが向上します。`,
|
||||
tavilyApiKeyTip:
|
||||
'ここにTavilyのAPIキーを設定すると、ナレッジベース検索に加えてウェブ検索も利用できます。',
|
||||
tavilyApiKeyMessage: 'Tavily APIキーを入力してください',
|
||||
tavilyApiKeyHelp: '取得方法はこちら',
|
||||
crossLanguage: 'クロス言語検索',
|
||||
crossLanguageTip: `1つ以上の言語を選択すると、その言語でも検索します。未選択の場合は元の言語で検索します。`,
|
||||
createChat: 'チャットを作成',
|
||||
metadata: 'メタデータ',
|
||||
metadataTip:
|
||||
'メタデータ(タグ、カテゴリ、アクセス権限など)を使って、関連情報の検索を制御・絞り込みます。',
|
||||
conditions: '条件',
|
||||
addCondition: '条件を追加',
|
||||
meta: {
|
||||
disabled: '無効',
|
||||
automatic: '自動',
|
||||
manual: '手動',
|
||||
},
|
||||
cancel: 'キャンセル',
|
||||
chatSetting: 'チャット設定',
|
||||
},
|
||||
setting: {
|
||||
profile: 'プロファイル',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useFetchKnowledgeGraph } from '@/hooks/knowledge-hooks';
|
||||
import { useFetchKnowledgeGraph } from '@/hooks/use-knowledge-request';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useRemoveKnowledgeGraph } from '@/hooks/knowledge-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useRemoveKnowledgeGraph } from '@/hooks/use-knowledge-request';
|
||||
import { useCallback } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user