diff --git a/frontend/src/agents/agentPreviewSlice.ts b/frontend/src/agents/agentPreviewSlice.ts
index d765b789..55152d52 100644
--- a/frontend/src/agents/agentPreviewSlice.ts
+++ b/frontend/src/agents/agentPreviewSlice.ts
@@ -195,12 +195,21 @@ export const agentPreviewSlice = createSlice({
},
resendQuery(
state,
- action: PayloadAction<{ index: number; prompt: string; query?: Query }>,
+ action: PayloadAction<{ index: number; prompt: string }>,
) {
- state.queries = [
- ...state.queries.splice(0, action.payload.index),
- action.payload,
- ];
+ const { index, prompt } = action.payload;
+ if (index < 0 || index >= state.queries.length) return;
+
+ state.queries.splice(index + 1);
+ state.queries[index].prompt = prompt;
+ delete state.queries[index].response;
+ delete state.queries[index].thought;
+ delete state.queries[index].sources;
+ delete state.queries[index].tool_calls;
+ delete state.queries[index].error;
+ delete state.queries[index].structured;
+ delete state.queries[index].schema;
+ delete state.queries[index].feedback;
},
updateStreamingQuery(
state,
@@ -309,10 +318,13 @@ export const agentPreviewSlice = createSlice({
.addCase(fetchPreviewAnswer.rejected, (state, action) => {
if (action.meta.aborted) {
state.status = 'idle';
- return state;
+ return;
}
state.status = 'failed';
- state.queries[state.queries.length - 1].error = 'Something went wrong';
+ if (state.queries.length > 0) {
+ state.queries[state.queries.length - 1].error =
+ 'Something went wrong';
+ }
});
},
});
diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx
index 62fe7c54..c36d6a7c 100644
--- a/frontend/src/conversation/Conversation.tsx
+++ b/frontend/src/conversation/Conversation.tsx
@@ -120,18 +120,20 @@ export default function Conversation() {
if (updated === true) {
handleQuestion({ question: question as string, index: indx });
} else if (question && status !== 'loading') {
- if (lastQueryReturnedErr) {
+ if (lastQueryReturnedErr && queries.length > 0) {
+ const retryIndex = queries.length - 1;
dispatch(
updateQuery({
- index: queries.length - 1,
+ index: retryIndex,
query: {
prompt: question,
},
}),
);
handleQuestion({
- question: question,
+ question,
isRetry: true,
+ index: retryIndex,
});
} else {
handleQuestion({
@@ -152,11 +154,14 @@ export default function Conversation() {
};
useEffect(() => {
- if (queries.length) {
- queries[queries.length - 1].error && setLastQueryReturnedErr(true);
- queries[queries.length - 1].response && setLastQueryReturnedErr(false);
+ if (queries.length === 0) {
+ setLastQueryReturnedErr(false);
+ return;
}
- }, [queries[queries.length - 1]]);
+
+ const lastQuery = queries[queries.length - 1];
+ setLastQueryReturnedErr(!!lastQuery.error && !lastQuery.response);
+ }, [queries]);
return (
diff --git a/frontend/src/conversation/ConversationMessages.tsx b/frontend/src/conversation/ConversationMessages.tsx
index bd099f12..02d5b831 100644
--- a/frontend/src/conversation/ConversationMessages.tsx
+++ b/frontend/src/conversation/ConversationMessages.tsx
@@ -24,13 +24,12 @@ type ConversationMessagesProps = {
handleQuestion: (params: {
question: string;
isRetry?: boolean;
- updated?: boolean | null;
- indx?: number;
+ index?: number;
}) => void;
handleQuestionSubmission: (
updatedQuestion?: string,
updated?: boolean,
- indx?: number,
+ index?: number,
) => void;
handleFeedback?: (query: Query, feedback: FEEDBACK, index: number) => void;
queries: Query[];
@@ -169,7 +168,7 @@ export default function ConversationMessages({
handleQuestion({
question: questionToRetry,
isRetry: true,
- indx: index,
+ index,
});
}}
aria-label={t('Retry') || 'Retry'}
diff --git a/frontend/src/conversation/conversationSlice.ts b/frontend/src/conversation/conversationSlice.ts
index 0298d78c..936d2de8 100644
--- a/frontend/src/conversation/conversationSlice.ts
+++ b/frontend/src/conversation/conversationSlice.ts
@@ -241,12 +241,21 @@ export const conversationSlice = createSlice({
},
resendQuery(
state,
- action: PayloadAction<{ index: number; prompt: string; query?: Query }>,
+ action: PayloadAction<{ index: number; prompt: string }>,
) {
- state.queries = [
- ...state.queries.splice(0, action.payload.index),
- action.payload,
- ];
+ const { index, prompt } = action.payload;
+ if (index < 0 || index >= state.queries.length) return;
+
+ state.queries.splice(index + 1);
+ state.queries[index].prompt = prompt;
+ delete state.queries[index].response;
+ delete state.queries[index].thought;
+ delete state.queries[index].sources;
+ delete state.queries[index].tool_calls;
+ delete state.queries[index].error;
+ delete state.queries[index].structured;
+ delete state.queries[index].schema;
+ delete state.queries[index].feedback;
},
updateStreamingQuery(
state,
@@ -370,7 +379,7 @@ export const conversationSlice = createSlice({
.addCase(fetchAnswer.rejected, (state, action) => {
if (action.meta.aborted) {
state.status = 'idle';
- return state;
+ return;
}
state.status = 'failed';
if (state.queries.length > 0) {
diff --git a/frontend/src/conversation/sharedConversationSlice.ts b/frontend/src/conversation/sharedConversationSlice.ts
index df2650a3..2d52c59a 100644
--- a/frontend/src/conversation/sharedConversationSlice.ts
+++ b/frontend/src/conversation/sharedConversationSlice.ts
@@ -266,10 +266,13 @@ export const sharedConversationSlice = createSlice({
.addCase(fetchSharedAnswer.rejected, (state, action) => {
if (action.meta.aborted) {
state.status = 'idle';
- return state;
+ return;
}
state.status = 'failed';
- state.queries[state.queries.length - 1].error = 'Something went wrong';
+ if (state.queries.length > 0) {
+ state.queries[state.queries.length - 1].error =
+ 'Something went wrong';
+ }
});
},
});
diff --git a/frontend/src/preferences/preferenceApi.ts b/frontend/src/preferences/preferenceApi.ts
index 39396959..f94a0f20 100644
--- a/frontend/src/preferences/preferenceApi.ts
+++ b/frontend/src/preferences/preferenceApi.ts
@@ -90,12 +90,49 @@ export function getLocalApiKey(): string | null {
return key;
}
-export function getLocalRecentDocs(sourceDocs?: Doc[] | null): Doc[] | null {
- const docsString = localStorage.getItem('DocsGPTRecentDocs');
- const selectedDocs = docsString ? (JSON.parse(docsString) as Doc[]) : null;
+function parseStoredRecentDocs(docsString: string | null): Doc[] | null {
+ if (!docsString) {
+ return null;
+ }
- if (!sourceDocs || !selectedDocs || selectedDocs.length === 0) {
- return selectedDocs;
+ try {
+ const parsedDocs: unknown = JSON.parse(docsString);
+
+ if (Array.isArray(parsedDocs)) {
+ const docs = parsedDocs.filter(
+ (doc): doc is Doc => typeof doc === 'object' && doc !== null,
+ );
+ return docs.length > 0 ? docs : null;
+ }
+
+ if (typeof parsedDocs === 'object' && parsedDocs !== null) {
+ return [parsedDocs as Doc];
+ }
+ } catch (error) {
+ console.warn('Failed to parse DocsGPTRecentDocs from localStorage', error);
+ }
+
+ return null;
+}
+
+export function getStoredRecentDocs(): Doc[] {
+ const recentDocs = parseStoredRecentDocs(
+ localStorage.getItem('DocsGPTRecentDocs'),
+ );
+
+ if (!recentDocs || recentDocs.length === 0) {
+ localStorage.removeItem('DocsGPTRecentDocs');
+ return [];
+ }
+
+ return recentDocs;
+}
+
+export function getLocalRecentDocs(sourceDocs?: Doc[] | null): Doc[] | null {
+ const selectedDocs = getStoredRecentDocs();
+
+ if (!sourceDocs || selectedDocs.length === 0) {
+ return selectedDocs.length > 0 ? selectedDocs : null;
}
const isDocAvailable = (selected: Doc) => {
return sourceDocs.some((source) => {
diff --git a/frontend/src/store.ts b/frontend/src/store.ts
index f5844a9a..6371570a 100644
--- a/frontend/src/store.ts
+++ b/frontend/src/store.ts
@@ -3,6 +3,7 @@ import { configureStore } from '@reduxjs/toolkit';
import agentPreviewReducer from './agents/agentPreviewSlice';
import { conversationSlice } from './conversation/conversationSlice';
import { sharedConversationSlice } from './conversation/sharedConversationSlice';
+import { getStoredRecentDocs } from './preferences/preferenceApi';
import {
Preference,
prefListenerMiddleware,
@@ -13,7 +14,6 @@ import uploadReducer from './upload/uploadSlice';
const key = localStorage.getItem('DocsGPTApiKey');
const prompt = localStorage.getItem('DocsGPTPrompt');
const chunks = localStorage.getItem('DocsGPTChunks');
-const doc = localStorage.getItem('DocsGPTRecentDocs');
const selectedModel = localStorage.getItem('DocsGPTSelectedModel');
const preloadedState: { preference: Preference } = {
@@ -30,7 +30,7 @@ const preloadedState: { preference: Preference } = {
{ name: 'strict', id: 'strict', type: 'public' },
],
chunks: JSON.parse(chunks ?? '2').toString(),
- selectedDocs: doc !== null ? JSON.parse(doc) : [],
+ selectedDocs: getStoredRecentDocs(),
conversations: {
data: null,
loading: false,
diff --git a/frontend/src/upload/uploadSlice.ts b/frontend/src/upload/uploadSlice.ts
index d997b1fb..44c67843 100644
--- a/frontend/src/upload/uploadSlice.ts
+++ b/frontend/src/upload/uploadSlice.ts
@@ -1,4 +1,4 @@
-import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '../store';
export interface Attachment {
@@ -147,8 +147,10 @@ export const {
} = uploadSlice.actions;
export const selectAttachments = (state: RootState) => state.upload.attachments;
-export const selectCompletedAttachments = (state: RootState) =>
- state.upload.attachments.filter((att) => att.status === 'completed');
+export const selectCompletedAttachments = createSelector(
+ [selectAttachments],
+ (attachments) => attachments.filter((att) => att.status === 'completed'),
+);
export const selectUploadTasks = (state: RootState) => state.upload.tasks;
export default uploadSlice.reducer;