Feat: Modify the data structure of the chunk in the conversation #3909 (#3955)

### What problem does this PR solve?

Feat: Modify the data structure of the chunk in the conversation #3909

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2024-12-10 16:36:16 +08:00 committed by GitHub
parent 03f00c9e6f
commit fc4e644e5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 304 additions and 202 deletions

View File

@ -63,7 +63,7 @@ const ChatApiKeyModal = ({
<Button
onClick={createToken}
loading={creatingLoading}
disabled={tokenList.length > 0}
disabled={tokenList?.length > 0}
>
{t('createNewKey')}
</Button>

View File

@ -3,8 +3,7 @@ import apiDoc from '@parent/docs/references/http_api_reference.md';
import MarkdownPreview from '@uiw/react-markdown-preview';
import { Button, Card, Flex, Space } from 'antd';
import ChatApiKeyModal from '../chat-api-key-modal';
import EmbedModal from '../embed-modal';
import { usePreviewChat, useShowEmbedModal } from '../hooks';
import { usePreviewChat } from '../hooks';
import BackendServiceApi from './backend-service-api';
const ApiContent = ({
@ -22,10 +21,10 @@ const ApiContent = ({
hideModal: hideApiKeyModal,
showModal: showApiKeyModal,
} = useSetModalState();
const { embedVisible, hideEmbedModal, showEmbedModal, embedToken } =
useShowEmbedModal(idKey, id);
// const { embedVisible, hideEmbedModal, showEmbedModal, embedToken } =
// useShowEmbedModal(idKey);
const { handlePreview } = usePreviewChat(idKey, id);
const { handlePreview } = usePreviewChat(idKey);
return (
<div>
@ -36,7 +35,9 @@ const ApiContent = ({
<Flex gap={8} vertical>
<Space size={'middle'}>
<Button onClick={handlePreview}>{t('preview')}</Button>
<Button onClick={showEmbedModal}>{t('embedded')}</Button>
{/* <Button onClick={() => showEmbedModal(id)}>
{t('embedded')}
</Button> */}
</Space>
</Flex>
</Card>
@ -50,13 +51,13 @@ const ApiContent = ({
idKey={idKey}
></ChatApiKeyModal>
)}
{embedVisible && (
{/* {embedVisible && (
<EmbedModal
token={embedToken}
visible={embedVisible}
hideModal={hideEmbedModal}
></EmbedModal>
)}
)} */}
</div>
);
};

View File

@ -6,3 +6,7 @@
padding: 10px;
background-color: #ffffff09;
}
.id {
.linkText();
}

View File

@ -1,21 +1,33 @@
import CopyToClipboard from '@/components/copy-to-clipboard';
import HightLightMarkdown from '@/components/highlight-markdown';
import { SharedFrom } from '@/constants/chat';
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { Card, Modal, Tabs, TabsProps } from 'antd';
import { Card, Modal, Tabs, TabsProps, Typography } from 'antd';
import styles from './index.less';
const { Paragraph, Link } = Typography;
const EmbedModal = ({
visible,
hideModal,
token = '',
}: IModalProps<any> & { token: string }) => {
form,
beta = '',
isAgent,
}: IModalProps<any> & {
token: string;
form: SharedFrom;
beta: string;
isAgent: boolean;
}) => {
const { t } = useTranslate('chat');
const text = `
~~~ html
<iframe
src="${location.origin}/chat/share?shared_id=${token}"
src="${location.origin}/chat/share?shared_id=${token}&from=${form}&auth=${beta}"
style="width: 100%; height: 100%; min-height: 600px"
frameborder="0"
>
@ -63,6 +75,23 @@ const EmbedModal = ({
onCancel={hideModal}
>
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
<div className="text-base font-medium mt-4 mb-1">
{t(isAgent ? 'flow' : 'chat', { keyPrefix: 'header' })}
<span className="ml-1 inline-block">ID</span>
</div>
<Paragraph copyable={{ text: token }} className={styles.id}>
{token}
</Paragraph>
<Link
href={
isAgent
? 'https://ragflow.io/docs/dev/http_api_reference#create-session-with-an-agent'
: 'https://ragflow.io/docs/dev/http_api_reference#create-session-with-chat-assistant'
}
target="_blank"
>
{t('howUseId')}
</Link>
</Modal>
);
};

View File

@ -6,6 +6,7 @@ import {
} from '@/hooks/common-hooks';
import {
useCreateSystemToken,
useFetchManualSystemTokenList,
useFetchSystemTokenList,
useRemoveSystemToken,
} from '@/hooks/user-setting-hooks';
@ -17,9 +18,7 @@ import { useCallback } from 'react';
export const useOperateApiKey = (idKey: string, dialogId?: string) => {
const { removeToken } = useRemoveSystemToken();
const { createToken, loading: creatingLoading } = useCreateSystemToken();
const { data: tokenList, loading: listLoading } = useFetchSystemTokenList({
[idKey]: dialogId,
});
const { data: tokenList, loading: listLoading } = useFetchSystemTokenList();
const showDeleteConfirm = useShowDeleteConfirm();
@ -72,49 +71,68 @@ export const useShowTokenEmptyError = () => {
return { showTokenEmptyError };
};
export const useShowBetaEmptyError = () => {
const { t } = useTranslate('chat');
const showBetaEmptyError = useCallback(() => {
message.error(t('betaError'));
}, [t]);
return { showBetaEmptyError };
};
const getUrlWithToken = (token: string, from: string = 'chat') => {
const { protocol, host } = window.location;
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
};
const useFetchTokenListBeforeOtherStep = (idKey: string, dialogId?: string) => {
const useFetchTokenListBeforeOtherStep = () => {
const { showTokenEmptyError } = useShowTokenEmptyError();
const { showBetaEmptyError } = useShowBetaEmptyError();
const { data: tokenList, refetch } = useFetchSystemTokenList({
[idKey]: dialogId,
});
const { data: tokenList, fetchSystemTokenList } =
useFetchManualSystemTokenList();
const token =
let token = '',
beta = '';
if (Array.isArray(tokenList) && tokenList.length > 0) {
token = tokenList[0].token;
beta = tokenList[0].beta;
}
token =
Array.isArray(tokenList) && tokenList.length > 0 ? tokenList[0].token : '';
const handleOperate = useCallback(async () => {
const ret = await refetch();
const list = ret.data;
const ret = await fetchSystemTokenList();
const list = ret;
if (Array.isArray(list) && list.length > 0) {
if (!list[0].beta) {
showBetaEmptyError();
return false;
}
return list[0]?.token;
} else {
showTokenEmptyError();
return false;
}
}, [showTokenEmptyError, refetch]);
}, [fetchSystemTokenList, showBetaEmptyError, showTokenEmptyError]);
return {
token,
beta,
handleOperate,
};
};
export const useShowEmbedModal = (idKey: string, dialogId?: string) => {
export const useShowEmbedModal = () => {
const {
visible: embedVisible,
hideModal: hideEmbedModal,
showModal: showEmbedModal,
} = useSetModalState();
const { handleOperate, token } = useFetchTokenListBeforeOtherStep(
idKey,
dialogId,
);
const { handleOperate, token, beta } = useFetchTokenListBeforeOtherStep();
const handleShowEmbedModal = useCallback(async () => {
const succeed = await handleOperate();
@ -128,11 +146,12 @@ export const useShowEmbedModal = (idKey: string, dialogId?: string) => {
hideEmbedModal,
embedVisible,
embedToken: token,
beta,
};
};
export const usePreviewChat = (idKey: string, dialogId?: string) => {
const { handleOperate } = useFetchTokenListBeforeOtherStep(idKey, dialogId);
export const usePreviewChat = (idKey: string) => {
const { handleOperate } = useFetchTokenListBeforeOtherStep();
const open = useCallback(
(t: string) => {

View File

@ -1,8 +1,7 @@
import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg';
import { MessageType } from '@/constants/chat';
import { useSetModalState } from '@/hooks/common-hooks';
import { IReference } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
import classNames from 'classnames';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
@ -31,7 +30,7 @@ interface IProps extends Partial<IRemoveMessageById>, IRegenerateMessage {
sendLoading?: boolean;
nickname?: string;
avatar?: string;
clickDocumentButton?: (documentId: string, chunk: IChunk) => void;
clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void;
index: number;
showLikeButton?: boolean;
}

View File

@ -1,14 +1,16 @@
import { useSetModalState } from '@/hooks/common-hooks';
import { IChunk } from '@/interfaces/database/knowledge';
import { IReferenceChunk } from '@/interfaces/database/chat';
import { useCallback, useState } from 'react';
export const useClickDrawer = () => {
const { visible, showModal, hideModal } = useSetModalState();
const [selectedChunk, setSelectedChunk] = useState<IChunk>({} as IChunk);
const [selectedChunk, setSelectedChunk] = useState<IReferenceChunk>(
{} as IReferenceChunk,
);
const [documentId, setDocumentId] = useState<string>('');
const clickDocumentButton = useCallback(
(documentId: string, chunk: IChunk) => {
(documentId: string, chunk: IReferenceChunk) => {
showModal();
setSelectedChunk(chunk);
setDocumentId(documentId);

View File

@ -1,11 +1,12 @@
import { IModalProps } from '@/interfaces/common';
import { IReferenceChunk } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import { Drawer } from 'antd';
import DocumentPreviewer from '../pdf-previewer';
interface IProps extends IModalProps<any> {
documentId: string;
chunk: IChunk;
chunk: IChunk | IReferenceChunk;
}
export const PdfDrawer = ({

View File

@ -2,7 +2,9 @@ import {
useGetChunkHighlights,
useGetDocumentUrl,
} from '@/hooks/document-hooks';
import { IReferenceChunk } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import FileError from '@/pages/document-viewer/file-error';
import { Skeleton } from 'antd';
import { useEffect, useRef, useState } from 'react';
import {
@ -13,13 +15,12 @@ import {
PdfLoader,
Popup,
} from 'react-pdf-highlighter';
import FileError from '@/pages/document-viewer/file-error';
import { useCatchDocumentError } from './hooks';
import styles from './index.less';
interface IProps {
chunk: IChunk;
chunk: IChunk | IReferenceChunk;
documentId: string;
visible: boolean;
}

View File

@ -504,11 +504,17 @@ export const useCreateNextSharedConversation = () => {
return { data, loading, createSharedConversation: mutateAsync };
};
export const useFetchNextSharedConversation = (conversationId: string) => {
// deprecated
export const useFetchNextSharedConversation = (
conversationId?: string | null,
) => {
const { data, isPending: loading } = useQuery({
queryKey: ['fetchSharedConversation'],
enabled: !!conversationId,
queryFn: async () => {
if (!conversationId) {
return {};
}
const { data } = await chatService.getExternalConversation(
null,
conversationId,

View File

@ -1,3 +1,4 @@
import { IReferenceChunk } from '@/interfaces/database/chat';
import { IDocumentInfo } from '@/interfaces/database/document';
import { IChunk } from '@/interfaces/database/knowledge';
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
@ -32,7 +33,9 @@ export const useGetDocumentUrl = (documentId?: string) => {
return getDocumentUrl;
};
export const useGetChunkHighlights = (selectedChunk: IChunk) => {
export const useGetChunkHighlights = (
selectedChunk: IChunk | IReferenceChunk,
) => {
const [size, setSize] = useState({ width: 849, height: 1200 });
const highlights: IHighlight[] = useMemo(() => {

View File

@ -12,6 +12,7 @@ import { PaginationProps, message } from 'antd';
import { FormInstance } from 'antd/lib';
import axios from 'axios';
import { EventSourceParserStream } from 'eventsource-parser/stream';
import { omit } from 'lodash';
import {
ChangeEventHandler,
useCallback,
@ -336,6 +337,7 @@ export const useSelectDerivedMessages = () => {
}),
prompt: answer.prompt,
audio_binary: answer.audio_binary,
...omit(answer, 'reference'),
},
];
});

View File

@ -169,17 +169,34 @@ export const useFetchSystemStatus = () => {
};
};
export const useFetchSystemTokenList = (params: Record<string, any>) => {
export const useFetchManualSystemTokenList = () => {
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['fetchManualSystemTokenList'],
mutationFn: async () => {
const { data } = await userService.listToken();
return data?.data ?? [];
},
});
return { data, loading, fetchSystemTokenList: mutateAsync };
};
export const useFetchSystemTokenList = () => {
const {
data,
isFetching: loading,
refetch,
} = useQuery<IToken[]>({
queryKey: ['fetchSystemTokenList', params],
queryKey: ['fetchSystemTokenList'],
initialData: [],
gcTime: 0,
queryFn: async () => {
const { data } = await userService.listToken(params);
const { data } = await userService.listToken();
return data?.data ?? [];
},
@ -213,6 +230,7 @@ export const useRemoveSystemToken = () => {
export const useCreateSystemToken = () => {
const queryClient = useQueryClient();
const {
data,
isPending: loading,

View File

@ -1,5 +1,4 @@
import { MessageType } from '@/constants/chat';
import { IChunk } from './knowledge';
export interface PromptConfig {
empty_response: string;
@ -35,7 +34,7 @@ export interface IDialog {
description: string;
icon: string;
id: string;
dialog_id?: string;
dialog_id: string;
kb_ids: string[];
kb_names: string[];
language: string;
@ -75,8 +74,21 @@ export interface Message {
audio_binary?: string;
}
export interface IReferenceChunk {
id: string;
content: null;
document_id: string;
document_name: string;
dataset_id: string;
image_id: string;
similarity: number;
vector_similarity: number;
term_similarity: number;
positions: number[];
}
export interface IReference {
chunks: IChunk[];
chunks: IReferenceChunk[];
doc_aggs: Docagg[];
total: number;
}
@ -117,6 +129,7 @@ export interface IToken {
token: string;
update_date?: any;
update_time?: any;
beta: string;
}
export interface IStats {

View File

@ -2,11 +2,11 @@ import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import { createTranslationTable, flattenObject } from './until';
import translation_en from './en';
import translation_es from './es';
import translation_id from './id';
import translation_ja from './ja';
import { createTranslationTable, flattenObject } from './until';
import translation_vi from './vi';
import translation_zh from './zh';
import translation_zh_traditional from './zh-traditional';

View File

@ -432,6 +432,7 @@ The above is the content you need to summarize.`,
partialTitle: 'Partial Embed',
extensionTitle: 'Chrome Extension',
tokenError: 'Please create API Token first!',
betaError: 'The beta field of the API Token cannot be empty!',
searching: 'searching...',
parsing: 'Parsing',
uploading: 'Uploading',

View File

@ -33,6 +33,7 @@ export default {
pleaseSelect: '選択してください',
pleaseInput: '入力してください',
submit: '送信',
japanese: '日本語',
},
login: {
login: 'ログイン',
@ -85,8 +86,7 @@ export default {
name: '名前',
namePlaceholder: '名前を入力してください',
doc: 'ドキュメント',
datasetDescription:
'😉 パースが成功すると、質問と回答が可能になります。',
datasetDescription: '😉 パースが成功すると、質問と回答が可能になります。',
addFile: 'ファイルを追加',
searchFiles: 'ファイルを検索',
localFiles: 'ローカルファイル',
@ -157,8 +157,7 @@ export default {
topK: 'トップK',
topKTip: `Kチャンクがリランキングモデルに供給されます。`,
delimiter: `区切り文字`,
delimiterTip:
'複数文字の区切り文字をサポートしています。',
delimiterTip: '複数文字の区切り文字をサポートしています。',
html4excel: 'ExcelをHTMLに変換',
html4excelTip: `有効にすると、スプレッドシートはHTMLテーブルとして解析されます。それ以外の場合、キーと値のペアとして解析されます。`,
autoKeywords: '自動キーワード',
@ -182,7 +181,7 @@ export default {
embeddingModelTip:
'チャンクを埋め込みに変換するモデルです。一度チャンクが作成されると変更できません。',
permissionsTip:
"「チーム」に設定すると、全てのチームメンバーがナレッジベースを管理できます。",
'「チーム」に設定すると、全てのチームメンバーがナレッジベースを管理できます。',
chunkTokenNumberTip:
'チャンクのトークンしきい値を設定します。このしきい値を下回る段落は、次の段落と結合され、しきい値を超えた時点でチャンクが作成されます。',
chunkMethod: 'チャンク方法',
@ -201,8 +200,7 @@ export default {
methodExamplesDescription:
'以下のスクリーンショットは明確な説明のために提供されています。',
dialogueExamplesTitle: '会話の例',
methodEmpty:
'ナレッジベースカテゴリの視覚的説明がここに表示されます',
methodEmpty: 'ナレッジベースカテゴリの視覚的説明がここに表示されます',
book: `<p>対応ファイル形式は<b>DOCX</b>, <b>PDF</b>, <b>TXT</b>です。</p><p>
PDF形式の書籍では<i></i></p>`,
laws: `<p>対応ファイル形式は<b>DOCX</b>, <b>PDF</b>, <b>TXT</b>です。</p><p>
@ -305,7 +303,7 @@ export default {
entityTypes: 'エンティティタイプ',
pageRank: 'ページランク',
pageRankTip: `これは関連性スコアを高めるために使用されます。すべての取得されたチャンクの関連性スコアにこの数値が加算されます。
`
`,
},
chunk: {
chunk: 'チャンク',
@ -360,8 +358,7 @@ export default {
{knowledge}
`,
systemMessage: '入力してください!',
systemTip:
'LLMが質問に答える際に従う指示を設定します。',
systemTip: 'LLMが質問に答える際に従う指示を設定します。',
topN: 'トップN',
topNTip: `類似度スコアがしきい値を超えるチャンクのうち、上位N件のみがLLMに供給されます。`,
variable: '変数',
@ -406,7 +403,8 @@ export default {
quote: '引用を表示',
quoteTip: '元のテキストの出典を表示しますか?',
selfRag: 'Self-RAG',
selfRagTip: '詳細は次を参照してくださいhttps://huggingface.co/papers/2310.11511',
selfRagTip:
'詳細は次を参照してくださいhttps://huggingface.co/papers/2310.11511',
overview: 'チャットID',
pv: 'メッセージ数',
uv: 'アクティブユーザー数',
@ -579,8 +577,7 @@ export default {
yiyanAKMessage: 'APIキーを入力してください',
addyiyanSK: 'yiyanシークレットキー',
yiyanSKMessage: 'シークレットキーを入力してください',
FishAudioModelNameMessage:
'音声合成モデルに名前を付けてください',
FishAudioModelNameMessage: '音声合成モデルに名前を付けてください',
addFishAudioAK: 'Fish Audio APIキー',
addFishAudioAKMessage: 'APIキーを入力してください',
addFishAudioRefID: 'FishAudio参照ID',
@ -700,7 +697,7 @@ export default {
relevantDescription: `LLMを使用して、上流の出力がユーザーの最新のクエリに関連しているかどうかを評価するコンポーネント。各判定結果に対して次のコンポーネントを指定してください。`,
rewriteQuestionDescription: `ナレッジベースから関連情報を取得できなかった場合にユーザーのクエリを修正するコンポーネント。定義されたループの上限に達するまでこのプロセスを繰り返します。上流が「Relevant」、下流が「Retrieval」であることを確認してください。`,
messageDescription:
"静的メッセージを送信するコンポーネント。複数のメッセージが提供されている場合は、その中からランダムに1つを選択して送信します。下流がインターフェースコンポーネント「Answer」であることを確認してください。",
'静的メッセージを送信するコンポーネント。複数のメッセージが提供されている場合は、その中からランダムに1つを選択して送信します。下流がインターフェースコンポーネント「Answer」であることを確認してください。',
keywordDescription: `ユーザーの入力からトップNの検索結果を取得するコンポーネント。使用前にTopNの値が適切に設定されていることを確認してください。`,
switchDescription: `前のコンポーネントの出力に基づいて条件を評価し、それに応じて実行の流れを指示するコンポーネント。ケースを定義し、各ケースのアクションまたは条件が満たされない場合のデフォルトアクションを指定することで、複雑な分岐ロジックを可能にします。`,
wikipediaDescription: `wikipedia.orgから検索を行うコンポーネントで、TopNを使用して検索結果の数を指定します。既存のナレッジベースを補完します。`,

View File

@ -414,6 +414,7 @@ export default {
partialTitle: '部分嵌入',
extensionTitle: 'Chrome 插件',
tokenError: '請先創建 API Token!',
betaError: 'API Token的beta欄位不可以為空',
searching: '搜索中',
parsing: '解析中',
uploading: '上傳中',

View File

@ -431,6 +431,7 @@ export default {
partialTitle: '部分嵌入',
extensionTitle: 'Chrome 插件',
tokenError: '请先创建 API Token!',
betaError: 'API Token的beta字段不可以为空',
searching: '搜索中',
parsing: '解析中',
uploading: '上传中',

View File

@ -29,18 +29,20 @@ import {
useSelectDerivedConversationList,
} from './hooks';
import EmbedModal from '@/components/api-service/embed-modal';
import { useShowEmbedModal } from '@/components/api-service/hooks';
import SvgIcon from '@/components/svg-icon';
import { useTheme } from '@/components/theme-provider';
import { SharedFrom } from '@/constants/chat';
import {
useClickConversationCard,
useClickDialogCard,
useFetchNextDialogList,
useGetChatSearchParams,
} from '@/hooks/chat-hooks';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { useTranslate } from '@/hooks/common-hooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { IDialog } from '@/interfaces/database/chat';
import ChatIdModal from './chat-id-modal';
import styles from './index.less';
const { Text } = Typography;
@ -82,13 +84,10 @@ const Chat = () => {
showDialogEditModal,
} = useEditDialog();
const { t } = useTranslate('chat');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
showModal: showOverviewModal,
} = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
const [controller, setController] = useState(new AbortController());
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
useShowEmbedModal();
const handleAppCardEnter = (id: string) => () => {
handleItemEnter(id);
@ -120,7 +119,7 @@ const Chat = () => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
setRecord(dialog);
showOverviewModal();
showEmbedModal();
};
const handleRemoveConversation =
@ -193,7 +192,7 @@ const Chat = () => {
label: (
<Space>
<KeyOutlined />
{t('overview')}
{t('publish', { keyPrefix: 'flow' })}
</Space>
),
},
@ -374,14 +373,16 @@ const Chat = () => {
initialName={initialConversationName}
loading={conversationRenameLoading}
></RenameModal>
{overviewVisible && (
<ChatIdModal
visible={overviewVisible}
hideModal={hideOverviewModal}
id={currentRecord.id}
name={currentRecord.name}
idKey="dialogId"
></ChatIdModal>
{embedVisible && (
<EmbedModal
visible={embedVisible}
hideModal={hideEmbedModal}
token={currentRecord.id}
form={SharedFrom.Chat}
beta={beta}
isAgent={false}
></EmbedModal>
)}
</Flex>
);

View File

@ -1,7 +1,6 @@
import Image from '@/components/image';
import SvgIcon from '@/components/svg-icon';
import { IReference } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import { IReference, IReferenceChunk } from '@/interfaces/database/chat';
import { getExtension } from '@/utils/document-util';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Flex, Popover, Space } from 'antd';
@ -11,6 +10,7 @@ import Markdown from 'react-markdown';
import reactStringReplace from 'react-string-replace';
import SyntaxHighlighter from 'react-syntax-highlighter';
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';
@ -36,7 +36,7 @@ const MarkdownContent = ({
content: string;
loading: boolean;
reference: IReference;
clickDocumentButton?: (documentId: string, chunk: IChunk) => void;
clickDocumentButton?: (documentId: string, chunk: IReferenceChunk) => void;
}) => {
const { t } = useTranslation();
const { setDocumentIds, data: fileThumbnails } =
@ -54,7 +54,7 @@ const MarkdownContent = ({
}, [reference, setDocumentIds]);
const handleDocumentButtonClick = useCallback(
(documentId: string, chunk: IChunk, isPdf: boolean) => () => {
(documentId: string, chunk: IReferenceChunk, isPdf: boolean) => () => {
if (!isPdf) {
return;
}
@ -85,15 +85,15 @@ const MarkdownContent = ({
const chunks = reference?.chunks ?? [];
const chunkItem = chunks[chunkIndex];
const document = reference?.doc_aggs?.find(
(x) => x?.doc_id === chunkItem?.doc_id,
(x) => x?.doc_id === chunkItem?.document_id,
);
const documentId = document?.doc_id;
const fileThumbnail = documentId ? fileThumbnails[documentId] : '';
const fileExtension = documentId ? getExtension(document?.doc_name) : '';
const imageId = chunkItem?.img_id;
const imageId = chunkItem?.image_id;
return (
<Flex
key={chunkItem?.chunk_id}
key={chunkItem?.id}
gap={10}
className={styles.referencePopoverWrapper}
>
@ -116,7 +116,7 @@ const MarkdownContent = ({
<Space direction={'vertical'}>
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(chunkItem?.content_with_weight),
__html: DOMPurify.sanitize(chunkItem?.content ?? ''),
}}
className={styles.chunkContentText}
></div>
@ -176,7 +176,7 @@ const MarkdownContent = ({
return (
<Markdown
rehypePlugins={[rehypeWrapReference, rehypeKatex]}
rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]}
remarkPlugins={[remarkGfm, remarkMath]}
components={
{

View File

@ -1,21 +1,23 @@
import MessageInput from '@/components/message-input';
import MessageItem from '@/components/message-item';
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { MessageType, SharedFrom } from '@/constants/chat';
import { useFetchNextSharedConversation } from '@/hooks/chat-hooks';
import { useSendButtonDisabled } from '@/pages/chat/hooks';
import { Flex, Spin } from 'antd';
import { forwardRef } from 'react';
import {
useCreateSharedConversationOnMount,
useGetSharedChatSearchParams,
useSendSharedMessage,
} from '../shared-hooks';
import { buildMessageItemReference } from '../utils';
import PdfDrawer from '@/components/pdf-drawer';
import styles from './index.less';
const ChatContainer = () => {
const { conversationId } = useCreateSharedConversationOnMount();
const { data } = useFetchNextSharedConversation(conversationId);
const { from, sharedId: conversationId } = useGetSharedChatSearchParams();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
const {
handlePressEnter,
@ -25,9 +27,13 @@ const ChatContainer = () => {
loading,
ref,
derivedMessages,
} = useSendSharedMessage(conversationId);
hasError,
} = useSendSharedMessage();
const sendDisabled = useSendButtonDisabled(value);
const { from } = useGetSharedChatSearchParams();
if (!conversationId) {
return <div>empty</div>;
}
return (
<>
@ -44,7 +50,7 @@ const ChatContainer = () => {
reference={buildMessageItemReference(
{
message: derivedMessages,
reference: data?.data?.reference,
reference: [],
},
message,
)}
@ -54,6 +60,7 @@ const ChatContainer = () => {
derivedMessages?.length - 1 === i
}
index={i}
clickDocumentButton={clickDocumentButton}
></MessageItem>
);
})}
@ -65,7 +72,7 @@ const ChatContainer = () => {
<MessageInput
isShared
value={value}
disabled={false}
disabled={hasError}
sendDisabled={sendDisabled}
conversationId={conversationId}
onInputChange={handleInputChange}
@ -75,6 +82,14 @@ const ChatContainer = () => {
showUploadIcon={from === SharedFrom.Chat}
></MessageInput>
</Flex>
{visible && (
<PdfDrawer
visible={visible}
hideModal={hideModal}
documentId={documentId}
chunk={selectedChunk}
></PdfDrawer>
)}
</>
);
};

View File

@ -1,83 +1,41 @@
import { MessageType, SharedFrom } from '@/constants/chat';
import {
useCreateNextSharedConversation,
useFetchNextSharedConversation,
} from '@/hooks/chat-hooks';
import { useCreateNextSharedConversation } from '@/hooks/chat-hooks';
import {
useSelectDerivedMessages,
useSendMessageWithSse,
} from '@/hooks/logic-hooks';
import { Message } from '@/interfaces/database/chat';
import api from '@/utils/api';
import { message } from 'antd';
import { get } from 'lodash';
import trim from 'lodash/trim';
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'umi';
import { v4 as uuid } from 'uuid';
import { useHandleMessageInputChange } from './hooks';
export const useCreateSharedConversationOnMount = () => {
const [currentQueryParameters] = useSearchParams();
const [conversationId, setConversationId] = useState('');
const { createSharedConversation: createConversation } =
useCreateNextSharedConversation();
const sharedId = currentQueryParameters.get('shared_id');
const userId = currentQueryParameters.get('user_id');
const setConversation = useCallback(async () => {
if (sharedId) {
const data = await createConversation(userId ?? undefined);
const id = data.data?.id;
if (id) {
setConversationId(id);
}
}
}, [createConversation, sharedId, userId]);
useEffect(() => {
setConversation();
}, [setConversation]);
return { conversationId };
};
export const useSelectNextSharedMessages = (conversationId: string) => {
const { data, loading } = useFetchNextSharedConversation(conversationId);
const {
derivedMessages,
ref,
setDerivedMessages,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
} = useSelectDerivedMessages();
useEffect(() => {
setDerivedMessages(data?.data?.message);
}, [setDerivedMessages, data]);
return {
derivedMessages,
addNewestAnswer,
addNewestQuestion,
removeLatestMessage,
loading,
ref,
setDerivedMessages,
};
};
const isCompletionError = (res: any) =>
res && (res?.response.status !== 200 || res?.data?.code !== 0);
export const useSendButtonDisabled = (value: string) => {
return trim(value) === '';
};
export const useSendSharedMessage = (conversationId: string) => {
export const useGetSharedChatSearchParams = () => {
const [searchParams] = useSearchParams();
return {
from: searchParams.get('from') as SharedFrom,
sharedId: searchParams.get('shared_id'),
};
};
export const useSendSharedMessage = () => {
const { from, sharedId: conversationId } = useGetSharedChatSearchParams();
const { createSharedConversation: setConversation } =
useCreateNextSharedConversation();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { send, answer, done } = useSendMessageWithSse(
api.completeExternalConversation,
`/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`,
);
const {
derivedMessages,
@ -85,24 +43,25 @@ export const useSendSharedMessage = (conversationId: string) => {
removeLatestMessage,
addNewestAnswer,
addNewestQuestion,
loading,
} = useSelectNextSharedMessages(conversationId);
} = useSelectDerivedMessages();
const [hasError, setHasError] = useState(false);
const sendMessage = useCallback(
async (message: Message, id?: string) => {
const res = await send({
conversation_id: id ?? conversationId,
quote: false,
messages: [...(derivedMessages ?? []), message],
quote: true,
question: message.content,
session_id: get(derivedMessages, '0.session_id'),
});
if (res && (res?.response.status !== 200 || res?.data?.code !== 0)) {
if (isCompletionError(res)) {
// cancel loading
setValue(message.content);
removeLatestMessage();
}
},
[conversationId, derivedMessages, removeLatestMessage, setValue, send],
[send, conversationId, derivedMessages, setValue, removeLatestMessage],
);
const handleSendMessage = useCallback(
@ -120,6 +79,18 @@ export const useSendSharedMessage = (conversationId: string) => {
[conversationId, setConversation, sendMessage],
);
const fetchSessionId = useCallback(async () => {
const ret = await send({ question: '' });
if (isCompletionError(ret)) {
message.error(ret?.data.message);
setHasError(true);
}
}, [send]);
useEffect(() => {
fetchSessionId();
}, [fetchSessionId, send]);
useEffect(() => {
if (answer.answer) {
addNewestAnswer(answer);
@ -154,16 +125,8 @@ export const useSendSharedMessage = (conversationId: string) => {
value,
sendLoading: !done,
ref,
loading,
loading: false,
derivedMessages,
};
};
export const useGetSharedChatSearchParams = () => {
const [searchParams] = useSearchParams();
return {
from: searchParams.get('from') as SharedFrom,
sharedId: searchParams.get('shared_id'),
hasError,
};
};

View File

@ -1,13 +1,15 @@
import ChatOverviewModal from '@/components/api-service/chat-overview-modal';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import EmbedModal from '@/components/api-service/embed-modal';
import { useShowEmbedModal } from '@/components/api-service/hooks';
import { SharedFrom } from '@/constants/chat';
import { useTranslate } from '@/hooks/common-hooks';
import { useFetchFlow } from '@/hooks/flow-hooks';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { Button, Flex, Space } from 'antd';
import { useCallback } from 'react';
import { Link, useParams } from 'umi';
import FlowIdModal from '../flow-id-modal';
import {
useGetBeginNodeDataQuery,
useGetBeginNodeDataQueryIsEmpty,
useSaveGraph,
useSaveGraphBeforeOpeningDebugDrawer,
useWatchAgentChange,
@ -25,15 +27,16 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
const { data } = useFetchFlow();
const { t } = useTranslate('flow');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
// showModal: showOverviewModal,
} = useSetModalState();
const { visible, hideModal, showModal } = useSetModalState();
const { id } = useParams();
const time = useWatchAgentChange(chatDrawerVisible);
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
useShowEmbedModal();
const isBeginNodeDataQueryEmpty = useGetBeginNodeDataQueryIsEmpty();
const handleShowEmbedModal = useCallback(() => {
showEmbedModal();
}, [showEmbedModal]);
const handleRunAgent = useCallback(() => {
const query: BeginQuery[] = getBeginNodeDataQuery();
@ -70,23 +73,25 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
<Button type="primary" onClick={() => saveGraph()}>
<b>{t('save')}</b>
</Button>
{/* <Button type="primary" onClick={showOverviewModal} disabled>
<Button
type="primary"
onClick={handleShowEmbedModal}
disabled={!isBeginNodeDataQueryEmpty}
>
<b>{t('publish')}</b>
</Button> */}
<Button type="primary" onClick={showModal}>
<b>Agent ID</b>
</Button>
</Space>
</Flex>
{overviewVisible && (
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
id={id!}
idKey="canvasId"
></ChatOverviewModal>
{embedVisible && (
<EmbedModal
visible={embedVisible}
hideModal={hideEmbedModal}
token={id!}
form={SharedFrom.Agent}
beta={beta}
isAgent
></EmbedModal>
)}
{visible && <FlowIdModal hideModal={hideModal}></FlowIdModal>}
</>
);
};

View File

@ -474,6 +474,20 @@ export const useGetBeginNodeDataQuery = () => {
return getBeginNodeDataQuery;
};
export const useGetBeginNodeDataQueryIsEmpty = () => {
const [isBeginNodeDataQueryEmpty, setIsBeginNodeDataQueryEmpty] =
useState(false);
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
const nodes = useGraphStore((state) => state.nodes);
useEffect(() => {
const query: BeginQuery[] = getBeginNodeDataQuery();
setIsBeginNodeDataQueryEmpty(query.length === 0);
}, [getBeginNodeDataQuery, nodes]);
return isBeginNodeDataQueryEmpty;
};
export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
const { saveGraph, loading } = useSaveGraph();
const { resetFlow } = useResetFlow();

View File

@ -47,9 +47,9 @@ const storage = {
};
export const getAuthorization = () => {
const sharedId = getSearchValue('shared_id');
const authorization = sharedId
? 'Bearer ' + sharedId
const auth = getSearchValue('auth');
const authorization = auth
? 'Bearer ' + auth
: storage.getAuthorization() || '';
return authorization;

View File

@ -1,10 +1,12 @@
import { Images, SupportedPreviewDocumentTypes } from '@/constants/common';
import { IReferenceChunk } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import { UploadFile } from 'antd';
import { get } from 'lodash';
import { v4 as uuid } from 'uuid';
export const buildChunkHighlights = (
selectedChunk: IChunk,
selectedChunk: IChunk | IReferenceChunk,
size: { width: number; height: number },
) => {
return Array.isArray(selectedChunk?.positions) &&
@ -24,7 +26,11 @@ export const buildChunkHighlights = (
text: '',
emoji: '',
},
content: { text: selectedChunk.content_with_weight },
content: {
text:
get(selectedChunk, 'content_with_weight') ||
get(selectedChunk, 'content', ''),
},
position: {
boundingRect: boundingRect,
rects: [boundingRect],