mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-16 23:45:33 +01:00
refactor: nav and search.
feat: use recoil to replace redux feat: use react-native THIS IS NOT FINISHED. DONT USE THIS
This commit is contained in:
parent
d8ccc5b870
commit
af3d74b104
33 changed files with 1142 additions and 473 deletions
106
client/src/store/conversation.js
Normal file
106
client/src/store/conversation.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import models from './models';
|
||||
import { atom, selector, useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil';
|
||||
import buildTree from '~/utils/buildTree';
|
||||
|
||||
// current conversation, can be null (need to be fetched from server)
|
||||
// sample structure
|
||||
// {
|
||||
// conversationId: "new",
|
||||
// title: "New Chat",
|
||||
// jailbreakConversationId: null,
|
||||
// conversationSignature: null,
|
||||
// clientId: null,
|
||||
// invocationId: null,
|
||||
// model: "chatgpt",
|
||||
// chatGptLabel: null,
|
||||
// promptPrefix: null,
|
||||
// user: null,
|
||||
// suggestions: [],
|
||||
// toneStyle: null,
|
||||
// }
|
||||
const conversation = atom({
|
||||
key: 'conversation',
|
||||
default: null
|
||||
});
|
||||
|
||||
// current messages of the conversation, must be an array
|
||||
// sample structure
|
||||
// [{text, sender, messageId, parentMessageId, isCreatedByUser}]
|
||||
const messages = atom({
|
||||
key: 'messages',
|
||||
default: []
|
||||
});
|
||||
|
||||
const messagesTree = selector({
|
||||
key: 'messagesTree',
|
||||
get: ({ get }) => {
|
||||
return buildTree(get(messages));
|
||||
}
|
||||
});
|
||||
|
||||
const latestMessage = atom({
|
||||
key: 'latestMessage',
|
||||
default: null
|
||||
});
|
||||
|
||||
const useConversation = () => {
|
||||
const modelsFilter = useRecoilValue(models.modelsFilter);
|
||||
const setConversation = useSetRecoilState(conversation);
|
||||
const setMessages = useSetRecoilState(messages);
|
||||
const resetLatestMessage = useResetRecoilState(latestMessage);
|
||||
|
||||
const newConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => {
|
||||
const getDefaultModel = () => {
|
||||
try {
|
||||
// try to read latest selected model from local storage
|
||||
const lastSelected = JSON.parse(localStorage.getItem('model'));
|
||||
const { model: _model, chatGptLabel: _chatGptLabel, promptPrefix: _promptPrefix } = lastSelected;
|
||||
|
||||
if (modelsFilter[_model]) {
|
||||
model = _model;
|
||||
chatGptLabel = _chatGptLabel;
|
||||
promptPrefix = _promptPrefix;
|
||||
return;
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
// if anything happens, reset to default model
|
||||
if (modelsFilter?.chatgpt) model = 'chatgpt';
|
||||
else if (modelsFilter?.bingai) model = 'bingai';
|
||||
else if (modelsFilter?.chatgptBrowser) model = 'chatgptBrowser';
|
||||
chatGptLabel = null;
|
||||
promptPrefix = null;
|
||||
};
|
||||
|
||||
if (model === null)
|
||||
// get the default model
|
||||
getDefaultModel();
|
||||
|
||||
setConversation({
|
||||
conversationId: 'new',
|
||||
title: 'New Chat',
|
||||
jailbreakConversationId: null,
|
||||
conversationSignature: null,
|
||||
clientId: null,
|
||||
invocationId: null,
|
||||
model: model,
|
||||
chatGptLabel: chatGptLabel,
|
||||
promptPrefix: promptPrefix,
|
||||
user: null,
|
||||
suggestions: [],
|
||||
toneStyle: null
|
||||
});
|
||||
setMessages([]);
|
||||
resetLatestMessage();
|
||||
};
|
||||
|
||||
return { newConversation };
|
||||
};
|
||||
|
||||
export default {
|
||||
conversation,
|
||||
messages,
|
||||
messagesTree,
|
||||
latestMessage,
|
||||
useConversation
|
||||
};
|
||||
27
client/src/store/conversations.js
Normal file
27
client/src/store/conversations.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React from "react";
|
||||
import {
|
||||
RecoilRoot,
|
||||
atom,
|
||||
selector,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
useSetRecoilState,
|
||||
} from "recoil";
|
||||
|
||||
const refreshConversationsHint = atom({
|
||||
key: "refreshConversationsHint",
|
||||
default: 1,
|
||||
});
|
||||
|
||||
const useConversations = () => {
|
||||
const setRefreshConversationsHint = useSetRecoilState(
|
||||
refreshConversationsHint
|
||||
);
|
||||
|
||||
const refreshConversations = () =>
|
||||
setRefreshConversationsHint((prevState) => prevState + 1);
|
||||
|
||||
return { refreshConversations };
|
||||
};
|
||||
|
||||
export default { refreshConversationsHint, useConversations };
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
error: false,
|
||||
title: 'ChatGPT Clone',
|
||||
conversationId: null,
|
||||
parentMessageId: null,
|
||||
jailbreakConversationId: null,
|
||||
conversationSignature: null,
|
||||
clientId: null,
|
||||
invocationId: null,
|
||||
toneStyle: null,
|
||||
chatGptLabel: null,
|
||||
promptPrefix: null,
|
||||
convosLoading: false,
|
||||
pageNumber: 1,
|
||||
pages: 1,
|
||||
refreshConvoHint: 0,
|
||||
search: false,
|
||||
latestMessage: null,
|
||||
convos: [],
|
||||
convoMap: {},
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'convo',
|
||||
initialState,
|
||||
reducers: {
|
||||
refreshConversation: (state) => {
|
||||
state.refreshConvoHint = state.refreshConvoHint + 1;
|
||||
},
|
||||
setConversation: (state, action) => {
|
||||
// return { ...state, ...action.payload };
|
||||
|
||||
for (const key in action.payload) {
|
||||
if (Object.hasOwnProperty.call(action.payload, key)) {
|
||||
state[key] = action.payload[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
setError: (state, action) => {
|
||||
state.error = action.payload;
|
||||
},
|
||||
increasePage: (state) => {
|
||||
state.pageNumber = state.pageNumber + 1;
|
||||
},
|
||||
decreasePage: (state) => {
|
||||
state.pageNumber = state.pageNumber - 1;
|
||||
},
|
||||
setPage: (state, action) => {
|
||||
state.pageNumber = action.payload;
|
||||
},
|
||||
setNewConvo: (state) => {
|
||||
state.error = false;
|
||||
state.title = 'ChatGPT Clone';
|
||||
state.conversationId = null;
|
||||
state.parentMessageId = null;
|
||||
state.jailbreakConversationId = null;
|
||||
state.conversationSignature = null;
|
||||
state.clientId = null;
|
||||
state.invocationId = null;
|
||||
state.toneStyle = null;
|
||||
state.chatGptLabel = null;
|
||||
state.promptPrefix = null;
|
||||
state.convosLoading = false;
|
||||
state.latestMessage = null;
|
||||
},
|
||||
setConvos: (state, action) => {
|
||||
const { convos, searchFetch } = action.payload;
|
||||
if (searchFetch) {
|
||||
state.convos = convos;
|
||||
} else {
|
||||
state.convos = convos.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
}
|
||||
|
||||
// state.convoMap = convos.reduce((acc, curr) => {
|
||||
// acc[curr.conversationId] = { ...curr };
|
||||
// delete acc[curr.conversationId].conversationId;
|
||||
// return acc;
|
||||
// }, {});
|
||||
|
||||
},
|
||||
setPages: (state, action) => {
|
||||
state.pages = action.payload;
|
||||
},
|
||||
removeConvo: (state, action) => {
|
||||
state.convos = state.convos.filter((convo) => convo.conversationId !== action.payload);
|
||||
},
|
||||
removeAll: (state) => {
|
||||
state.convos = [];
|
||||
},
|
||||
setLatestMessage: (state, action) => {
|
||||
state.latestMessage = action.payload;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
refreshConversation,
|
||||
setConversation,
|
||||
setPages,
|
||||
setConvos,
|
||||
setNewConvo,
|
||||
setError,
|
||||
increasePage,
|
||||
decreasePage,
|
||||
setPage,
|
||||
removeConvo,
|
||||
removeAll,
|
||||
setLatestMessage
|
||||
} = currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
|
|
@ -1,22 +1,15 @@
|
|||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import conversation from './conversation';
|
||||
import conversations from './conversations';
|
||||
import models from './models';
|
||||
import user from './user';
|
||||
import submission from './submission';
|
||||
import search from './search';
|
||||
|
||||
import convoReducer from './convoSlice.js';
|
||||
import messageReducer from './messageSlice.js';
|
||||
import modelReducer from './modelSlice.js';
|
||||
import submitReducer from './submitSlice.js';
|
||||
import textReducer from './textSlice.js';
|
||||
import userReducer from './userReducer.js';
|
||||
import searchReducer from './searchSlice.js';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
convo: convoReducer,
|
||||
messages: messageReducer,
|
||||
models: modelReducer,
|
||||
text: textReducer,
|
||||
submit: submitReducer,
|
||||
user: userReducer,
|
||||
search: searchReducer
|
||||
},
|
||||
devTools: true
|
||||
});
|
||||
export default {
|
||||
...conversation,
|
||||
...conversations,
|
||||
...models,
|
||||
...user,
|
||||
...submission,
|
||||
...search
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import buildTree from '~/utils/buildTree';
|
||||
|
||||
const initialState = {
|
||||
messages: [],
|
||||
messageTree: []
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'messages',
|
||||
initialState,
|
||||
reducers: {
|
||||
setMessages: (state, action) => {
|
||||
state.messages = action.payload;
|
||||
const groupAll = action.payload[0]?.searchResult;
|
||||
if (groupAll) console.log('grouping all messages');
|
||||
state.messageTree = buildTree(action.payload, groupAll);
|
||||
},
|
||||
setEmptyMessage: (state) => {
|
||||
state.messages = [
|
||||
{
|
||||
messageId: '1',
|
||||
conversationId: '1',
|
||||
parentMessageId: '1',
|
||||
sender: '',
|
||||
text: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export const { setMessages, setEmptyMessage } = currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
models: [
|
||||
{
|
||||
_id: '0',
|
||||
name: 'ChatGPT',
|
||||
value: 'chatgpt',
|
||||
model: 'chatgpt'
|
||||
},
|
||||
{
|
||||
_id: '1',
|
||||
name: 'CustomGPT',
|
||||
value: 'chatgptCustom',
|
||||
model: 'chatgptCustom'
|
||||
},
|
||||
{
|
||||
_id: '2',
|
||||
name: 'BingAI',
|
||||
value: 'bingai',
|
||||
model: 'bingai'
|
||||
},
|
||||
{
|
||||
_id: '3',
|
||||
name: 'Sydney',
|
||||
value: 'sydney',
|
||||
model: 'sydney'
|
||||
},
|
||||
{
|
||||
_id: '4',
|
||||
name: 'ChatGPT',
|
||||
value: 'chatgptBrowser',
|
||||
model: 'chatgptBrowser'
|
||||
},
|
||||
],
|
||||
modelMap: {},
|
||||
initial: { chatgpt: false, chatgptCustom: false, bingai: false, sydney: false, chatgptBrowser: false }
|
||||
// initial: { chatgpt: true, chatgptCustom: true, bingai: true, }
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'models',
|
||||
initialState,
|
||||
reducers: {
|
||||
setModels: (state, action) => {
|
||||
const models = [...initialState.models, ...action.payload];
|
||||
state.models = models;
|
||||
const modelMap = {};
|
||||
|
||||
models.slice(initialState.models.length).forEach((modelItem) => {
|
||||
modelMap[modelItem.value] = {
|
||||
chatGptLabel: modelItem.chatGptLabel,
|
||||
promptPrefix: modelItem.promptPrefix,
|
||||
model: 'chatgptCustom'
|
||||
};
|
||||
});
|
||||
|
||||
state.modelMap = modelMap;
|
||||
},
|
||||
setInitial: (state, action) => {
|
||||
state.initial = action.payload;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { setModels, setInitial } = currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
80
client/src/store/models.js
Normal file
80
client/src/store/models.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import React from "react";
|
||||
import {
|
||||
RecoilRoot,
|
||||
atom,
|
||||
selector,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
} from "recoil";
|
||||
|
||||
const customGPTModels = atom({
|
||||
key: "customGPTModels",
|
||||
default: [],
|
||||
});
|
||||
|
||||
const models = selector({
|
||||
key: "models",
|
||||
get: ({ get }) => {
|
||||
return [
|
||||
{
|
||||
_id: "0",
|
||||
name: "ChatGPT",
|
||||
value: "chatgpt",
|
||||
model: "chatgpt",
|
||||
},
|
||||
{
|
||||
_id: "1",
|
||||
name: "CustomGPT",
|
||||
value: "chatgptCustom",
|
||||
model: "chatgptCustom",
|
||||
},
|
||||
{
|
||||
_id: "2",
|
||||
name: "BingAI",
|
||||
value: "bingai",
|
||||
model: "bingai",
|
||||
},
|
||||
{
|
||||
_id: "3",
|
||||
name: "Sydney",
|
||||
value: "sydney",
|
||||
model: "sydney",
|
||||
},
|
||||
{
|
||||
_id: "4",
|
||||
name: "ChatGPT",
|
||||
value: "chatgptBrowser",
|
||||
model: "chatgptBrowser",
|
||||
},
|
||||
...get(customGPTModels),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
const modelsFilter = atom({
|
||||
key: "modelsFilter",
|
||||
default: {
|
||||
chatgpt: false,
|
||||
chatgptCustom: false,
|
||||
bingai: false,
|
||||
sydney: false,
|
||||
chatgptBrowser: false,
|
||||
},
|
||||
});
|
||||
|
||||
const availableModels = selector({
|
||||
key: "availableModels",
|
||||
get: ({ get }) => {
|
||||
const m = get(models);
|
||||
const f = get(modelsFilter);
|
||||
return m.filter(({ model }) => f[model]);
|
||||
},
|
||||
});
|
||||
// const modelAvailable
|
||||
|
||||
export default {
|
||||
customGPTModels,
|
||||
models,
|
||||
modelsFilter,
|
||||
availableModels,
|
||||
};
|
||||
25
client/src/store/search.js
Normal file
25
client/src/store/search.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { atom, selector } from 'recoil';
|
||||
|
||||
const isSearchEnabled = atom({
|
||||
key: 'isSearchEnabled',
|
||||
default: null
|
||||
});
|
||||
|
||||
const searchQuery = atom({
|
||||
key: 'searchQuery',
|
||||
default: ''
|
||||
});
|
||||
|
||||
const isSearching = selector({
|
||||
key: 'isSearching',
|
||||
get: ({ get }) => {
|
||||
const data = get(searchQuery);
|
||||
return !!data;
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
isSearchEnabled,
|
||||
isSearching,
|
||||
searchQuery
|
||||
};
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
searchEnabled: false,
|
||||
search: false,
|
||||
query: '',
|
||||
inputValue: '',
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'search',
|
||||
initialState,
|
||||
reducers: {
|
||||
setInputValue: (state, action) => {
|
||||
state.inputValue = action.payload;
|
||||
},
|
||||
setSearchState: (state, action) => {
|
||||
state.searchEnabled = action.payload;
|
||||
},
|
||||
setQuery: (state, action) => {
|
||||
const q = action.payload;
|
||||
state.query = q;
|
||||
|
||||
if (q === '') {
|
||||
state.search = false;
|
||||
} else if (q?.length > 0 && !state.search) {
|
||||
state.search = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export const { setInputValue, setSearchState, setQuery } = currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
37
client/src/store/submission.js
Normal file
37
client/src/store/submission.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
RecoilRoot,
|
||||
atom,
|
||||
selector,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
useSetRecoilState,
|
||||
} from "recoil";
|
||||
import buildTree from "~/utils/buildTree";
|
||||
|
||||
// current submission
|
||||
// submit any new value to this state will cause new message to be send.
|
||||
// set to null to give up any submission
|
||||
// {
|
||||
// conversation, // target submission, must have: model, chatGptLabel, promptPrefix
|
||||
// messages, // old messages
|
||||
// message, // request message
|
||||
// initialResponse, // response message
|
||||
// isRegenerate=false, // isRegenerate?
|
||||
// }
|
||||
|
||||
const submission = atom({
|
||||
key: "submission",
|
||||
default: null,
|
||||
});
|
||||
|
||||
const isSubmitting = atom({
|
||||
key: "isSubmitting",
|
||||
default: false,
|
||||
});
|
||||
|
||||
export default {
|
||||
submission,
|
||||
isSubmitting,
|
||||
};
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
isSubmitting: false,
|
||||
submission: {},
|
||||
stopStream: false,
|
||||
disabled: true,
|
||||
model: 'chatgpt',
|
||||
promptPrefix: null,
|
||||
chatGptLabel: null,
|
||||
customModel: null,
|
||||
cursor: true,
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'submit',
|
||||
initialState,
|
||||
reducers: {
|
||||
setSubmitState: (state, action) => {
|
||||
state.isSubmitting = action.payload;
|
||||
},
|
||||
setSubmission: (state, action) => {
|
||||
state.submission = action.payload;
|
||||
if (Object.keys(action.payload).length === 0) {
|
||||
state.isSubmitting = false;
|
||||
}
|
||||
},
|
||||
setStopStream: (state, action) => {
|
||||
state.stopStream = action.payload;
|
||||
},
|
||||
setDisabled: (state, action) => {
|
||||
state.disabled = action.payload;
|
||||
},
|
||||
setModel: (state, action) => {
|
||||
state.model = action.payload;
|
||||
},
|
||||
toggleCursor: (state, action) => {
|
||||
if (action.payload) {
|
||||
state.cursor = action.payload;
|
||||
} else {
|
||||
state.cursor = !state.cursor;
|
||||
}
|
||||
},
|
||||
setCustomGpt: (state, action) => {
|
||||
state.promptPrefix = action.payload.promptPrefix;
|
||||
state.chatGptLabel = action.payload.chatGptLabel;
|
||||
},
|
||||
setCustomModel: (state, action) => {
|
||||
state.customModel = action.payload;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { toggleCursor, setSubmitState, setSubmission, setStopStream, setDisabled, setModel, setCustomGpt, setCustomModel } =
|
||||
currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
text: '',
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'text',
|
||||
initialState,
|
||||
reducers: {
|
||||
setText: (state, action) => {
|
||||
state.text = action.payload;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export const { setText } = currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
17
client/src/store/user.js
Normal file
17
client/src/store/user.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import {
|
||||
RecoilRoot,
|
||||
atom,
|
||||
selector,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
} from "recoil";
|
||||
|
||||
const user = atom({
|
||||
key: "user",
|
||||
default: null,
|
||||
});
|
||||
|
||||
export default {
|
||||
user,
|
||||
};
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
user: null,
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState,
|
||||
reducers: {
|
||||
setUser: (state, action) => {
|
||||
state.user = action.payload;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export const { setUser } = currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
Loading…
Add table
Add a link
Reference in a new issue