mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 00:23:17 +00:00
Merge pull request #1444 from Niharika0104/Fixes-#1260
Fixed edit and resend issue
This commit is contained in:
@@ -118,8 +118,19 @@ def is_azure_configured():
|
||||
)
|
||||
|
||||
|
||||
def save_conversation(conversation_id, question, response, source_log_docs, llm):
|
||||
if conversation_id is not None and conversation_id != "None":
|
||||
def save_conversation(conversation_id, question, response, source_log_docs, llm,index=None):
|
||||
if conversation_id is not None and index is not None:
|
||||
conversations_collection.update_one(
|
||||
{"_id": ObjectId(conversation_id), f"queries.{index}": {"$exists": True}},
|
||||
{
|
||||
"$set": {
|
||||
f"queries.{index}.prompt": question,
|
||||
f"queries.{index}.response": response,
|
||||
f"queries.{index}.sources": source_log_docs,
|
||||
}
|
||||
}
|
||||
)
|
||||
elif conversation_id is not None and conversation_id != "None":
|
||||
conversations_collection.update_one(
|
||||
{"_id": ObjectId(conversation_id)},
|
||||
{
|
||||
@@ -186,7 +197,7 @@ def get_prompt(prompt_id):
|
||||
|
||||
|
||||
def complete_stream(
|
||||
question, retriever, conversation_id, user_api_key, isNoneDoc=False
|
||||
question, retriever, conversation_id, user_api_key, isNoneDoc=False,index=None
|
||||
):
|
||||
|
||||
try:
|
||||
@@ -217,7 +228,7 @@ def complete_stream(
|
||||
)
|
||||
if user_api_key is None:
|
||||
conversation_id = save_conversation(
|
||||
conversation_id, question, response_full, source_log_docs, llm
|
||||
conversation_id, question, response_full, source_log_docs, llm,index
|
||||
)
|
||||
# send data.type = "end" to indicate that the stream has ended as json
|
||||
data = json.dumps({"type": "id", "id": str(conversation_id)})
|
||||
@@ -282,6 +293,9 @@ class Stream(Resource):
|
||||
"isNoneDoc": fields.Boolean(
|
||||
required=False, description="Flag indicating if no document is used"
|
||||
),
|
||||
"index":fields.Integer(
|
||||
required=False, description="The position where query is to be updated"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -290,23 +304,24 @@ class Stream(Resource):
|
||||
def post(self):
|
||||
data = request.get_json()
|
||||
required_fields = ["question"]
|
||||
|
||||
if "index" in data:
|
||||
required_fields = ["question","conversation_id"]
|
||||
missing_fields = check_required_fields(data, required_fields)
|
||||
if missing_fields:
|
||||
return missing_fields
|
||||
|
||||
try:
|
||||
question = data["question"]
|
||||
history = data.get("history", [])
|
||||
history = json.loads(history)
|
||||
history = str(data.get("history", []))
|
||||
history = str(json.loads(history))
|
||||
conversation_id = data.get("conversation_id")
|
||||
prompt_id = data.get("prompt_id", "default")
|
||||
|
||||
|
||||
index=data.get("index",None)
|
||||
chunks = int(data.get("chunks", 2))
|
||||
token_limit = data.get("token_limit", settings.DEFAULT_MAX_HISTORY)
|
||||
retriever_name = data.get("retriever", "classic")
|
||||
|
||||
|
||||
if "api_key" in data:
|
||||
data_key = get_data_from_api_key(data["api_key"])
|
||||
chunks = int(data_key.get("chunks", 2))
|
||||
@@ -343,7 +358,7 @@ class Stream(Resource):
|
||||
gpt_model=gpt_model,
|
||||
user_api_key=user_api_key,
|
||||
)
|
||||
|
||||
|
||||
return Response(
|
||||
complete_stream(
|
||||
question=question,
|
||||
@@ -351,6 +366,7 @@ class Stream(Resource):
|
||||
conversation_id=conversation_id,
|
||||
user_api_key=user_api_key,
|
||||
isNoneDoc=data.get("isNoneDoc"),
|
||||
index=index,
|
||||
),
|
||||
mimetype="text/event-stream",
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ const endpoints = {
|
||||
TOKEN_ANALYTICS: '/api/get_token_analytics',
|
||||
FEEDBACK_ANALYTICS: '/api/get_feedback_analytics',
|
||||
LOGS: `/api/get_user_logs`,
|
||||
MANAGE_SYNC: '/api/manage_sync',
|
||||
MANAGE_SYNC: '/api/manage_sync'
|
||||
},
|
||||
CONVERSATION: {
|
||||
ANSWER: '/api/answer',
|
||||
|
||||
@@ -15,12 +15,14 @@ import { useDarkTheme, useMediaQuery } from '../hooks';
|
||||
import { ShareConversationModal } from '../modals/ShareConversationModal';
|
||||
import { selectConversationId } from '../preferences/preferenceSlice';
|
||||
import { AppDispatch } from '../store';
|
||||
import conversationService from '../api/services/conversationService';
|
||||
import ConversationBubble from './ConversationBubble';
|
||||
import { handleSendFeedback } from './conversationHandlers';
|
||||
import { FEEDBACK, Query } from './conversationModels';
|
||||
import {
|
||||
addQuery,
|
||||
fetchAnswer,
|
||||
resendQuery,
|
||||
selectQueries,
|
||||
selectStatus,
|
||||
setConversation,
|
||||
@@ -85,15 +87,25 @@ export default function Conversation() {
|
||||
const handleQuestion = ({
|
||||
question,
|
||||
isRetry = false,
|
||||
updated = null,
|
||||
indx = undefined,
|
||||
}: {
|
||||
question: string;
|
||||
isRetry?: boolean;
|
||||
updated?: boolean | null;
|
||||
indx?: number;
|
||||
}) => {
|
||||
question = question.trim();
|
||||
if (question === '') return;
|
||||
setEventInterrupt(false);
|
||||
!isRetry && dispatch(addQuery({ prompt: question })); //dispatch only new queries
|
||||
fetchStream.current = dispatch(fetchAnswer({ question }));
|
||||
if (updated === true) {
|
||||
!isRetry &&
|
||||
dispatch(resendQuery({ index: indx as number, prompt: question })); //dispatch only new queries
|
||||
fetchStream.current = dispatch(fetchAnswer({ question, indx }));
|
||||
} else {
|
||||
question = question.trim();
|
||||
if (question === '') return;
|
||||
setEventInterrupt(false);
|
||||
!isRetry && dispatch(addQuery({ prompt: question })); //dispatch only new queries
|
||||
fetchStream.current = dispatch(fetchAnswer({ question }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFeedback = (query: Query, feedback: FEEDBACK, index: number) => {
|
||||
@@ -104,8 +116,14 @@ export default function Conversation() {
|
||||
);
|
||||
};
|
||||
|
||||
const handleQuestionSubmission = () => {
|
||||
if (inputRef.current?.value && status !== 'loading') {
|
||||
const handleQuestionSubmission = (
|
||||
updatedQuestion?: string,
|
||||
updated?: boolean,
|
||||
indx?: number,
|
||||
) => {
|
||||
if (updated === true) {
|
||||
handleQuestion({ question: updatedQuestion as string, updated, indx });
|
||||
} else if (inputRef.current?.value && status !== 'loading') {
|
||||
if (lastQueryReturnedErr) {
|
||||
// update last failed query with new prompt
|
||||
dispatch(
|
||||
@@ -290,6 +308,8 @@ export default function Conversation() {
|
||||
key={`${index}QUESTION`}
|
||||
message={query.prompt}
|
||||
type="QUESTION"
|
||||
handleUpdatedQuestionSubmission={handleQuestionSubmission}
|
||||
questionNumber={index}
|
||||
sources={query.sources}
|
||||
></ConversationBubble>
|
||||
|
||||
@@ -328,7 +348,7 @@ export default function Conversation() {
|
||||
<div className="mx-1 cursor-pointer rounded-full p-3 text-center hover:bg-gray-3000 dark:hover:bg-dark-charcoal">
|
||||
<img
|
||||
className="ml-[4px] h-6 w-6 text-white "
|
||||
onClick={handleQuestionSubmission}
|
||||
onClick={() => handleQuestionSubmission()}
|
||||
src={isDarkTheme ? SendDark : Send}
|
||||
></img>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@ import Document from '../assets/document.svg';
|
||||
import Like from '../assets/like.svg?react';
|
||||
import Link from '../assets/link.svg';
|
||||
import Sources from '../assets/sources.svg';
|
||||
import Edit from '../assets/edit.svg';
|
||||
import Avatar from '../components/Avatar';
|
||||
import CopyButton from '../components/CopyButton';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
@@ -38,37 +39,104 @@ const ConversationBubble = forwardRef<
|
||||
handleFeedback?: (feedback: FEEDBACK) => void;
|
||||
sources?: { title: string; text: string; source: string }[];
|
||||
retryBtn?: React.ReactElement;
|
||||
questionNumber?: number;
|
||||
handleUpdatedQuestionSubmission?: (
|
||||
updatedquestion?: string,
|
||||
updated?: boolean,
|
||||
index?: number,
|
||||
) => void;
|
||||
}
|
||||
>(function ConversationBubble(
|
||||
{ message, type, className, feedback, handleFeedback, sources, retryBtn },
|
||||
{
|
||||
message,
|
||||
type,
|
||||
className,
|
||||
feedback,
|
||||
handleFeedback,
|
||||
sources,
|
||||
retryBtn,
|
||||
questionNumber,
|
||||
handleUpdatedQuestionSubmission,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
// const bubbleRef = useRef<HTMLDivElement | null>(null);
|
||||
const chunks = useSelector(selectChunks);
|
||||
const selectedDocs = useSelector(selectSelectedDocs);
|
||||
const [isLikeHovered, setIsLikeHovered] = useState(false);
|
||||
const [isEditClicked, setIsEditClicked] = useState(false);
|
||||
const [isDislikeHovered, setIsDislikeHovered] = useState(false);
|
||||
const [isQuestionHovered, setIsQuestionHovered] = useState(false);
|
||||
const [editInputBox, setEditInputBox] = useState<string>('');
|
||||
|
||||
const [isLikeClicked, setIsLikeClicked] = useState(false);
|
||||
const [isDislikeClicked, setIsDislikeClicked] = useState(false);
|
||||
const [activeTooltip, setActiveTooltip] = useState<number | null>(null);
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);
|
||||
|
||||
const handleEditClick = () => {
|
||||
setIsEditClicked(false);
|
||||
handleUpdatedQuestionSubmission?.(editInputBox, true, questionNumber);
|
||||
};
|
||||
let bubble;
|
||||
if (type === 'QUESTION') {
|
||||
bubble = (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`flex flex-row-reverse self-end flex-wrap ${className}`}
|
||||
onMouseEnter={() => setIsQuestionHovered(true)}
|
||||
onMouseLeave={() => setIsQuestionHovered(false)}
|
||||
>
|
||||
<Avatar className="mt-2 text-2xl" avatar="🧑💻"></Avatar>
|
||||
<div
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
className="ml-10 mr-2 flex items-center rounded-[28px] bg-purple-30 py-[14px] px-[19px] text-white max-w-full whitespace-pre-wrap leading-normal"
|
||||
ref={ref}
|
||||
className={`flex flex-row-reverse self-end flex-wrap ${className}`}
|
||||
>
|
||||
{message}
|
||||
<Avatar className="mt-2 text-2xl" avatar="🧑💻"></Avatar>
|
||||
{!isEditClicked && (
|
||||
<div
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
className="ml-2 mr-2 flex items-center rounded-[28px] bg-purple-30 py-[14px] px-[19px] text-white max-w-full whitespace-pre-wrap leading-normal"
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
{isEditClicked && (
|
||||
<input
|
||||
onChange={(e) => setEditInputBox(e.target.value)}
|
||||
value={editInputBox}
|
||||
className="w-[85%] ml-2 mr-2 rounded-[28px] py-[12px] dark:border-[0.5px] dark:border-white dark:bg-raisin-black dark:text-white px-[18px] border-[1.5px] border-black"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={` flex items-center ${isQuestionHovered ? 'visible' : 'invisible'}`}
|
||||
>
|
||||
<img
|
||||
src={Edit}
|
||||
alt="Edit"
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsEditClicked(true);
|
||||
setEditInputBox(message);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{isEditClicked && (
|
||||
<div className={`flex gap-3 self-end mt-3 `}>
|
||||
<button
|
||||
className="ml-2 mr-2 flex items-center rounded-[28px] font-semibold hover:font-bold py-[14px] px-[19px] no-underline hover:underline text-purple-500 max-w-full whitespace-pre-wrap leading-normal"
|
||||
onClick={() => setIsEditClicked(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="ml-2 mr-2 flex items-center rounded-full bg-purple-300 hover:bg-purple-100 font-bold px-[19px] py-[2px] text-purple-500 max-w-full whitespace-pre-wrap leading-none"
|
||||
onClick={() => handleEditClick()}
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -75,6 +75,7 @@ export function handleFetchAnswerSteaming(
|
||||
chunks: string,
|
||||
token_limit: number,
|
||||
onEvent: (event: MessageEvent) => void,
|
||||
indx?: number,
|
||||
): Promise<Answer> {
|
||||
history = history.map((item) => {
|
||||
return { prompt: item.prompt, response: item.response };
|
||||
@@ -87,6 +88,7 @@ export function handleFetchAnswerSteaming(
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
index: indx,
|
||||
};
|
||||
if (selectedDocs && 'id' in selectedDocs) {
|
||||
payload.active_docs = selectedDocs.id as string;
|
||||
|
||||
@@ -41,4 +41,5 @@ export interface RetrievalPayload {
|
||||
chunks: string;
|
||||
token_limit: number;
|
||||
isNoneDoc: boolean;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
@@ -25,138 +25,139 @@ export function handleAbort() {
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
|
||||
'fetchAnswer',
|
||||
async ({ question }, { dispatch, getState }) => {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
}
|
||||
abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
export const fetchAnswer = createAsyncThunk<
|
||||
Answer,
|
||||
{ question: string; indx?: number }
|
||||
>('fetchAnswer', async ({ question, indx }, { dispatch, getState }) => {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
}
|
||||
abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
|
||||
let isSourceUpdated = false;
|
||||
const state = getState() as RootState;
|
||||
if (state.preference) {
|
||||
if (API_STREAMING) {
|
||||
await handleFetchAnswerSteaming(
|
||||
question,
|
||||
signal,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
let isSourceUpdated = false;
|
||||
const state = getState() as RootState;
|
||||
if (state.preference) {
|
||||
if (API_STREAMING) {
|
||||
await handleFetchAnswerSteaming(
|
||||
question,
|
||||
signal,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
(event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
(event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'end') {
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
getConversations()
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
});
|
||||
if (!isSourceUpdated) {
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
index: state.conversation.queries.length - 1,
|
||||
query: { sources: [] },
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else if (data.type === 'id') {
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: data.id },
|
||||
}),
|
||||
);
|
||||
} else if (data.type === 'source') {
|
||||
isSourceUpdated = true;
|
||||
if (data.type === 'end') {
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
getConversations()
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
});
|
||||
if (!isSourceUpdated) {
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
index: state.conversation.queries.length - 1,
|
||||
query: { sources: data.source ?? [] },
|
||||
}),
|
||||
);
|
||||
} else if (data.type === 'error') {
|
||||
// set status to 'failed'
|
||||
dispatch(conversationSlice.actions.setStatus('failed'));
|
||||
dispatch(
|
||||
conversationSlice.actions.raiseError({
|
||||
index: state.conversation.queries.length - 1,
|
||||
message: data.error,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const result = data.answer;
|
||||
dispatch(
|
||||
updateStreamingQuery({
|
||||
index: state.conversation.queries.length - 1,
|
||||
query: { response: result },
|
||||
index: indx ?? state.conversation.queries.length - 1,
|
||||
query: { sources: [] },
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const answer = await handleFetchAnswer(
|
||||
question,
|
||||
signal,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
);
|
||||
if (answer) {
|
||||
let sourcesPrepped = [];
|
||||
sourcesPrepped = answer.sources.map((source: { title: string }) => {
|
||||
if (source && source.title) {
|
||||
const titleParts = source.title.split('/');
|
||||
return {
|
||||
...source,
|
||||
title: titleParts[titleParts.length - 1],
|
||||
};
|
||||
}
|
||||
return source;
|
||||
});
|
||||
} else if (data.type === 'id') {
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: data.id },
|
||||
}),
|
||||
);
|
||||
} else if (data.type === 'source') {
|
||||
isSourceUpdated = true;
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
index: indx ?? state.conversation.queries.length - 1,
|
||||
query: { sources: data.source ?? [] },
|
||||
}),
|
||||
);
|
||||
} else if (data.type === 'error') {
|
||||
// set status to 'failed'
|
||||
dispatch(conversationSlice.actions.setStatus('failed'));
|
||||
dispatch(
|
||||
conversationSlice.actions.raiseError({
|
||||
index: indx ?? state.conversation.queries.length - 1,
|
||||
message: data.error,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const result = data.answer;
|
||||
dispatch(
|
||||
updateStreamingQuery({
|
||||
index: indx ?? state.conversation.queries.length - 1,
|
||||
query: { response: result },
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
indx,
|
||||
);
|
||||
} else {
|
||||
const answer = await handleFetchAnswer(
|
||||
question,
|
||||
signal,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
);
|
||||
if (answer) {
|
||||
let sourcesPrepped = [];
|
||||
sourcesPrepped = answer.sources.map((source: { title: string }) => {
|
||||
if (source && source.title) {
|
||||
const titleParts = source.title.split('/');
|
||||
return {
|
||||
...source,
|
||||
title: titleParts[titleParts.length - 1],
|
||||
};
|
||||
}
|
||||
return source;
|
||||
});
|
||||
|
||||
dispatch(
|
||||
updateQuery({
|
||||
index: state.conversation.queries.length - 1,
|
||||
query: { response: answer.answer, sources: sourcesPrepped },
|
||||
}),
|
||||
);
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: answer.conversationId },
|
||||
}),
|
||||
);
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
getConversations()
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
});
|
||||
}
|
||||
dispatch(
|
||||
updateQuery({
|
||||
index: indx ?? state.conversation.queries.length - 1,
|
||||
query: { response: answer.answer, sources: sourcesPrepped },
|
||||
}),
|
||||
);
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: answer.conversationId },
|
||||
}),
|
||||
);
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
getConversations()
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
conversationId: null,
|
||||
title: null,
|
||||
answer: '',
|
||||
query: question,
|
||||
result: '',
|
||||
sources: [],
|
||||
};
|
||||
},
|
||||
}
|
||||
return {
|
||||
conversationId: null,
|
||||
title: null,
|
||||
answer: '',
|
||||
query: question,
|
||||
result: '',
|
||||
sources: [],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const conversationSlice = createSlice({
|
||||
@@ -169,6 +170,12 @@ export const conversationSlice = createSlice({
|
||||
setConversation(state, action: PayloadAction<Query[]>) {
|
||||
state.queries = action.payload;
|
||||
},
|
||||
resendQuery(
|
||||
state,
|
||||
action: PayloadAction<{ index: number; prompt: string; query?: Query }>,
|
||||
) {
|
||||
state.queries[action.payload.index] = action.payload;
|
||||
},
|
||||
updateStreamingQuery(
|
||||
state,
|
||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||
@@ -250,6 +257,7 @@ export const selectStatus = (state: RootState) => state.conversation.status;
|
||||
export const {
|
||||
addQuery,
|
||||
updateQuery,
|
||||
resendQuery,
|
||||
updateStreamingQuery,
|
||||
updateConversationId,
|
||||
updateStreamingSource,
|
||||
|
||||
Reference in New Issue
Block a user