mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
Merge branch 'master' into feat-model-based-on-key
This commit is contained in:
commit
1fe9e29187
27 changed files with 602 additions and 157 deletions
63
README.md
63
README.md
|
|
@ -8,6 +8,18 @@ https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5
|
||||||
|
|
||||||
## Updates
|
## Updates
|
||||||
<details open>
|
<details open>
|
||||||
|
<summary><strong>2023-03-16</strong></summary>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[Latest release (v0.0.4)](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.0.4) includes Resubmitting messages & Branching messages, which mirrors official ChatGPT feature of editing a sent message, that then branches the conversation into separate message paths (works only with ChatGPT)
|
||||||
|
|
||||||
|
Full details and [example here](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.0.4). Message search is on the docket
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<details>
|
||||||
<summary><strong>2023-03-12</strong></summary>
|
<summary><strong>2023-03-12</strong></summary>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22,8 +34,6 @@ Many improvements across the board, the biggest is being able to start conversat
|
||||||
|
|
||||||
Adding support for conversation search is next! Thank you [mysticaltech](https://github.com/mysticaltech) for bringing up a method I can use for this.
|
Adding support for conversation search is next! Thank you [mysticaltech](https://github.com/mysticaltech) for bringing up a method I can use for this.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>2023-03-09</strong></summary>
|
<summary><strong>2023-03-09</strong></summary>
|
||||||
Released v.0.0.2
|
Released v.0.0.2
|
||||||
|
|
@ -84,6 +94,7 @@ Currently, this project is only functional with the `text-davinci-003` model.
|
||||||
- [Docker](#docker)
|
- [Docker](#docker)
|
||||||
- [Access Tokens](#access-tokens)
|
- [Access Tokens](#access-tokens)
|
||||||
- [Proxy](#proxy)
|
- [Proxy](#proxy)
|
||||||
|
- [User System](#user-system)
|
||||||
- [Updating](#updating)
|
- [Updating](#updating)
|
||||||
- [Use Cases](#use-cases)
|
- [Use Cases](#use-cases)
|
||||||
- [Origin](#origin)
|
- [Origin](#origin)
|
||||||
|
|
@ -111,12 +122,11 @@ Here are my recently completed and planned features:
|
||||||
- [x] Server convo pagination (limit fetch and load more with 'show more' button)
|
- [x] Server convo pagination (limit fetch and load more with 'show more' button)
|
||||||
- [x] Config file for easy startup (docker compose)
|
- [x] Config file for easy startup (docker compose)
|
||||||
- [x] Mobile styling (thanks to [wtlyu](https://github.com/wtlyu))
|
- [x] Mobile styling (thanks to [wtlyu](https://github.com/wtlyu))
|
||||||
- [ ] Bing AI Styling (for suggested responses, convo end, etc.) - **In progress**
|
- [x] Resubmit/edit sent messages (thanks to [wtlyu](https://github.com/wtlyu))
|
||||||
|
- [ ] Message Search
|
||||||
|
- [ ] Bing AI Styling (params, suggested responses, convo end, etc.) - **In progress**
|
||||||
- [ ] Add warning before clearing convos
|
- [ ] Add warning before clearing convos
|
||||||
- [ ] Build test suite for CI/CD
|
- [ ] Build test suite for CI/CD
|
||||||
- [ ] Conversation Search (by title)
|
|
||||||
- [ ] Resubmit/edit sent messages
|
|
||||||
- [ ] Semantic Search Option (requires more tokens)
|
|
||||||
- [ ] Prompt Templates/Search
|
- [ ] Prompt Templates/Search
|
||||||
- [ ] Refactor/clean up code (tech debt)
|
- [ ] Refactor/clean up code (tech debt)
|
||||||
- [ ] Optional use of local storage for credentials
|
- [ ] Optional use of local storage for credentials
|
||||||
|
|
@ -169,16 +179,7 @@ By default, only local machine can access this server. To share within network o
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
- **Provide** all credentials, (API keys, access tokens, and Mongo Connection String) in [docker-compose.yml](docker-compose.yml) under api service
|
- **Provide** all credentials, (API keys, access tokens, and Mongo Connection String) in [docker-compose.yml](docker-compose.yml) under api service
|
||||||
- **Build images** in both /api/ and /client/ directories (will eventually share through docker hub)
|
- **Run** `docker-compose up` to start the app
|
||||||
- `api/`
|
|
||||||
```bash
|
|
||||||
docker build -t node-api .
|
|
||||||
```
|
|
||||||
- `client/`
|
|
||||||
```bash
|
|
||||||
docker build -t react-client .
|
|
||||||
```
|
|
||||||
- **Run** `docker-compose build` in project root dir and then `docker-compose up` to start the app
|
|
||||||
|
|
||||||
### Access Tokens
|
### Access Tokens
|
||||||
|
|
||||||
|
|
@ -235,6 +236,36 @@ set in docker-compose.yml file, under services - api - environment
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
### User System
|
||||||
|
|
||||||
|
By default, there is no user system enabled, so anyone can access your server.
|
||||||
|
|
||||||
|
**This project is not designed to provide a complete and full-featured user system.** It's not high priority task and might never be provided.
|
||||||
|
|
||||||
|
[wtlyu](https://github.com/wtlyu) provide a sample user system structure, that you can implement your own user system. It's simple and not a ready-for-use edition.
|
||||||
|
|
||||||
|
(If you want to implement your user system, open this ↓)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Implement your own user system </strong></summary>
|
||||||
|
|
||||||
|
To enable the user system, set `ENABLE_USER_SYSTEM=1` in your `.env` file.
|
||||||
|
|
||||||
|
The sample structure is simple. It provide three basic endpoint:
|
||||||
|
|
||||||
|
1. `/auth/login` will redirect to your own login url. In the sample code, it's `/auth/your_login_page`.
|
||||||
|
2. `/auth/logout` will redirect to your own logout url. In the sample code, it's `/auth/your_login_page/logout`.
|
||||||
|
3. `/api/me` will return the userinfo: `{ username, display }`.
|
||||||
|
1. `username` will be used in db, used to distinguish between users.
|
||||||
|
2. `display` will be displayed in UI.
|
||||||
|
|
||||||
|
The only one thing that drive user system work is `req.session.user`. Once it's set, the client will be trusted. Set to `null` if logout.
|
||||||
|
|
||||||
|
Please refer to `/api/server/routes/authYoutLogin.js` file. It's very clear and simple to tell you how to implement your user system.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
### Updating
|
### Updating
|
||||||
- As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data.
|
- As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,10 @@ MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone"
|
||||||
OPENAI_KEY=
|
OPENAI_KEY=
|
||||||
CHATGPT_TOKEN=
|
CHATGPT_TOKEN=
|
||||||
BING_TOKEN=
|
BING_TOKEN=
|
||||||
|
|
||||||
|
# User System
|
||||||
|
|
||||||
|
# global enable/disable the sample user system.
|
||||||
|
# this is not a ready to use user system.
|
||||||
|
# dont't use it, unless you can write your own code.
|
||||||
|
ENABLE_USER_SYSTEM=
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ const convoSchema = mongoose.Schema(
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
user: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
suggestions: [{ type: String }],
|
suggestions: [{ type: String }],
|
||||||
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }]
|
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }]
|
||||||
},
|
},
|
||||||
|
|
@ -52,9 +55,9 @@ const convoSchema = mongoose.Schema(
|
||||||
const Conversation =
|
const Conversation =
|
||||||
mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
|
mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
|
||||||
|
|
||||||
const getConvo = async (conversationId) => {
|
const getConvo = async (user, conversationId) => {
|
||||||
try {
|
try {
|
||||||
return await Conversation.findOne({ conversationId }).exec();
|
return await Conversation.findOne({ user, conversationId }).exec();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return { message: 'Error getting single conversation' };
|
return { message: 'Error getting single conversation' };
|
||||||
|
|
@ -62,12 +65,13 @@ const getConvo = async (conversationId) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
saveConvo: async ({ conversationId, newConversationId, title, ...convo }) => {
|
saveConvo: async (user, { conversationId, newConversationId, title, ...convo }) => {
|
||||||
try {
|
try {
|
||||||
const messages = await getMessages({ conversationId });
|
const messages = await getMessages({ conversationId });
|
||||||
const update = { ...convo, messages };
|
const update = { ...convo, messages };
|
||||||
if (title) {
|
if (title) {
|
||||||
update.title = title;
|
update.title = title;
|
||||||
|
update.user = user
|
||||||
}
|
}
|
||||||
if (newConversationId) {
|
if (newConversationId) {
|
||||||
update.conversationId = newConversationId;
|
update.conversationId = newConversationId;
|
||||||
|
|
@ -82,7 +86,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Conversation.findOneAndUpdate(
|
return await Conversation.findOneAndUpdate(
|
||||||
{ conversationId },
|
{ conversationId: conversationId, user },
|
||||||
{ $set: update },
|
{ $set: update },
|
||||||
{ new: true, upsert: true }
|
{ new: true, upsert: true }
|
||||||
).exec();
|
).exec();
|
||||||
|
|
@ -91,9 +95,9 @@ module.exports = {
|
||||||
return { message: 'Error saving conversation' };
|
return { message: 'Error saving conversation' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateConvo: async ({ conversationId, ...update }) => {
|
updateConvo: async (user, { conversationId, ...update }) => {
|
||||||
try {
|
try {
|
||||||
return await Conversation.findOneAndUpdate({ conversationId }, update, {
|
return await Conversation.findOneAndUpdate({ conversationId: conversationId, user }, update, {
|
||||||
new: true
|
new: true
|
||||||
}).exec();
|
}).exec();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -101,12 +105,11 @@ module.exports = {
|
||||||
return { message: 'Error updating conversation' };
|
return { message: 'Error updating conversation' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(),
|
getConvosByPage: async (user, pageNumber = 1, pageSize = 12) => {
|
||||||
getConvosByPage: async (pageNumber = 1, pageSize = 12) => {
|
|
||||||
try {
|
try {
|
||||||
const totalConvos = (await Conversation.countDocuments()) || 1;
|
const totalConvos = (await Conversation.countDocuments({ user })) || 1;
|
||||||
const totalPages = Math.ceil(totalConvos / pageSize);
|
const totalPages = Math.ceil(totalConvos / pageSize);
|
||||||
const convos = await Conversation.find()
|
const convos = await Conversation.find({ user })
|
||||||
.sort({ createdAt: -1, created: -1 })
|
.sort({ createdAt: -1, created: -1 })
|
||||||
.skip((pageNumber - 1) * pageSize)
|
.skip((pageNumber - 1) * pageSize)
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
|
|
@ -119,17 +122,17 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getConvo,
|
getConvo,
|
||||||
getConvoTitle: async (conversationId) => {
|
getConvoTitle: async (user, conversationId) => {
|
||||||
try {
|
try {
|
||||||
const convo = await getConvo(conversationId);
|
const convo = await getConvo(user, conversationId);
|
||||||
return convo.title;
|
return convo.title;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return { message: 'Error getting conversation title' };
|
return { message: 'Error getting conversation title' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteConvos: async (filter) => {
|
deleteConvos: async (user, filter) => {
|
||||||
let deleteCount = await Conversation.deleteMany(filter).exec();
|
let deleteCount = await Conversation.deleteMany({...filter, user}).exec();
|
||||||
deleteCount.messages = await deleteMessages(filter);
|
deleteCount.messages = await deleteMessages(filter);
|
||||||
return deleteCount;
|
return deleteCount;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,20 @@ const customGptSchema = mongoose.Schema({
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
user: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
}, { timestamps: true });
|
}, { timestamps: true });
|
||||||
|
|
||||||
const CustomGpt = mongoose.models.CustomGpt || mongoose.model('CustomGpt', customGptSchema);
|
const CustomGpt = mongoose.models.CustomGpt || mongoose.model('CustomGpt', customGptSchema);
|
||||||
|
|
||||||
const createCustomGpt = async ({ chatGptLabel, promptPrefix, value }) => {
|
const createCustomGpt = async ({ chatGptLabel, promptPrefix, value, user }) => {
|
||||||
try {
|
try {
|
||||||
await CustomGpt.create({
|
await CustomGpt.create({
|
||||||
chatGptLabel,
|
chatGptLabel,
|
||||||
promptPrefix,
|
promptPrefix,
|
||||||
value
|
value,
|
||||||
|
user
|
||||||
});
|
});
|
||||||
return { chatGptLabel, promptPrefix, value };
|
return { chatGptLabel, promptPrefix, value };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -31,22 +35,22 @@ const createCustomGpt = async ({ chatGptLabel, promptPrefix, value }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getCustomGpts: async (filter) => {
|
getCustomGpts: async (user, filter) => {
|
||||||
try {
|
try {
|
||||||
return await CustomGpt.find(filter).exec();
|
return await CustomGpt.find({ ...filter, user }).exec();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return { customGpt: 'Error getting customGpts' };
|
return { customGpt: 'Error getting customGpts' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateCustomGpt: async ({ value, ...update }) => {
|
updateCustomGpt: async (user, { value, ...update }) => {
|
||||||
try {
|
try {
|
||||||
const customGpt = await CustomGpt.findOne({ value }).exec();
|
const customGpt = await CustomGpt.findOne({ value, user }).exec();
|
||||||
|
|
||||||
if (!customGpt) {
|
if (!customGpt) {
|
||||||
return await createCustomGpt({ value, ...update });
|
return await createCustomGpt({ value, ...update, user });
|
||||||
} else {
|
} else {
|
||||||
return await CustomGpt.findOneAndUpdate({ value }, update, {
|
return await CustomGpt.findOneAndUpdate({ value, user }, update, {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
}).exec();
|
}).exec();
|
||||||
|
|
@ -56,9 +60,9 @@ module.exports = {
|
||||||
return { message: 'Error updating customGpt' };
|
return { message: 'Error updating customGpt' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateByLabel: async ({ prevLabel, ...update }) => {
|
updateByLabel: async (user, { prevLabel, ...update }) => {
|
||||||
try {
|
try {
|
||||||
return await CustomGpt.findOneAndUpdate({ chatGptLabel: prevLabel }, update, {
|
return await CustomGpt.findOneAndUpdate({ chatGptLabel: prevLabel, user }, update, {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true
|
upsert: true
|
||||||
}).exec();
|
}).exec();
|
||||||
|
|
@ -67,9 +71,9 @@ module.exports = {
|
||||||
return { message: 'Error updating customGpt' };
|
return { message: 'Error updating customGpt' };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteCustomGpts: async (filter) => {
|
deleteCustomGpts: async (user, filter) => {
|
||||||
try {
|
try {
|
||||||
return await CustomGpt.deleteMany(filter).exec();
|
return await CustomGpt.deleteMany({ ...filter, user }).exec();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return { customGpt: 'Error deleting customGpts' };
|
return { customGpt: 'Error deleting customGpts' };
|
||||||
|
|
|
||||||
169
api/package-lock.json
generated
169
api/package-lock.json
generated
|
|
@ -12,9 +12,11 @@
|
||||||
"@keyv/mongo": "^2.1.8",
|
"@keyv/mongo": "^2.1.8",
|
||||||
"@vscode/vscode-languagedetection": "^1.0.22",
|
"@vscode/vscode-languagedetection": "^1.0.22",
|
||||||
"@waylaidwanderer/chatgpt-api": "^1.28.2",
|
"@waylaidwanderer/chatgpt-api": "^1.28.2",
|
||||||
|
"axios": "^1.3.4",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-session": "^1.17.3",
|
||||||
"keyv": "^4.5.2",
|
"keyv": "^4.5.2",
|
||||||
"keyv-file": "^0.2.0",
|
"keyv-file": "^0.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|
@ -1776,11 +1778,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "0.26.1",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.14.8"
|
"follow-redirects": "^1.15.0",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
|
|
@ -2452,6 +2456,45 @@
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-session": {
|
||||||
|
"version": "1.17.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
|
||||||
|
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.4.2",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~2.0.0",
|
||||||
|
"on-headers": "~1.0.2",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"uid-safe": "~2.1.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session/node_modules/cookie": {
|
||||||
|
"version": "0.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||||
|
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session/node_modules/debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-session/node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/debug": {
|
"node_modules/express/node_modules/debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
|
@ -3507,6 +3550,14 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/on-headers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/onetime": {
|
"node_modules/onetime": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||||
|
|
@ -3530,6 +3581,14 @@
|
||||||
"form-data": "^4.0.0"
|
"form-data": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openai/node_modules/axios": {
|
||||||
|
"version": "0.26.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||||
|
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.14.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ora": {
|
"node_modules/ora": {
|
||||||
"version": "6.1.2",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz",
|
||||||
|
|
@ -3738,6 +3797,11 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
|
},
|
||||||
"node_modules/pstree.remy": {
|
"node_modules/pstree.remy": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||||
|
|
@ -3771,6 +3835,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
|
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/random-bytes": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
|
@ -4353,6 +4425,17 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uid-safe": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||||
|
"dependencies": {
|
||||||
|
"random-bytes": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undefsafe": {
|
"node_modules/undefsafe": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||||
|
|
@ -6152,11 +6235,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "0.26.1",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.14.8"
|
"follow-redirects": "^1.15.0",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
|
|
@ -6628,6 +6713,41 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"express-session": {
|
||||||
|
"version": "1.17.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
|
||||||
|
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.4.2",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~2.0.0",
|
||||||
|
"on-headers": "~1.0.2",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"uid-safe": "~2.1.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||||
|
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"external-editor": {
|
"external-editor": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||||
|
|
@ -7397,6 +7517,11 @@
|
||||||
"ee-first": "1.1.1"
|
"ee-first": "1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"on-headers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
|
||||||
|
},
|
||||||
"onetime": {
|
"onetime": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||||
|
|
@ -7412,6 +7537,16 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.26.0",
|
||||||
"form-data": "^4.0.0"
|
"form-data": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": {
|
||||||
|
"version": "0.26.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||||
|
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.14.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ora": {
|
"ora": {
|
||||||
|
|
@ -7569,6 +7704,11 @@
|
||||||
"ipaddr.js": "1.9.1"
|
"ipaddr.js": "1.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
|
},
|
||||||
"pstree.remy": {
|
"pstree.remy": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||||
|
|
@ -7593,6 +7733,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
|
||||||
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
|
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
|
||||||
},
|
},
|
||||||
|
"random-bytes": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="
|
||||||
|
},
|
||||||
"range-parser": {
|
"range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
|
@ -8033,6 +8178,14 @@
|
||||||
"mime-types": "~2.1.24"
|
"mime-types": "~2.1.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"uid-safe": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||||
|
"requires": {
|
||||||
|
"random-bytes": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"undefsafe": {
|
"undefsafe": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@
|
||||||
"@keyv/mongo": "^2.1.8",
|
"@keyv/mongo": "^2.1.8",
|
||||||
"@vscode/vscode-languagedetection": "^1.0.22",
|
"@vscode/vscode-languagedetection": "^1.0.22",
|
||||||
"@waylaidwanderer/chatgpt-api": "^1.28.2",
|
"@waylaidwanderer/chatgpt-api": "^1.28.2",
|
||||||
|
"axios": "^1.3.4",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"express-session": "^1.17.3",
|
||||||
"keyv": "^4.5.2",
|
"keyv": "^4.5.2",
|
||||||
"keyv-file": "^0.2.0",
|
"keyv-file": "^0.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const session = require('express-session')
|
||||||
const dbConnect = require('../models/dbConnect');
|
const dbConnect = require('../models/dbConnect');
|
||||||
const { migrateDb } = require('../models');
|
const { migrateDb } = require('../models');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
@ -7,6 +8,7 @@ const routes = require('./routes');
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 3080;
|
const port = process.env.PORT || 3080;
|
||||||
const host = process.env.HOST || 'localhost'
|
const host = process.env.HOST || 'localhost'
|
||||||
|
const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
|
||||||
const projectPath = path.join(__dirname, '..', '..', 'client');
|
const projectPath = path.join(__dirname, '..', '..', 'client');
|
||||||
dbConnect().then(() => {
|
dbConnect().then(() => {
|
||||||
console.log('Connected to MongoDB');
|
console.log('Connected to MongoDB');
|
||||||
|
|
@ -16,17 +18,38 @@ dbConnect().then(() => {
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.static(path.join(projectPath, 'public')));
|
app.use(express.static(path.join(projectPath, 'public')));
|
||||||
|
app.set('trust proxy', 1) // trust first proxy
|
||||||
|
app.use(session({
|
||||||
|
secret: 'chatgpt-clone-random-secrect',
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
}))
|
||||||
|
|
||||||
app.get('/', function (req, res) {
|
app.get('/', routes.authenticatedOrRedirect, function (req, res) {
|
||||||
console.log(path.join(projectPath, 'public', 'index.html'));
|
console.log(path.join(projectPath, 'public', 'index.html'));
|
||||||
res.sendFile(path.join(projectPath, 'public', 'index.html'));
|
res.sendFile(path.join(projectPath, 'public', 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/api/ask', routes.ask);
|
app.get('/api/me', function (req, res) {
|
||||||
app.use('/api/messages', routes.messages);
|
if (userSystemEnabled) {
|
||||||
app.use('/api/convos', routes.convos);
|
const user = req?.session?.user
|
||||||
app.use('/api/customGpts', routes.customGpts);
|
|
||||||
app.use('/api/prompts', routes.prompts);
|
if (user)
|
||||||
|
res.send(JSON.stringify({username: user?.username, display: user?.display}));
|
||||||
|
else
|
||||||
|
res.send(JSON.stringify(null));
|
||||||
|
} else {
|
||||||
|
res.send(JSON.stringify({username: 'anonymous_user', display: 'Anonymous User'}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use('/api/ask', routes.authenticatedOr401, routes.ask);
|
||||||
|
app.use('/api/messages', routes.authenticatedOr401, routes.messages);
|
||||||
|
app.use('/api/convos', routes.authenticatedOr401, routes.convos);
|
||||||
|
app.use('/api/customGpts', routes.authenticatedOr401, routes.customGpts);
|
||||||
|
app.use('/api/prompts', routes.authenticatedOr401, routes.prompts);
|
||||||
|
app.use('/auth', routes.auth);
|
||||||
|
|
||||||
|
|
||||||
app.get('/api/models', function (req, res) {
|
app.get('/api/models', function (req, res) {
|
||||||
const hasOpenAI = !!process.env.OPENAI_KEY;
|
const hasOpenAI = !!process.env.OPENAI_KEY;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ router.post('/', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await saveMessage(userMessage);
|
await saveMessage(userMessage);
|
||||||
await saveConvo({ ...userMessage, model, ...convo });
|
await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
|
||||||
|
|
||||||
return await ask({
|
return await ask({
|
||||||
userMessage,
|
userMessage,
|
||||||
|
|
@ -176,9 +176,9 @@ const ask = async ({
|
||||||
gptResponse.parentMessageId = overrideParentMessageId || userMessageId;
|
gptResponse.parentMessageId = overrideParentMessageId || userMessageId;
|
||||||
|
|
||||||
await saveMessage(gptResponse);
|
await saveMessage(gptResponse);
|
||||||
await saveConvo(gptResponse);
|
await saveConvo(req?.session?.user?.username, gptResponse);
|
||||||
sendMessage(res, {
|
sendMessage(res, {
|
||||||
title: await getConvoTitle(conversationId),
|
title: await getConvoTitle(req?.session?.user?.username, conversationId),
|
||||||
final: true,
|
final: true,
|
||||||
requestMessage: userMessage,
|
requestMessage: userMessage,
|
||||||
responseMessage: gptResponse
|
responseMessage: gptResponse
|
||||||
|
|
@ -188,10 +188,13 @@ const ask = async ({
|
||||||
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||||
const title = await titleConvo({ model, text, response: gptResponse });
|
const title = await titleConvo({ model, text, response: gptResponse });
|
||||||
|
|
||||||
await saveConvo({
|
await saveConvo(
|
||||||
conversationId,
|
req?.session?.user?.username,
|
||||||
title
|
{
|
||||||
});
|
conversationId,
|
||||||
|
title
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ router.post('/', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await saveMessage(userMessage);
|
await saveMessage(userMessage);
|
||||||
await saveConvo({ ...userMessage, model, ...convo });
|
await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
|
||||||
|
|
||||||
return await ask({
|
return await ask({
|
||||||
isNewConversation,
|
isNewConversation,
|
||||||
|
|
@ -108,10 +108,13 @@ const ask = async ({
|
||||||
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
|
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
|
||||||
// but in this situation, don't change the conversationId, but create new convo.
|
// but in this situation, don't change the conversationId, but create new convo.
|
||||||
if (conversationId != userMessage.conversationId && isNewConversation)
|
if (conversationId != userMessage.conversationId && isNewConversation)
|
||||||
await saveConvo({
|
await saveConvo(
|
||||||
conversationId: conversationId,
|
req?.session?.user?.username,
|
||||||
newConversationId: userMessage.conversationId
|
{
|
||||||
});
|
conversationId: conversationId,
|
||||||
|
newConversationId: userMessage.conversationId
|
||||||
|
}
|
||||||
|
);
|
||||||
conversationId = userMessage.conversationId;
|
conversationId = userMessage.conversationId;
|
||||||
|
|
||||||
response.text = response.response;
|
response.text = response.response;
|
||||||
|
|
@ -129,9 +132,10 @@ const ask = async ({
|
||||||
|
|
||||||
response.text = await handleText(response, true);
|
response.text = await handleText(response, true);
|
||||||
await saveMessage(response);
|
await saveMessage(response);
|
||||||
await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
|
await saveConvo(req?.session?.user?.username, { ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
|
||||||
|
|
||||||
sendMessage(res, {
|
sendMessage(res, {
|
||||||
title: await getConvoTitle(conversationId),
|
title: await getConvoTitle(req?.session?.user?.username, conversationId),
|
||||||
final: true,
|
final: true,
|
||||||
requestMessage: userMessage,
|
requestMessage: userMessage,
|
||||||
responseMessage: response
|
responseMessage: response
|
||||||
|
|
@ -141,10 +145,13 @@ const ask = async ({
|
||||||
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||||
const title = await titleConvo({ model, text, response });
|
const title = await titleConvo({ model, text, response });
|
||||||
|
|
||||||
await saveConvo({
|
await saveConvo(
|
||||||
conversationId,
|
req?.session?.user?.username,
|
||||||
title
|
{
|
||||||
});
|
conversationId,
|
||||||
|
title
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ router.post('/', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await saveMessage(userMessage);
|
await saveMessage(userMessage);
|
||||||
await saveConvo({ ...userMessage, model, ...convo });
|
await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
|
||||||
|
|
||||||
return await ask({
|
return await ask({
|
||||||
isNewConversation,
|
isNewConversation,
|
||||||
|
|
@ -132,18 +132,22 @@ const ask = async ({
|
||||||
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
|
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
|
||||||
// but in this situation, don't change the conversationId, but create new convo.
|
// but in this situation, don't change the conversationId, but create new convo.
|
||||||
if (conversationId != userMessage.conversationId && isNewConversation)
|
if (conversationId != userMessage.conversationId && isNewConversation)
|
||||||
await saveConvo({
|
await saveConvo(
|
||||||
conversationId: conversationId,
|
req?.session?.user?.username,
|
||||||
newConversationId: userMessage.conversationId
|
{
|
||||||
});
|
conversationId: conversationId,
|
||||||
|
newConversationId: userMessage.conversationId
|
||||||
|
}
|
||||||
|
);
|
||||||
conversationId = userMessage.conversationId;
|
conversationId = userMessage.conversationId;
|
||||||
|
|
||||||
response.text = await handleText(response, true);
|
response.text = await handleText(response, true);
|
||||||
// Save sydney response & convo, then send
|
// Save sydney response & convo, then send
|
||||||
await saveMessage(response);
|
await saveMessage(response);
|
||||||
await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
|
await saveConvo(req?.session?.user?.username, { ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
|
||||||
|
|
||||||
sendMessage(res, {
|
sendMessage(res, {
|
||||||
title: await getConvoTitle(conversationId),
|
title: await getConvoTitle(req?.session?.user?.username, conversationId),
|
||||||
final: true,
|
final: true,
|
||||||
requestMessage: userMessage,
|
requestMessage: userMessage,
|
||||||
responseMessage: response
|
responseMessage: response
|
||||||
|
|
@ -153,10 +157,13 @@ const ask = async ({
|
||||||
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||||
const title = await titleConvo({ model, text, response });
|
const title = await titleConvo({ model, text, response });
|
||||||
|
|
||||||
await saveConvo({
|
await saveConvo(
|
||||||
conversationId,
|
req?.session?.user?.username,
|
||||||
title
|
{
|
||||||
});
|
conversationId,
|
||||||
|
title
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
|
||||||
46
api/server/routes/auth.js
Normal file
46
api/server/routes/auth.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const authYourLogin = require('./authYourLogin');
|
||||||
|
const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
|
||||||
|
|
||||||
|
router.get('/login', function (req, res) {
|
||||||
|
if (userSystemEnabled)
|
||||||
|
res.redirect('/auth/your_login_page')
|
||||||
|
else
|
||||||
|
res.redirect('/')
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/logout', function (req, res) {
|
||||||
|
// clear the session
|
||||||
|
req.session.user = null
|
||||||
|
|
||||||
|
req.session.save(function (error) {
|
||||||
|
if (userSystemEnabled)
|
||||||
|
res.redirect('/auth/your_login_page/logout')
|
||||||
|
else
|
||||||
|
res.redirect('/')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const authenticatedOr401 = (req, res, next) => {
|
||||||
|
if (userSystemEnabled) {
|
||||||
|
const user = req?.session?.user;
|
||||||
|
|
||||||
|
if (user) next();
|
||||||
|
else res.status(401).end();
|
||||||
|
} else next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const authenticatedOrRedirect = (req, res, next) => {
|
||||||
|
if (userSystemEnabled) {
|
||||||
|
const user = req?.session?.user;
|
||||||
|
|
||||||
|
if (user) next();
|
||||||
|
else res.redirect('/auth/login').end();
|
||||||
|
} else next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userSystemEnabled)
|
||||||
|
router.use('/your_login_page', authYourLogin);
|
||||||
|
|
||||||
|
module.exports = { router, authenticatedOr401, authenticatedOrRedirect };
|
||||||
40
api/server/routes/authYourLogin.js
Normal file
40
api/server/routes/authYourLogin.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// WARNING!
|
||||||
|
// THIS IS NOT A READY TO USE USER SYSTEM
|
||||||
|
// PLEASE IMPLEMENT YOUR OWN USER SYSTEM
|
||||||
|
|
||||||
|
const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
router.get('/logout', (req, res) => {
|
||||||
|
// Do anything you want
|
||||||
|
console.warn('logout not implemented!')
|
||||||
|
|
||||||
|
// finish
|
||||||
|
res.redirect('/')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
// Do anything you want
|
||||||
|
console.warn('login not implemented! Automatic passed as sample user')
|
||||||
|
|
||||||
|
// save the user info into session
|
||||||
|
// username will be used in db
|
||||||
|
// display will be used in UI
|
||||||
|
req.session.user = {
|
||||||
|
username: 'sample_user',
|
||||||
|
display: 'Sample User',
|
||||||
|
}
|
||||||
|
|
||||||
|
req.session.save(function (error) {
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.send(`<h1>Login Failed. An error occurred. Please see the server logs for details.</h1>`);
|
||||||
|
} else res.redirect('/')
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -1,10 +1,39 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const { titleConvo } = require('../../app/');
|
||||||
|
const { getConvo, saveConvo, getConvoTitle } = require('../../models');
|
||||||
const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation');
|
const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation');
|
||||||
|
const { getMessages } = require('../../models/Message');
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
const pageNumber = req.query.pageNumber || 1;
|
const pageNumber = req.query.pageNumber || 1;
|
||||||
res.status(200).send(await getConvosByPage(pageNumber));
|
res.status(200).send(await getConvosByPage(req?.session?.user?.username, pageNumber));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/gen_title', async (req, res) => {
|
||||||
|
const { conversationId } = req.body.arg;
|
||||||
|
|
||||||
|
const convo = await getConvo(req?.session?.user?.username, conversationId)
|
||||||
|
const firstMessage = (await getMessages({ conversationId }))[0]
|
||||||
|
const secondMessage = (await getMessages({ conversationId }))[1]
|
||||||
|
|
||||||
|
const title = convo.jailbreakConversationId
|
||||||
|
? await getConvoTitle(req?.session?.user?.username, conversationId)
|
||||||
|
: await titleConvo({
|
||||||
|
model: convo?.model,
|
||||||
|
message: firstMessage?.text,
|
||||||
|
response: JSON.stringify(secondMessage?.text || '')
|
||||||
|
});
|
||||||
|
|
||||||
|
await saveConvo(
|
||||||
|
req?.session?.user?.username,
|
||||||
|
{
|
||||||
|
conversationId,
|
||||||
|
title
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).send(title);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/clear', async (req, res) => {
|
router.post('/clear', async (req, res) => {
|
||||||
|
|
@ -15,7 +44,7 @@ router.post('/clear', async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbResponse = await deleteConvos(filter);
|
const dbResponse = await deleteConvos(req?.session?.user?.username, filter);
|
||||||
res.status(201).send(dbResponse);
|
res.status(201).send(dbResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
@ -27,7 +56,7 @@ router.post('/update', async (req, res) => {
|
||||||
const update = req.body.arg;
|
const update = req.body.arg;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbResponse = await updateConvo(update);
|
const dbResponse = await updateConvo(req?.session?.user?.username, update);
|
||||||
res.status(201).send(dbResponse);
|
res.status(201).send(dbResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const {
|
||||||
} = require('../../models');
|
} = require('../../models');
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
const models = (await getCustomGpts()).map((model) => {
|
const models = (await getCustomGpts(req?.session?.user?.username)).map((model) => {
|
||||||
model = model.toObject();
|
model = model.toObject();
|
||||||
model._id = model._id.toString();
|
model._id = model._id.toString();
|
||||||
return model;
|
return model;
|
||||||
|
|
@ -20,8 +20,8 @@ router.post('/delete', async (req, res) => {
|
||||||
const { arg } = req.body;
|
const { arg } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteCustomGpts(arg);
|
await deleteCustomGpts(req?.session?.user?.username, arg);
|
||||||
const models = (await getCustomGpts()).map((model) => {
|
const models = (await getCustomGpts(req?.session?.user?.username)).map((model) => {
|
||||||
model = model.toObject();
|
model = model.toObject();
|
||||||
model._id = model._id.toString();
|
model._id = model._id.toString();
|
||||||
return model;
|
return model;
|
||||||
|
|
@ -56,7 +56,7 @@ router.post('/', async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbResponse = await setter(update);
|
const dbResponse = await setter(req?.session?.user?.username, update);
|
||||||
res.status(201).send(dbResponse);
|
res.status(201).send(dbResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,6 @@ const messages = require('./messages');
|
||||||
const convos = require('./convos');
|
const convos = require('./convos');
|
||||||
const customGpts = require('./customGpts');
|
const customGpts = require('./customGpts');
|
||||||
const prompts = require('./prompts');
|
const prompts = require('./prompts');
|
||||||
|
const { router: auth, authenticatedOr401, authenticatedOrRedirect } = require('./auth');
|
||||||
|
|
||||||
module.exports = { ask, messages, convos, customGpts, prompts };
|
module.exports = { ask, messages, convos, customGpts, prompts, auth, authenticatedOr401, authenticatedOrRedirect };
|
||||||
|
|
@ -5,34 +5,67 @@ import TextChat from './components/Main/TextChat';
|
||||||
import Nav from './components/Nav';
|
import Nav from './components/Nav';
|
||||||
import MobileNav from './components/Nav/MobileNav';
|
import MobileNav from './components/Nav/MobileNav';
|
||||||
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { setUser } from './store/userReducer';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const { messages, messageTree } = useSelector((state) => state.messages);
|
const { messages, messageTree } = useSelector((state) => state.messages);
|
||||||
|
const { user } = useSelector((state) => state.user);
|
||||||
const { title } = useSelector((state) => state.convo);
|
const { title } = useSelector((state) => state.convo);
|
||||||
const { conversationId } = useSelector((state) => state.convo);
|
const { conversationId } = useSelector((state) => state.convo);
|
||||||
const [ navVisible, setNavVisible ]= useState(false)
|
const [ navVisible, setNavVisible ]= useState(false)
|
||||||
useDocumentTitle(title);
|
useDocumentTitle(title);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div className="flex h-screen">
|
axios.get('/api/me', {
|
||||||
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
|
timeout: 1000,
|
||||||
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
|
withCredentials: true
|
||||||
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white dark:bg-gray-800">
|
}).then((res) => {
|
||||||
<MobileNav setNavVisible={setNavVisible} />
|
return res.data
|
||||||
{messages.length === 0 ? (
|
}).then((user) => {
|
||||||
<Landing title={title} />
|
if (user)
|
||||||
) : (
|
dispatch(setUser(user))
|
||||||
<Messages
|
else {
|
||||||
messages={messages}
|
console.log('Not login!')
|
||||||
messageTree={messageTree}
|
window.location.href = "/auth/login";
|
||||||
/>
|
}
|
||||||
)}
|
}).catch((error) => {
|
||||||
<TextChat messages={messages} />
|
console.error(error)
|
||||||
|
console.log('Not login!')
|
||||||
|
window.location.href = "/auth/login";
|
||||||
|
})
|
||||||
|
// setUser
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (user)
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen">
|
||||||
|
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
|
||||||
|
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
|
||||||
|
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white dark:bg-gray-800">
|
||||||
|
<MobileNav setNavVisible={setNavVisible} />
|
||||||
|
{messages.length === 0 ? (
|
||||||
|
<Landing title={title} />
|
||||||
|
) : (
|
||||||
|
<Messages
|
||||||
|
messages={messages}
|
||||||
|
messageTree={messageTree}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TextChat messages={messages} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
else
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export default function Landing({ title }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col items-center overflow-y-auto text-sm dark:bg-gray-800">
|
<div className="flex h-full flex-col items-center overflow-y-auto text-sm dark:bg-gray-800">
|
||||||
<div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl">
|
<div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl">
|
||||||
<h1 className="mt-6 ml-auto mr-auto mb-10 flex items-center justify-center gap-2 text-center text-4xl font-semibold sm:mt-[20vh] sm:mb-16">
|
<h1 className="mt-6 ml-auto mr-auto mb-10 flex items-center justify-center gap-2 text-center text-4xl font-semibold md:mt-[20vh] sm:mb-16">
|
||||||
ChatGPT Clone
|
ChatGPT Clone
|
||||||
</h1>
|
</h1>
|
||||||
<div className="items-start gap-3.5 text-center md:flex">
|
<div className="items-start gap-3.5 text-center md:flex">
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@ export default function SubmitButton({ submitMessage }) {
|
||||||
|
|
||||||
if (isSubmitting) {
|
if (isSubmitting) {
|
||||||
return (
|
return (
|
||||||
<button className="absolute bottom-0 h-[50px] w-[30px] right-1 rounded-md p-1 text-gray-500 hover:bg-gray-100disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:right-2" disabled>
|
<button
|
||||||
|
className="absolute bottom-0 right-1 h-[100%] w-[30px] rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:right-2"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
<div className="text-2xl">
|
<div className="text-2xl">
|
||||||
<span >·</span>
|
<span>·</span>
|
||||||
<span className="blink">·</span>
|
<span className="blink">·</span>
|
||||||
<span className="blink2">·</span>
|
<span className="blink2">·</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,28 +26,30 @@ export default function SubmitButton({ submitMessage }) {
|
||||||
<button
|
<button
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="absolute bottom-0 flex justify-center items-center h-[50px] w-[50px] right-0 rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent"
|
className="group absolute bottom-0 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500"
|
||||||
>
|
>
|
||||||
<svg
|
<div className="m-1 rounded-md p-2 pt-[10px] pb-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
|
||||||
stroke="currentColor"
|
<svg
|
||||||
fill="none"
|
stroke="currentColor"
|
||||||
strokeWidth="2"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
viewBox="0 0 24 24"
|
||||||
strokeLinejoin="round"
|
strokeLinecap="round"
|
||||||
className="mr-1 h-4 w-4"
|
strokeLinejoin="round"
|
||||||
height="1em"
|
className="mr-1 h-4 w-4 "
|
||||||
width="1em"
|
height="1em"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
width="1em"
|
||||||
>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<line
|
>
|
||||||
x1="22"
|
<line
|
||||||
y1="2"
|
x1="22"
|
||||||
x2="11"
|
y1="2"
|
||||||
y2="13"
|
x2="11"
|
||||||
/>
|
y2="13"
|
||||||
<polygon points="22 2 15 22 11 13 2 9 22 2" />
|
/>
|
||||||
</svg>
|
<polygon points="22 2 15 22 11 13 2 9 22 2" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export default function TextChat({ messages }) {
|
||||||
const inputRef = useRef(null)
|
const inputRef = useRef(null)
|
||||||
const isComposing = useRef(false);
|
const isComposing = useRef(false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { user } = useSelector((state) => state.user);
|
||||||
const convo = useSelector((state) => state.convo);
|
const convo = useSelector((state) => state.convo);
|
||||||
const { initial } = useSelector((state) => state.models);
|
const { initial } = useSelector((state) => state.models);
|
||||||
const { isSubmitting, stopStream, submission, disabled, model, chatGptLabel, promptPrefix } =
|
const { isSubmitting, stopStream, submission, disabled, model, chatGptLabel, promptPrefix } =
|
||||||
|
|
@ -309,7 +310,7 @@ export default function TextChat({ messages }) {
|
||||||
<div
|
<div
|
||||||
className={`relative flex w-full flex-grow flex-col rounded-md border border-black/10 ${
|
className={`relative flex w-full flex-grow flex-col rounded-md border border-black/10 ${
|
||||||
disabled ? 'bg-gray-100' : 'bg-white'
|
disabled ? 'bg-gray-100' : 'bg-white'
|
||||||
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
|
} py-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
|
||||||
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
|
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
|
||||||
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
|
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
|
||||||
>
|
>
|
||||||
|
|
@ -328,7 +329,7 @@ export default function TextChat({ messages }) {
|
||||||
onCompositionEnd={handleCompositionEnd}
|
onCompositionEnd={handleCompositionEnd}
|
||||||
placeholder={disabled ? 'Choose another model or customize GPT again' : ''}
|
placeholder={disabled ? 'Choose another model or customize GPT again' : ''}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-9 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
|
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-12 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
|
||||||
/>
|
/>
|
||||||
<SubmitButton submitMessage={submitMessage} />
|
<SubmitButton submitMessage={submitMessage} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
dispatch(setModels(fetchedModels));
|
dispatch(setModels(fetchedModels));
|
||||||
});
|
});
|
||||||
|
|
||||||
const icon = getIconOfModel({ size: 16, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" });
|
const icon = getIconOfModel({ size: 20, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" });
|
||||||
|
|
||||||
if (value === 'chatgptCustom') {
|
if (value === 'chatgptCustom') {
|
||||||
return (
|
return (
|
||||||
|
|
@ -143,7 +143,7 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-3/4 overflow-hidden">{modelInput}</div>
|
<div className=" overflow-hidden">{modelInput}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{value === 'chatgpt' && <sup>$</sup>}
|
{value === 'chatgpt' && <sup>$</sup>}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { setText } from '~/store/textSlice';
|
||||||
import GPTIcon from '../svg/GPTIcon';
|
import GPTIcon from '../svg/GPTIcon';
|
||||||
import BingIcon from '../svg/BingIcon';
|
import BingIcon from '../svg/BingIcon';
|
||||||
import { Button } from '../ui/Button.tsx';
|
import { Button } from '../ui/Button.tsx';
|
||||||
|
import { getIconOfModel } from '../../utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -34,7 +35,7 @@ export default function ModelMenu() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [modelSave, setModelSave] = useState(false);
|
const [modelSave, setModelSave] = useState(false);
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const { model, customModel } = useSelector((state) => state.submit);
|
const { model, customModel, promptPrefix, chatGptLabel } = useSelector((state) => state.submit);
|
||||||
const { models, modelMap, initial } = useSelector((state) => state.models);
|
const { models, modelMap, initial } = useSelector((state) => state.models);
|
||||||
const { data, isLoading, mutate } = swr(`/api/customGpts`, (res) => {
|
const { data, isLoading, mutate } = swr(`/api/customGpts`, (res) => {
|
||||||
const fetchedModels = res.map((modelItem) => ({
|
const fetchedModels = res.map((modelItem) => ({
|
||||||
|
|
@ -176,7 +177,7 @@ export default function ModelMenu() {
|
||||||
|
|
||||||
const isBing = model === 'bingai' || model === 'sydney';
|
const isBing = model === 'bingai' || model === 'sydney';
|
||||||
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
|
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
|
||||||
const icon = isBing ? <BingIcon button={true} /> : <GPTIcon button={true} />;
|
const icon = getIconOfModel({ size: 32, sender: chatGptLabel || model, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, button: true});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onOpenChange={onOpenChange}>
|
<Dialog onOpenChange={onOpenChange}>
|
||||||
|
|
@ -188,9 +189,9 @@ export default function ModelMenu() {
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
|
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
|
||||||
className={`absolute bottom-0.5 rounded-md border-0 p-1 pl-2 outline-none ${colorProps.join(
|
className={`absolute bottom-0.5 items-center mb-[1.75px] md:mb-0 rounded-md border-0 p-1 ml-1 md:ml-0 outline-none ${colorProps.join(
|
||||||
' '
|
' '
|
||||||
)} focus:ring-0 focus:ring-offset-0 disabled:bottom-0.5 dark:data-[state=open]:bg-opacity-50 md:bottom-1 md:left-2 md:pl-1 md:disabled:bottom-1`}
|
)} focus:ring-0 focus:ring-offset-0 disabled:bottom-0.5 dark:data-[state=open]:bg-opacity-50 md:bottom-1 md:left-1 md:pl-1 md:disabled:bottom-1`}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
23
client/src/components/Nav/Logout.jsx
Normal file
23
client/src/components/Nav/Logout.jsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React, { useState, useContext } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import LogOutIcon from '../svg/LogOutIcon';
|
||||||
|
|
||||||
|
|
||||||
|
export default function Logout() {
|
||||||
|
const { user } = useSelector((state) => state.user);
|
||||||
|
|
||||||
|
const clickHandler = () => {
|
||||||
|
window.location.href = "/auth/logout";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className="flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"
|
||||||
|
onClick={clickHandler}
|
||||||
|
>
|
||||||
|
<LogOutIcon />
|
||||||
|
{user?.display || user?.username || 'USER'}
|
||||||
|
<small>Log out</small>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -3,16 +3,18 @@ import NavLink from './NavLink';
|
||||||
import LogOutIcon from '../svg/LogOutIcon';
|
import LogOutIcon from '../svg/LogOutIcon';
|
||||||
import ClearConvos from './ClearConvos';
|
import ClearConvos from './ClearConvos';
|
||||||
import DarkMode from './DarkMode';
|
import DarkMode from './DarkMode';
|
||||||
|
import Logout from './Logout';
|
||||||
|
|
||||||
export default function NavLinks() {
|
export default function NavLinks() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ClearConvos />
|
<ClearConvos />
|
||||||
<DarkMode />
|
<DarkMode />
|
||||||
<NavLink
|
<Logout />
|
||||||
|
{/* <NavLink
|
||||||
svg={LogOutIcon}
|
svg={LogOutIcon}
|
||||||
text="Log out"
|
text="Log out"
|
||||||
/>
|
/> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import messageReducer from './messageSlice.js'
|
||||||
import modelReducer from './modelSlice.js'
|
import modelReducer from './modelSlice.js'
|
||||||
import submitReducer from './submitSlice.js'
|
import submitReducer from './submitSlice.js'
|
||||||
import textReducer from './textSlice.js'
|
import textReducer from './textSlice.js'
|
||||||
|
import userReducer from './userReducer.js'
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
|
|
@ -13,6 +14,7 @@ export const store = configureStore({
|
||||||
models: modelReducer,
|
models: modelReducer,
|
||||||
text: textReducer,
|
text: textReducer,
|
||||||
submit: submitReducer,
|
submit: submitReducer,
|
||||||
|
user: userReducer,
|
||||||
},
|
},
|
||||||
devTools: true,
|
devTools: true,
|
||||||
});
|
});
|
||||||
19
client/src/store/userReducer.js
Normal file
19
client/src/store/userReducer.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
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;
|
||||||
|
|
@ -3,10 +3,10 @@ import axios from 'axios';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import useSWRMutation from 'swr/mutation';
|
import useSWRMutation from 'swr/mutation';
|
||||||
|
|
||||||
const fetcher = (url) => fetch(url).then((res) => res.json());
|
const fetcher = (url) => fetch(url, {credentials: 'include'}).then((res) => res.json());
|
||||||
|
|
||||||
const postRequest = async (url, { arg }) => {
|
const postRequest = async (url, { arg }) => {
|
||||||
return await axios.post(url, { arg });
|
return await axios.post(url, { withCredentials: true, arg });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const swr = (path, successCallback, options) => {
|
export const swr = (path, successCallback, options) => {
|
||||||
|
|
|
||||||
|
|
@ -47,20 +47,23 @@ export const wrapperRegex = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGptLabel, error, ...props }) => {
|
export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGptLabel, error, ...props }) => {
|
||||||
|
const { button } = props;
|
||||||
const bgColors = {
|
const bgColors = {
|
||||||
chatgpt: 'rgb(16, 163, 127)',
|
chatgpt: `rgb(16, 163, 127${ button ? ', 0.75' : ''})`,
|
||||||
chatgptBrowser: 'rgb(25, 207, 207)',
|
chatgptBrowser: `rgb(25, 207, 207${ button ? ', 0.75' : ''})`,
|
||||||
bingai: 'transparent',
|
bingai: 'transparent',
|
||||||
sydney: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)',
|
sydney: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)',
|
||||||
chatgptCustom: 'rgb(0, 163, 255)',
|
chatgptCustom: `rgb(0, 163, 255${ button ? ', 0.75' : ''})`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(sender, isCreatedByUser, model, chatGptLabel, error, )
|
||||||
|
|
||||||
if (isCreatedByUser)
|
if (isCreatedByUser)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
title='User'
|
title='User'
|
||||||
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12 }}
|
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12, width: size, height: size }}
|
||||||
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
|
className={`relative flex items-center justify-center rounded-sm text-white ` + props?.className}
|
||||||
>
|
>
|
||||||
User
|
User
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -75,11 +78,11 @@ export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGp
|
||||||
<div
|
<div
|
||||||
title={chatGptLabel || model}
|
title={chatGptLabel || model}
|
||||||
style={
|
style={
|
||||||
{ background } || { background: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)' }
|
{ background: background || 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)', width: size, height: size }
|
||||||
}
|
}
|
||||||
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
|
className={`relative flex items-center justify-center rounded-sm text-white ` + props?.className}
|
||||||
>
|
>
|
||||||
{isBing ? <BingIcon size={size} /> : <GPTIcon size={size} />}
|
{isBing ? <BingIcon size={size * 0.7} /> : <GPTIcon size={size * 0.7} />}
|
||||||
{error && (
|
{error && (
|
||||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
|
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
|
||||||
!
|
!
|
||||||
|
|
@ -91,8 +94,8 @@ export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGp
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
title='User'
|
title='User'
|
||||||
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12 }}
|
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12, width: size, height: size }}
|
||||||
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
|
className={`relative flex items-center justify-center rounded-sm p-1 text-white ` + props?.className}
|
||||||
>
|
>
|
||||||
{chatGptLabel}
|
{chatGptLabel}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue