Bladeren bron

Feat: Interrupt streaming #6515 (#6723)

### What problem does this PR solve?

Feat: Interrupt streaming #6515
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.18.0
balibabu 7 maanden geleden
bovenliggende
commit
132eae9d5b
No account linked to committer's email address

+ 22
- 9
web/src/components/message-input/index.tsx Bestand weergeven

UploadProps, UploadProps,
} from 'antd'; } from 'antd';
import get from 'lodash/get'; import get from 'lodash/get';
import { CircleStop } from 'lucide-react';
import { import {
ChangeEventHandler, ChangeEventHandler,
memo, memo,
isShared?: boolean; isShared?: boolean;
showUploadIcon?: boolean; showUploadIcon?: boolean;
createConversationBeforeUploadDocument?(message: string): Promise<any>; createConversationBeforeUploadDocument?(message: string): Promise<any>;
stopOutputMessage?(): void;
} }


const getBase64 = (file: FileType): Promise<string> => const getBase64 = (file: FileType): Promise<string> =>
showUploadIcon = true, showUploadIcon = true,
createConversationBeforeUploadDocument, createConversationBeforeUploadDocument,
uploadMethod = 'upload_and_parse', uploadMethod = 'upload_and_parse',
stopOutputMessage,
}: IProps) => { }: IProps) => {
const { t } = useTranslate('chat'); const { t } = useTranslate('chat');
const { removeDocument } = useRemoveNextDocument(); const { removeDocument } = useRemoveNextDocument();
event.preventDefault(); event.preventDefault();
handlePressEnter(); handlePressEnter();
}, },
[fileList, onPressEnter, isUploadingFile],
[sendDisabled, isUploadingFile, sendLoading, handlePressEnter],
); );


const handlePressEnter = useCallback(async () => { const handlePressEnter = useCallback(async () => {
[removeDocument, deleteDocument, isShared], [removeDocument, deleteDocument, isShared],
); );


const handleStopOutputMessage = useCallback(() => {
stopOutputMessage?.();
}, [stopOutputMessage]);

const getDocumentInfoById = useCallback( const getDocumentInfoById = useCallback(
(id: string) => { (id: string) => {
return documentInfos.find((x) => x.id === id); return documentInfos.find((x) => x.id === id);
</Button> </Button>
</Upload> </Upload>
)} )}
<Button
type="primary"
onClick={handlePressEnter}
loading={sendLoading}
disabled={sendDisabled || isUploadingFile || sendLoading}
>
<SendOutlined />
</Button>
{sendLoading ? (
<Button onClick={handleStopOutputMessage}>
<CircleStop />
</Button>
) : (
<Button
type="primary"
onClick={handlePressEnter}
loading={sendLoading}
disabled={sendDisabled || isUploadingFile || sendLoading}
>
<SendOutlined />
</Button>
)}
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>

+ 13
- 3
web/src/hooks/logic-hooks.ts Bestand weergeven

const [answer, setAnswer] = useState<IAnswer>({} as IAnswer); const [answer, setAnswer] = useState<IAnswer>({} as IAnswer);
const [done, setDone] = useState(true); const [done, setDone] = useState(true);
const timer = useRef<any>(); const timer = useRef<any>();
const sseRef = useRef<AbortController>();

const initializeSseRef = useCallback(() => {
sseRef.current = new AbortController();
}, []);


const resetAnswer = useCallback(() => { const resetAnswer = useCallback(() => {
if (timer.current) { if (timer.current) {
body: any, body: any,
controller?: AbortController, controller?: AbortController,
): Promise<{ response: Response; data: ResponseType } | undefined> => { ): Promise<{ response: Response; data: ResponseType } | undefined> => {
initializeSseRef();
try { try {
setDone(false); setDone(false);
const response = await fetch(url, { const response = await fetch(url, {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
signal: controller?.signal,
signal: controller?.signal || sseRef.current?.signal,
}); });


const res = response.clone().json(); const res = response.clone().json();
console.warn(e); console.warn(e);
} }
}, },
[url, resetAnswer],
[initializeSseRef, url, resetAnswer],
); );


return { send, answer, done, setDone, resetAnswer };
const stopOutputMessage = useCallback(() => {
sseRef.current?.abort();
}, []);

return { send, answer, done, setDone, resetAnswer, stopOutputMessage };
}; };


export const useSpeechWithSse = (url: string = api.tts) => { export const useSpeechWithSse = (url: string = api.tts) => {

+ 2
- 0
web/src/pages/chat/chat-container/index.tsx Bestand weergeven

handlePressEnter, handlePressEnter,
regenerateMessage, regenerateMessage,
removeMessageById, removeMessageById,
stopOutputMessage,
} = useSendNextMessage(controller); } = useSendNextMessage(controller);


const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
createConversationBeforeUploadDocument={ createConversationBeforeUploadDocument={
createConversationBeforeUploadDocument createConversationBeforeUploadDocument
} }
stopOutputMessage={stopOutputMessage}
></MessageInput> ></MessageInput>
</Flex> </Flex>
<PdfDrawer <PdfDrawer

+ 5
- 0
web/src/pages/chat/hooks.ts Bestand weergeven

const { setConversationIsNew, getConversationIsNew } = const { setConversationIsNew, getConversationIsNew } =
useSetChatRouteParams(); useSetChatRouteParams();


const stopOutputMessage = useCallback(() => {
controller.abort();
}, [controller]);

const sendMessage = useCallback( const sendMessage = useCallback(
async ({ async ({
message, message,
ref, ref,
derivedMessages, derivedMessages,
removeMessageById, removeMessageById,
stopOutputMessage,
}; };
}; };



+ 2
- 0
web/src/pages/chat/share/large.tsx Bestand weergeven

ref, ref,
derivedMessages, derivedMessages,
hasError, hasError,
stopOutputMessage,
} = useSendSharedMessage(); } = useSendSharedMessage();
const sendDisabled = useSendButtonDisabled(value); const sendDisabled = useSendButtonDisabled(value);


sendLoading={sendLoading} sendLoading={sendLoading}
uploadMethod="external_upload_and_parse" uploadMethod="external_upload_and_parse"
showUploadIcon={false} showUploadIcon={false}
stopOutputMessage={stopOutputMessage}
></MessageInput> ></MessageInput>
</Flex> </Flex>
{visible && ( {visible && (

+ 2
- 1
web/src/pages/chat/shared-hooks.ts Bestand weergeven

const { createSharedConversation: setConversation } = const { createSharedConversation: setConversation } =
useCreateNextSharedConversation(); useCreateNextSharedConversation();
const { handleInputChange, value, setValue } = useHandleMessageInputChange(); const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { send, answer, done } = useSendMessageWithSse(
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
`/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`, `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`,
); );
const { const {
loading: false, loading: false,
derivedMessages, derivedMessages,
hasError, hasError,
stopOutputMessage,
}; };
}; };

+ 2
- 0
web/src/pages/flow/chat/box.tsx Bestand weergeven

ref, ref,
derivedMessages, derivedMessages,
reference, reference,
stopOutputMessage,
} = useSendNextMessage(); } = useSendNextMessage();


const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
conversationId="" conversationId=""
onPressEnter={handlePressEnter} onPressEnter={handlePressEnter}
onInputChange={handleInputChange} onInputChange={handleInputChange}
stopOutputMessage={stopOutputMessage}
/> />
</Flex> </Flex>
<PdfDrawer <PdfDrawer

+ 4
- 1
web/src/pages/flow/chat/hooks.ts Bestand weergeven

const { handleInputChange, value, setValue } = useHandleMessageInputChange(); const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { refetch } = useFetchFlow(); const { refetch } = useFetchFlow();


const { send, answer, done } = useSendMessageWithSse(api.runCanvas);
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
api.runCanvas,
);


const sendMessage = useCallback( const sendMessage = useCallback(
async ({ message }: { message: Message; messages?: Message[] }) => { async ({ message }: { message: Message; messages?: Message[] }) => {
derivedMessages, derivedMessages,
ref, ref,
removeMessageById, removeMessageById,
stopOutputMessage,
}; };
}; };

+ 4
- 1
web/src/pages/search/hooks.ts Bestand weergeven

} from 'react'; } from 'react';


export const useSendQuestion = (kbIds: string[]) => { export const useSendQuestion = (kbIds: string[]) => {
const { send, answer, done } = useSendMessageWithSse(api.ask);
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
api.ask,
);
const { testChunk, loading } = useTestChunkRetrieval(); const { testChunk, loading } = useTestChunkRetrieval();
const [sendingLoading, setSendingLoading] = useState(false); const [sendingLoading, setSendingLoading] = useState(false);
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer); const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
isFirstRender, isFirstRender,
selectedDocumentIds, selectedDocumentIds,
isSearchStrEmpty: isEmpty(trim(searchStr)), isSearchStrEmpty: isEmpty(trim(searchStr)),
stopOutputMessage,
}; };
}; };



+ 6
- 0
web/src/pages/search/index.less Bestand weergeven

.input(); .input();
} }


.searchInput {
:global(.ant-input-search-button) {
display: none;
}
}

.appIcon { .appIcon {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;

+ 24
- 3
web/src/pages/search/index.tsx Bestand weergeven

import { useGetPaginationWithRouter } from '@/hooks/logic-hooks'; import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
import { IReference } from '@/interfaces/database/chat'; import { IReference } from '@/interfaces/database/chat';
import { import {
Button,
Card, Card,
Divider, Divider,
Flex, Flex,
Tag, Tag,
Tooltip, Tooltip,
} from 'antd'; } from 'antd';
import classNames from 'classnames';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { useMemo, useState } from 'react';
import { CircleStop, SendHorizontal } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import MarkdownContent from '../chat/markdown-content'; import MarkdownContent from '../chat/markdown-content';
import { useSendQuestion, useShowMindMapDrawer } from './hooks'; import { useSendQuestion, useShowMindMapDrawer } from './hooks';
isFirstRender, isFirstRender,
selectedDocumentIds, selectedDocumentIds,
isSearchStrEmpty, isSearchStrEmpty,
stopOutputMessage,
} = useSendQuestion(checkedWithoutEmbeddingIdList); } = useSendQuestion(checkedWithoutEmbeddingIdList);
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer(); useClickDrawer();
handleTestChunk(selectedDocumentIds, pageNumber, pageSize); handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
}; };


const handleSearch = useCallback(() => {
sendQuestion(searchStr);
}, [searchStr, sendQuestion]);

const InputSearch = ( const InputSearch = (
<Search <Search
value={searchStr} value={searchStr}
onChange={handleSearchStrChange} onChange={handleSearchStrChange}
placeholder={t('header.search')} placeholder={t('header.search')}
allowClear allowClear
enterButton
addonAfter={
sendingLoading ? (
<Button onClick={stopOutputMessage}>
<CircleStop />
</Button>
) : (
<Button onClick={handleSearch}>
<SendHorizontal className="size-5 text-blue-500" />
</Button>
)
}
onSearch={sendQuestion} onSearch={sendQuestion}
size="large" size="large"
loading={sendingLoading} loading={sendingLoading}
disabled={checkedWithoutEmbeddingIdList.length === 0} disabled={checkedWithoutEmbeddingIdList.length === 0}
className={isFirstRender ? styles.globalInput : styles.partialInput}
className={classNames(
styles.searchInput,
isFirstRender ? styles.globalInput : styles.partialInput,
)}
/> />
); );



Laden…
Annuleren
Opslaan