mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
Merge pull request #1851 from ManishMadan2882/main
Fixed conflict while switching conversations, separated concerns
This commit is contained in:
@@ -81,8 +81,27 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
|||||||
useState<ActiveState>('INACTIVE');
|
useState<ActiveState>('INACTIVE');
|
||||||
const [recentAgents, setRecentAgents] = useState<Agent[]>([]);
|
const [recentAgents, setRecentAgents] = useState<Agent[]>([]);
|
||||||
|
|
||||||
const navRef = useRef(null);
|
const navRef = useRef<HTMLDivElement>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
function handleClickOutside(event: MouseEvent) {
|
||||||
|
if (
|
||||||
|
navRef.current &&
|
||||||
|
!navRef.current.contains(event.target as Node) &&
|
||||||
|
(isMobile || isTablet) &&
|
||||||
|
navOpen
|
||||||
|
) {
|
||||||
|
setNavOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//event listener only for mobile/tablet when nav is open
|
||||||
|
if ((isMobile || isTablet) && navOpen) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [navOpen, isMobile, isTablet, setNavOpen]);
|
||||||
async function fetchRecentAgents() {
|
async function fetchRecentAgents() {
|
||||||
try {
|
try {
|
||||||
const response = await userService.getPinnedAgents(token);
|
const response = await userService.getPinnedAgents(token);
|
||||||
|
|||||||
@@ -6,24 +6,23 @@ import ConversationMessages from '../conversation/ConversationMessages';
|
|||||||
import { Query } from '../conversation/conversationModels';
|
import { Query } from '../conversation/conversationModels';
|
||||||
import {
|
import {
|
||||||
addQuery,
|
addQuery,
|
||||||
fetchAnswer,
|
fetchPreviewAnswer,
|
||||||
handleAbort,
|
handlePreviewAbort,
|
||||||
resendQuery,
|
resendQuery,
|
||||||
resetConversation,
|
resetPreview,
|
||||||
selectQueries,
|
selectPreviewQueries,
|
||||||
selectStatus,
|
selectPreviewStatus,
|
||||||
} from '../conversation/conversationSlice';
|
} from './agentPreviewSlice';
|
||||||
import { selectSelectedAgent } from '../preferences/preferenceSlice';
|
import { selectSelectedAgent } from '../preferences/preferenceSlice';
|
||||||
import { AppDispatch } from '../store';
|
import { AppDispatch } from '../store';
|
||||||
|
|
||||||
export default function AgentPreview() {
|
export default function AgentPreview() {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
const queries = useSelector(selectQueries);
|
const queries = useSelector(selectPreviewQueries);
|
||||||
const status = useSelector(selectStatus);
|
const status = useSelector(selectPreviewStatus);
|
||||||
const selectedAgent = useSelector(selectSelectedAgent);
|
const selectedAgent = useSelector(selectSelectedAgent);
|
||||||
|
|
||||||
const [input, setInput] = useState('');
|
|
||||||
const [lastQueryReturnedErr, setLastQueryReturnedErr] = useState(false);
|
const [lastQueryReturnedErr, setLastQueryReturnedErr] = useState(false);
|
||||||
|
|
||||||
const fetchStream = useRef<any>(null);
|
const fetchStream = useRef<any>(null);
|
||||||
@@ -31,7 +30,7 @@ export default function AgentPreview() {
|
|||||||
const handleFetchAnswer = useCallback(
|
const handleFetchAnswer = useCallback(
|
||||||
({ question, index }: { question: string; index?: number }) => {
|
({ question, index }: { question: string; index?: number }) => {
|
||||||
fetchStream.current = dispatch(
|
fetchStream.current = dispatch(
|
||||||
fetchAnswer({ question, indx: index, isPreview: true }),
|
fetchPreviewAnswer({ question, indx: index }),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[dispatch],
|
[dispatch],
|
||||||
@@ -95,11 +94,11 @@ export default function AgentPreview() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(resetConversation());
|
dispatch(resetPreview());
|
||||||
return () => {
|
return () => {
|
||||||
if (fetchStream.current) fetchStream.current.abort();
|
if (fetchStream.current) fetchStream.current.abort();
|
||||||
handleAbort();
|
handlePreviewAbort();
|
||||||
dispatch(resetConversation());
|
dispatch(resetPreview());
|
||||||
};
|
};
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
|||||||
@@ -57,9 +57,7 @@ export default function SharedAgent() {
|
|||||||
|
|
||||||
const handleFetchAnswer = useCallback(
|
const handleFetchAnswer = useCallback(
|
||||||
({ question, index }: { question: string; index?: number }) => {
|
({ question, index }: { question: string; index?: number }) => {
|
||||||
fetchStream.current = dispatch(
|
fetchStream.current = dispatch(fetchAnswer({ question, indx: index }));
|
||||||
fetchAnswer({ question, indx: index, isPreview: false }),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|||||||
319
frontend/src/agents/agentPreviewSlice.ts
Normal file
319
frontend/src/agents/agentPreviewSlice.ts
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
Answer,
|
||||||
|
ConversationState,
|
||||||
|
Query,
|
||||||
|
Status,
|
||||||
|
} from '../conversation/conversationModels';
|
||||||
|
import {
|
||||||
|
handleFetchAnswer,
|
||||||
|
handleFetchAnswerSteaming,
|
||||||
|
} from '../conversation/conversationHandlers';
|
||||||
|
import {
|
||||||
|
selectCompletedAttachments,
|
||||||
|
clearAttachments,
|
||||||
|
} from '../upload/uploadSlice';
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
|
const initialState: ConversationState = {
|
||||||
|
queries: [],
|
||||||
|
status: 'idle',
|
||||||
|
conversationId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_STREAMING = import.meta.env.VITE_API_STREAMING === 'true';
|
||||||
|
|
||||||
|
let abortController: AbortController | null = null;
|
||||||
|
export function handlePreviewAbort() {
|
||||||
|
if (abortController) {
|
||||||
|
abortController.abort();
|
||||||
|
abortController = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchPreviewAnswer = createAsyncThunk<
|
||||||
|
Answer,
|
||||||
|
{ question: string; indx?: number }
|
||||||
|
>(
|
||||||
|
'agentPreview/fetchAnswer',
|
||||||
|
async ({ question, indx }, { dispatch, getState }) => {
|
||||||
|
if (abortController) abortController.abort();
|
||||||
|
abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const attachmentIds = selectCompletedAttachments(state)
|
||||||
|
.filter((a) => a.id)
|
||||||
|
.map((a) => a.id) as string[];
|
||||||
|
|
||||||
|
if (attachmentIds.length > 0) {
|
||||||
|
dispatch(clearAttachments());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.preference) {
|
||||||
|
if (API_STREAMING) {
|
||||||
|
await handleFetchAnswerSteaming(
|
||||||
|
question,
|
||||||
|
signal,
|
||||||
|
state.preference.token,
|
||||||
|
state.preference.selectedDocs!,
|
||||||
|
state.agentPreview.queries,
|
||||||
|
null, // No conversation ID for previews
|
||||||
|
state.preference.prompt.id,
|
||||||
|
state.preference.chunks,
|
||||||
|
state.preference.token_limit,
|
||||||
|
(event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
const targetIndex = indx ?? state.agentPreview.queries.length - 1;
|
||||||
|
|
||||||
|
if (data.type === 'end') {
|
||||||
|
dispatch(agentPreviewSlice.actions.setStatus('idle'));
|
||||||
|
} else if (data.type === 'thought') {
|
||||||
|
dispatch(
|
||||||
|
updateThought({
|
||||||
|
index: targetIndex,
|
||||||
|
query: { thought: data.thought },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (data.type === 'source') {
|
||||||
|
dispatch(
|
||||||
|
updateStreamingSource({
|
||||||
|
index: targetIndex,
|
||||||
|
query: { sources: data.source ?? [] },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (data.type === 'tool_call') {
|
||||||
|
dispatch(
|
||||||
|
updateToolCall({
|
||||||
|
index: targetIndex,
|
||||||
|
tool_call: data.data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (data.type === 'error') {
|
||||||
|
dispatch(agentPreviewSlice.actions.setStatus('failed'));
|
||||||
|
dispatch(
|
||||||
|
agentPreviewSlice.actions.raiseError({
|
||||||
|
index: targetIndex,
|
||||||
|
message: data.error,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
updateStreamingQuery({
|
||||||
|
index: targetIndex,
|
||||||
|
query: { response: data.answer },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indx,
|
||||||
|
state.preference.selectedAgent?.id,
|
||||||
|
attachmentIds,
|
||||||
|
false, // Don't save preview conversations
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Non-streaming implementation
|
||||||
|
const answer = await handleFetchAnswer(
|
||||||
|
question,
|
||||||
|
signal,
|
||||||
|
state.preference.token,
|
||||||
|
state.preference.selectedDocs!,
|
||||||
|
state.agentPreview.queries,
|
||||||
|
null, // No conversation ID for previews
|
||||||
|
state.preference.prompt.id,
|
||||||
|
state.preference.chunks,
|
||||||
|
state.preference.token_limit,
|
||||||
|
state.preference.selectedAgent?.id,
|
||||||
|
attachmentIds,
|
||||||
|
false, // Don't save preview conversations
|
||||||
|
);
|
||||||
|
|
||||||
|
if (answer) {
|
||||||
|
const 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;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const targetIndex = indx ?? state.agentPreview.queries.length - 1;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateQuery({
|
||||||
|
index: targetIndex,
|
||||||
|
query: {
|
||||||
|
response: answer.answer,
|
||||||
|
thought: answer.thought,
|
||||||
|
sources: sourcesPrepped,
|
||||||
|
tool_calls: answer.toolCalls,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
dispatch(agentPreviewSlice.actions.setStatus('idle'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversationId: null,
|
||||||
|
title: null,
|
||||||
|
answer: '',
|
||||||
|
query: question,
|
||||||
|
result: '',
|
||||||
|
thought: '',
|
||||||
|
sources: [],
|
||||||
|
tool_calls: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const agentPreviewSlice = createSlice({
|
||||||
|
name: 'agentPreview',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
addQuery(state, action: PayloadAction<Query>) {
|
||||||
|
state.queries.push(action.payload);
|
||||||
|
},
|
||||||
|
resendQuery(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ index: number; prompt: string; query?: Query }>,
|
||||||
|
) {
|
||||||
|
state.queries = [
|
||||||
|
...state.queries.splice(0, action.payload.index),
|
||||||
|
action.payload,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
updateStreamingQuery(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
index: number;
|
||||||
|
query: Partial<Query>;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const { index, query } = action.payload;
|
||||||
|
if (state.status === 'idle') return;
|
||||||
|
|
||||||
|
if (query.response != undefined) {
|
||||||
|
state.queries[index].response =
|
||||||
|
(state.queries[index].response || '') + query.response;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateThought(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
index: number;
|
||||||
|
query: Partial<Query>;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const { index, query } = action.payload;
|
||||||
|
if (query.thought != undefined) {
|
||||||
|
state.queries[index].thought =
|
||||||
|
(state.queries[index].thought || '') + query.thought;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateStreamingSource(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
index: number;
|
||||||
|
query: Partial<Query>;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const { index, query } = action.payload;
|
||||||
|
if (!state.queries[index].sources) {
|
||||||
|
state.queries[index].sources = query?.sources;
|
||||||
|
} else if (query.sources) {
|
||||||
|
state.queries[index].sources!.push(...query.sources);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateToolCall(state, action) {
|
||||||
|
const { index, tool_call } = action.payload;
|
||||||
|
|
||||||
|
if (!state.queries[index].tool_calls) {
|
||||||
|
state.queries[index].tool_calls = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingIndex = state.queries[index].tool_calls.findIndex(
|
||||||
|
(call) => call.call_id === tool_call.call_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
const existingCall = state.queries[index].tool_calls[existingIndex];
|
||||||
|
state.queries[index].tool_calls[existingIndex] = {
|
||||||
|
...existingCall,
|
||||||
|
...tool_call,
|
||||||
|
};
|
||||||
|
} else state.queries[index].tool_calls.push(tool_call);
|
||||||
|
},
|
||||||
|
updateQuery(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||||
|
) {
|
||||||
|
const { index, query } = action.payload;
|
||||||
|
state.queries[index] = {
|
||||||
|
...state.queries[index],
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setStatus(state, action: PayloadAction<Status>) {
|
||||||
|
state.status = action.payload;
|
||||||
|
},
|
||||||
|
raiseError(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
index: number;
|
||||||
|
message: string;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
const { index, message } = action.payload;
|
||||||
|
state.queries[index].error = message;
|
||||||
|
},
|
||||||
|
resetPreview: (state) => {
|
||||||
|
state.queries = initialState.queries;
|
||||||
|
state.status = initialState.status;
|
||||||
|
state.conversationId = initialState.conversationId;
|
||||||
|
handlePreviewAbort();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers(builder) {
|
||||||
|
builder
|
||||||
|
.addCase(fetchPreviewAnswer.pending, (state) => {
|
||||||
|
state.status = 'loading';
|
||||||
|
})
|
||||||
|
.addCase(fetchPreviewAnswer.rejected, (state, action) => {
|
||||||
|
if (action.meta.aborted) {
|
||||||
|
state.status = 'idle';
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
state.status = 'failed';
|
||||||
|
state.queries[state.queries.length - 1].error = 'Something went wrong';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type RootState = ReturnType<typeof store.getState>;
|
||||||
|
|
||||||
|
export const selectPreviewQueries = (state: RootState) =>
|
||||||
|
state.agentPreview.queries;
|
||||||
|
export const selectPreviewStatus = (state: RootState) =>
|
||||||
|
state.agentPreview.status;
|
||||||
|
|
||||||
|
export const {
|
||||||
|
addQuery,
|
||||||
|
updateQuery,
|
||||||
|
resendQuery,
|
||||||
|
updateStreamingQuery,
|
||||||
|
updateThought,
|
||||||
|
updateStreamingSource,
|
||||||
|
updateToolCall,
|
||||||
|
setStatus,
|
||||||
|
raiseError,
|
||||||
|
resetPreview,
|
||||||
|
} = agentPreviewSlice.actions;
|
||||||
|
|
||||||
|
export default agentPreviewSlice.reducer;
|
||||||
@@ -53,7 +53,7 @@ function Dropdown({
|
|||||||
darkBorderColor?: string;
|
darkBorderColor?: string;
|
||||||
showEdit?: boolean;
|
showEdit?: boolean;
|
||||||
onEdit?: (value: { name: string; id: string; type: string }) => void;
|
onEdit?: (value: { name: string; id: string; type: string }) => void;
|
||||||
showDelete?: boolean;
|
showDelete?: boolean | ((option: any) => boolean);
|
||||||
onDelete?: (value: string) => void;
|
onDelete?: (value: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
placeholderTextColor?: string;
|
placeholderTextColor?: string;
|
||||||
@@ -173,8 +173,15 @@ function Dropdown({
|
|||||||
)}
|
)}
|
||||||
{showDelete && onDelete && (
|
{showDelete && onDelete && (
|
||||||
<button
|
<button
|
||||||
onClick={() => onDelete(option.id)}
|
onClick={(e) => {
|
||||||
disabled={option.type === 'public'}
|
e.stopPropagation();
|
||||||
|
onDelete?.(typeof option === 'string' ? option : option.id);
|
||||||
|
}}
|
||||||
|
className={`${
|
||||||
|
typeof showDelete === 'function' && !showDelete(option)
|
||||||
|
? 'hidden'
|
||||||
|
: ''
|
||||||
|
} mr-2 h-4 w-4 cursor-pointer hover:opacity-50`}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={Trash}
|
src={Trash}
|
||||||
|
|||||||
@@ -157,12 +157,7 @@ const ConversationBubble = forwardRef<
|
|||||||
{!isEditClicked && (
|
{!isEditClicked && (
|
||||||
<>
|
<>
|
||||||
<div className="relative mr-2 flex w-full flex-col">
|
<div className="relative mr-2 flex w-full flex-col">
|
||||||
<div
|
<div className="ml-2 mr-2 flex max-w-full items-start gap-2 whitespace-pre-wrap break-words rounded-[28px] bg-gradient-to-b from-medium-purple to-slate-blue px-5 py-4 text-sm leading-normal text-white sm:text-base">
|
||||||
style={{
|
|
||||||
wordBreak: 'break-word',
|
|
||||||
}}
|
|
||||||
className="ml-2 mr-2 flex max-w-full items-start gap-2 whitespace-pre-wrap rounded-[28px] bg-gradient-to-b from-medium-purple to-slate-blue py-[14px] pl-[19px] pr-3 text-sm leading-normal text-white sm:text-base"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
ref={messageRef}
|
ref={messageRef}
|
||||||
className={`${isQuestionCollapsed ? 'line-clamp-4' : ''} w-full`}
|
className={`${isQuestionCollapsed ? 'line-clamp-4' : ''} w-full`}
|
||||||
@@ -175,7 +170,7 @@ const ConversationBubble = forwardRef<
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsQuestionCollapsed(!isQuestionCollapsed);
|
setIsQuestionCollapsed(!isQuestionCollapsed);
|
||||||
}}
|
}}
|
||||||
className="rounded-full p-2.5 hover:bg-[#D9D9D933]"
|
className="ml-1 rounded-full p-2 hover:bg-[#D9D9D933]"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={ChevronDown}
|
src={ChevronDown}
|
||||||
|
|||||||
@@ -11,13 +11,7 @@ import {
|
|||||||
handleFetchAnswer,
|
handleFetchAnswer,
|
||||||
handleFetchAnswerSteaming,
|
handleFetchAnswerSteaming,
|
||||||
} from './conversationHandlers';
|
} from './conversationHandlers';
|
||||||
import {
|
import { Answer, ConversationState, Query, Status } from './conversationModels';
|
||||||
Answer,
|
|
||||||
Attachment,
|
|
||||||
ConversationState,
|
|
||||||
Query,
|
|
||||||
Status,
|
|
||||||
} from './conversationModels';
|
|
||||||
import { ToolCallsType } from './types';
|
import { ToolCallsType } from './types';
|
||||||
|
|
||||||
const initialState: ConversationState = {
|
const initialState: ConversationState = {
|
||||||
@@ -38,10 +32,8 @@ export function handleAbort() {
|
|||||||
|
|
||||||
export const fetchAnswer = createAsyncThunk<
|
export const fetchAnswer = createAsyncThunk<
|
||||||
Answer,
|
Answer,
|
||||||
{ question: string; indx?: number; isPreview?: boolean }
|
{ question: string; indx?: number }
|
||||||
>(
|
>('fetchAnswer', async ({ question, indx }, { dispatch, getState }) => {
|
||||||
'fetchAnswer',
|
|
||||||
async ({ question, indx, isPreview = false }, { dispatch, getState }) => {
|
|
||||||
if (abortController) abortController.abort();
|
if (abortController) abortController.abort();
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
const { signal } = abortController;
|
const { signal } = abortController;
|
||||||
@@ -57,8 +49,6 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentConversationId = state.conversation.conversationId;
|
const currentConversationId = state.conversation.conversationId;
|
||||||
const conversationIdToSend = isPreview ? null : currentConversationId;
|
|
||||||
const save_conversation = isPreview ? false : true;
|
|
||||||
|
|
||||||
if (state.preference) {
|
if (state.preference) {
|
||||||
if (API_STREAMING) {
|
if (API_STREAMING) {
|
||||||
@@ -68,7 +58,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
state.preference.token,
|
state.preference.token,
|
||||||
state.preference.selectedDocs!,
|
state.preference.selectedDocs!,
|
||||||
state.conversation.queries,
|
state.conversation.queries,
|
||||||
conversationIdToSend,
|
currentConversationId,
|
||||||
state.preference.prompt.id,
|
state.preference.prompt.id,
|
||||||
state.preference.chunks,
|
state.preference.chunks,
|
||||||
state.preference.token_limit,
|
state.preference.token_limit,
|
||||||
@@ -76,9 +66,10 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
const targetIndex = indx ?? state.conversation.queries.length - 1;
|
const targetIndex = indx ?? state.conversation.queries.length - 1;
|
||||||
|
|
||||||
|
// Only process events if they match the current conversation
|
||||||
|
if (currentConversationId === state.conversation.conversationId) {
|
||||||
if (data.type === 'end') {
|
if (data.type === 'end') {
|
||||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||||
if (!isPreview) {
|
|
||||||
getConversations(state.preference.token)
|
getConversations(state.preference.token)
|
||||||
.then((fetchedConversations) => {
|
.then((fetchedConversations) => {
|
||||||
dispatch(setConversations(fetchedConversations));
|
dispatch(setConversations(fetchedConversations));
|
||||||
@@ -86,17 +77,19 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to fetch conversations: ', error);
|
console.error('Failed to fetch conversations: ', error);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (!isSourceUpdated) {
|
if (!isSourceUpdated) {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateStreamingSource({
|
updateStreamingSource({
|
||||||
|
conversationId: currentConversationId,
|
||||||
index: targetIndex,
|
index: targetIndex,
|
||||||
query: { sources: [] },
|
query: { sources: [] },
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (data.type === 'id') {
|
} else if (data.type === 'id') {
|
||||||
if (!isPreview) {
|
// Only update the conversationId if it's currently null
|
||||||
|
const currentState = getState() as RootState;
|
||||||
|
if (currentState.conversation.conversationId === null) {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateConversationId({
|
updateConversationId({
|
||||||
query: { conversationId: data.id },
|
query: { conversationId: data.id },
|
||||||
@@ -107,6 +100,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
const result = data.thought;
|
const result = data.thought;
|
||||||
dispatch(
|
dispatch(
|
||||||
updateThought({
|
updateThought({
|
||||||
|
conversationId: currentConversationId,
|
||||||
index: targetIndex,
|
index: targetIndex,
|
||||||
query: { thought: result },
|
query: { thought: result },
|
||||||
}),
|
}),
|
||||||
@@ -115,6 +109,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
isSourceUpdated = true;
|
isSourceUpdated = true;
|
||||||
dispatch(
|
dispatch(
|
||||||
updateStreamingSource({
|
updateStreamingSource({
|
||||||
|
conversationId: currentConversationId,
|
||||||
index: targetIndex,
|
index: targetIndex,
|
||||||
query: { sources: data.source ?? [] },
|
query: { sources: data.source ?? [] },
|
||||||
}),
|
}),
|
||||||
@@ -131,6 +126,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
dispatch(conversationSlice.actions.setStatus('failed'));
|
dispatch(conversationSlice.actions.setStatus('failed'));
|
||||||
dispatch(
|
dispatch(
|
||||||
conversationSlice.actions.raiseError({
|
conversationSlice.actions.raiseError({
|
||||||
|
conversationId: currentConversationId,
|
||||||
index: targetIndex,
|
index: targetIndex,
|
||||||
message: data.error,
|
message: data.error,
|
||||||
}),
|
}),
|
||||||
@@ -138,16 +134,18 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
} else {
|
} else {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateStreamingQuery({
|
updateStreamingQuery({
|
||||||
|
conversationId: currentConversationId,
|
||||||
index: targetIndex,
|
index: targetIndex,
|
||||||
query: { response: data.answer },
|
query: { response: data.answer },
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
indx,
|
indx,
|
||||||
state.preference.selectedAgent?.id,
|
state.preference.selectedAgent?.id,
|
||||||
attachmentIds,
|
attachmentIds,
|
||||||
save_conversation,
|
true, // Always save conversation
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const answer = await handleFetchAnswer(
|
const answer = await handleFetchAnswer(
|
||||||
@@ -162,7 +160,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
state.preference.token_limit,
|
state.preference.token_limit,
|
||||||
state.preference.selectedAgent?.id,
|
state.preference.selectedAgent?.id,
|
||||||
attachmentIds,
|
attachmentIds,
|
||||||
save_conversation,
|
true, // Always save conversation
|
||||||
);
|
);
|
||||||
if (answer) {
|
if (answer) {
|
||||||
let sourcesPrepped = [];
|
let sourcesPrepped = [];
|
||||||
@@ -190,7 +188,6 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (!isPreview) {
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updateConversationId({
|
updateConversationId({
|
||||||
query: { conversationId: answer.conversationId },
|
query: { conversationId: answer.conversationId },
|
||||||
@@ -203,7 +200,6 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to fetch conversations: ', error);
|
console.error('Failed to fetch conversations: ', error);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,8 +214,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
sources: [],
|
sources: [],
|
||||||
tool_calls: [],
|
tool_calls: [],
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const conversationSlice = createSlice({
|
export const conversationSlice = createSlice({
|
||||||
name: 'conversation',
|
name: 'conversation',
|
||||||
@@ -242,18 +237,20 @@ export const conversationSlice = createSlice({
|
|||||||
},
|
},
|
||||||
updateStreamingQuery(
|
updateStreamingQuery(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
action: PayloadAction<{
|
||||||
|
conversationId: string | null;
|
||||||
|
index: number;
|
||||||
|
query: Partial<Query>;
|
||||||
|
}>,
|
||||||
) {
|
) {
|
||||||
if (state.status === 'idle') return;
|
const { conversationId, index, query } = action.payload;
|
||||||
const { index, query } = action.payload;
|
// Only update if this update is for the current conversation
|
||||||
|
if (state.status === 'idle' || state.conversationId !== conversationId)
|
||||||
|
return;
|
||||||
|
|
||||||
if (query.response != undefined) {
|
if (query.response != undefined) {
|
||||||
state.queries[index].response =
|
state.queries[index].response =
|
||||||
(state.queries[index].response || '') + query.response;
|
(state.queries[index].response || '') + query.response;
|
||||||
} else {
|
|
||||||
state.queries[index] = {
|
|
||||||
...state.queries[index],
|
|
||||||
...query,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateConversationId(
|
updateConversationId(
|
||||||
@@ -265,28 +262,35 @@ export const conversationSlice = createSlice({
|
|||||||
},
|
},
|
||||||
updateThought(
|
updateThought(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
action: PayloadAction<{
|
||||||
|
conversationId: string | null;
|
||||||
|
index: number;
|
||||||
|
query: Partial<Query>;
|
||||||
|
}>,
|
||||||
) {
|
) {
|
||||||
const { index, query } = action.payload;
|
const { conversationId, index, query } = action.payload;
|
||||||
|
if (state.conversationId !== conversationId) return;
|
||||||
|
|
||||||
if (query.thought != undefined) {
|
if (query.thought != undefined) {
|
||||||
state.queries[index].thought =
|
state.queries[index].thought =
|
||||||
(state.queries[index].thought || '') + query.thought;
|
(state.queries[index].thought || '') + query.thought;
|
||||||
} else {
|
|
||||||
state.queries[index] = {
|
|
||||||
...state.queries[index],
|
|
||||||
...query,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateStreamingSource(
|
updateStreamingSource(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
action: PayloadAction<{
|
||||||
|
conversationId: string | null;
|
||||||
|
index: number;
|
||||||
|
query: Partial<Query>;
|
||||||
|
}>,
|
||||||
) {
|
) {
|
||||||
const { index, query } = action.payload;
|
const { conversationId, index, query } = action.payload;
|
||||||
|
if (state.conversationId !== conversationId) return;
|
||||||
|
|
||||||
if (!state.queries[index].sources) {
|
if (!state.queries[index].sources) {
|
||||||
state.queries[index].sources = query?.sources;
|
state.queries[index].sources = query?.sources;
|
||||||
} else {
|
} else if (query.sources) {
|
||||||
state.queries[index].sources!.push(query.sources![0]);
|
state.queries[index].sources!.push(...query.sources);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateToolCall(state, action) {
|
updateToolCall(state, action) {
|
||||||
@@ -323,9 +327,15 @@ export const conversationSlice = createSlice({
|
|||||||
},
|
},
|
||||||
raiseError(
|
raiseError(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ index: number; message: string }>,
|
action: PayloadAction<{
|
||||||
|
conversationId: string | null;
|
||||||
|
index: number;
|
||||||
|
message: string;
|
||||||
|
}>,
|
||||||
) {
|
) {
|
||||||
const { index, message } = action.payload;
|
const { conversationId, index, message } = action.payload;
|
||||||
|
if (state.conversationId !== conversationId) return;
|
||||||
|
|
||||||
state.queries[index].error = message;
|
state.queries[index].error = message;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ export default function Prompts({
|
|||||||
rounded="3xl"
|
rounded="3xl"
|
||||||
border="border"
|
border="border"
|
||||||
showEdit
|
showEdit
|
||||||
showDelete
|
showDelete={(prompt) => prompt.type !== 'public'}
|
||||||
onEdit={({
|
onEdit={({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
prefSlice,
|
prefSlice,
|
||||||
} from './preferences/preferenceSlice';
|
} from './preferences/preferenceSlice';
|
||||||
import uploadReducer from './upload/uploadSlice';
|
import uploadReducer from './upload/uploadSlice';
|
||||||
|
import agentPreviewReducer from './agents/agentPreviewSlice';
|
||||||
|
|
||||||
const key = localStorage.getItem('DocsGPTApiKey');
|
const key = localStorage.getItem('DocsGPTApiKey');
|
||||||
const prompt = localStorage.getItem('DocsGPTPrompt');
|
const prompt = localStorage.getItem('DocsGPTPrompt');
|
||||||
@@ -54,6 +55,7 @@ const store = configureStore({
|
|||||||
conversation: conversationSlice.reducer,
|
conversation: conversationSlice.reducer,
|
||||||
sharedConversation: sharedConversationSlice.reducer,
|
sharedConversation: sharedConversationSlice.reducer,
|
||||||
upload: uploadReducer,
|
upload: uploadReducer,
|
||||||
|
agentPreview: agentPreviewReducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware().concat(prefListenerMiddleware.middleware),
|
getDefaultMiddleware().concat(prefListenerMiddleware.middleware),
|
||||||
|
|||||||
Reference in New Issue
Block a user