Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47d0184990 | ||
|
|
ee59fa40f5 | ||
|
|
68b3731d06 | ||
|
|
e4889ff8bb | ||
|
|
2c026d11a5 | ||
|
|
1a252170f5 | ||
|
|
d40aaa703d | ||
|
|
44005258fc | ||
|
|
19495a461d | ||
|
|
fcf068dddf | ||
|
|
7468b3011f | ||
|
|
dade7b450f | ||
|
|
7fbf27c5aa | ||
|
|
4705975e59 | ||
|
|
2f59c82bec | ||
|
|
6a34978e98 | ||
|
|
d437e4b8cd | ||
|
|
f40a2f8ee8 | ||
|
|
2d31c9f8b6 |
57
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
Normal file
57
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Feature Request
|
||||
description: File a feature request
|
||||
title: "Enhancement: "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill this out!
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
label: Contact Details
|
||||
description: How can we contact you if we need more information?
|
||||
placeholder: ex. email@example.com
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: what
|
||||
attributes:
|
||||
label: What features would you like to see added?
|
||||
description: Please provide as many details as possible.
|
||||
placeholder: Please provide as many details as possible.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: More details
|
||||
description: Please provide additional details if needed.
|
||||
placeholder: Please provide additional details if needed.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: subject
|
||||
attributes:
|
||||
label: Which components are impacted by your request?
|
||||
multiple: true
|
||||
options:
|
||||
- General
|
||||
- UI
|
||||
- Endpoints
|
||||
- Plugins
|
||||
- Other
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Pictures
|
||||
description: If relevant, please include images to help clarify your request. You can drag and drop images directly here, paste them, or provide a link to them.
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/chatgpt-clone/blob/main/documents/contributions/code_of_conduct.md)
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
58
.github/ISSUE_TEMPLATE/QUESTION.yml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/QUESTION.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Question
|
||||
description: Ask your question
|
||||
title: "[Question]: "
|
||||
labels: ["question"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill this!
|
||||
- type: input
|
||||
id: contact
|
||||
attributes:
|
||||
label: Contact Details
|
||||
description: How can we get in touch with you if we need more info?
|
||||
placeholder: ex. email@example.com
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: what-is-your-question
|
||||
attributes:
|
||||
label: What is your question?
|
||||
description: Please give as many details as possible
|
||||
placeholder: Please give as many details as possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: more-details
|
||||
attributes:
|
||||
label: More Details
|
||||
description: Please provide more details if needed.
|
||||
placeholder: Please provide more details if needed.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What is the main subject of your question?
|
||||
multiple: true
|
||||
options:
|
||||
- Documentation
|
||||
- Installation
|
||||
- UI
|
||||
- Endpoints
|
||||
- User System/OAuth
|
||||
- Other
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem. You can drag and drop, paste images directly here or link to them.
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/danny-avila/chatgpt-clone/blob/main/documents/contributions/code_of_conduct.md)
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -7,6 +7,8 @@ version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/api" # Location of package manifests
|
||||
target-branch: "develop"
|
||||
versioning-strategy: increase-if-necessary
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
allow:
|
||||
@@ -18,6 +20,8 @@ updates:
|
||||
include: "scope"
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/client" # Location of package manifests
|
||||
target-branch: "develop"
|
||||
versioning-strategy: increase-if-necessary
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
allow:
|
||||
@@ -29,6 +33,8 @@ updates:
|
||||
include: "scope"
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
target-branch: "develop"
|
||||
versioning-strategy: increase-if-necessary
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
allow:
|
||||
|
||||
@@ -10,7 +10,7 @@ Please delete options that are not relevant.
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
- [ ] Documentation update
|
||||
|
||||
|
||||
## How Has This Been Tested?
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -26,6 +26,7 @@ dist/
|
||||
public/main.js
|
||||
public/main.js.map
|
||||
public/main.js.LICENSE.txt
|
||||
client/public/images/
|
||||
client/public/main.js
|
||||
client/public/main.js.map
|
||||
client/public/main.js.LICENSE.txt
|
||||
@@ -48,6 +49,7 @@ bower_components/
|
||||
# Environment
|
||||
.npmrc
|
||||
.env
|
||||
.env.test
|
||||
cache.json
|
||||
api/data/
|
||||
owner.yml
|
||||
|
||||
@@ -59,8 +59,8 @@ representative at an online or offline event.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
https://t.me/proffapt.
|
||||
reported to the community leaders responsible for enforcement here on GitHub or
|
||||
on the official [Discord Server](https://discord.gg/uDyZ5Tzhct).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
@@ -129,4 +129,4 @@ https://www.contributor-covenant.org/translations.
|
||||
|
||||
##
|
||||
|
||||
## [Go Back to ReadMe](../../README.md)
|
||||
## [Go Back to ReadMe](README.md)
|
||||
@@ -12,11 +12,11 @@ If the feature you would like to contribute has not already received prior appro
|
||||
|
||||
*Please note that a pull request involving a feature that has not been reviewed and approved by the project maintainers may be rejected.*
|
||||
|
||||
If you would like to discuss the changes you wish to make, join our [Discord community](https://discord.gg/NGaa9RPCft).
|
||||
If you would like to discuss the changes you wish to make, join our [Discord community](https://discord.gg/uDyZ5Tzhct).
|
||||
|
||||
## Our Standards
|
||||
|
||||
Please read our [Coding Standards and Conventions](coding_conventions.md) before beginning on a contribution.
|
||||
Please read our [Coding Standards and Conventions](docs/contributions/coding_conventions.md) before beginning on a contribution.
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
@@ -180,6 +180,6 @@ Apply the following naming conventions to branches, labels, and other Git-relate
|
||||
|
||||
##
|
||||
|
||||
## [Go Back to ReadMe](../../README.md)
|
||||
## [Go Back to ReadMe](README.md)
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Contributors List
|
||||
|
||||
We appreciate all the contributors who helped make this project possible:
|
||||
|
||||
- danny-avila (Admin)
|
||||
- wtlyu (Contributor)
|
||||
- danorlando (Contributor)
|
||||
- alfredo-f (Contributor)
|
||||
- HyunggyuJang (Contributor)
|
||||
- fuegovic (Contributor)
|
||||
- DavidDev1334
|
||||
- toordog (Contributor)
|
||||
- heathriel (External Contributor)
|
||||
- hackreactor-bot (Contributor)
|
||||
- git-bruh (Contributor)
|
||||
- zhangsean (Contributor)
|
||||
- llk89 (Contributor)
|
||||
- adamrb (Contributor)
|
||||
|
||||
|
||||
|
||||
If you have contributed to this project and would like to be added to the list of contributors, please submit a pull request updating this file with your name and GitHub username.
|
||||
|
||||
##
|
||||
|
||||
## [Go Back to ReadMe](README.md)
|
||||
37
README.md
37
README.md
@@ -74,46 +74,45 @@
|
||||
<details open>
|
||||
<summary><strong>Getting Started</strong></summary>
|
||||
|
||||
* [Docker Install](/documents/install/docker_install.md)
|
||||
* [Linux Install](documents/install/linux_install.md)
|
||||
* [Mac Install](documents/install/mac_install.md)
|
||||
* [Windows Install](documents/install/windows_install.md)
|
||||
* [Docker Install](/docs/install/docker_install.md)
|
||||
* [Linux Install](docs/install/linux_install.md)
|
||||
* [Mac Install](docs/install/mac_install.md)
|
||||
* [Windows Install](docs/install/windows_install.md)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>General Information</strong></summary>
|
||||
|
||||
* [Project Origin](documents/general_info/project_origin.md)
|
||||
* [Multilingual Information](documents/general_info/multilingual_information.md)
|
||||
* [Roadmap](documents/general_info/roadmap.md)
|
||||
* [Tech Stack](documents/general_info/tech_stack.md)
|
||||
* [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
* [Project Origin](docs/general_info/project_origin.md)
|
||||
* [Multilingual Information](docs/general_info/multilingual_information.md)
|
||||
* [Roadmap](docs/general_info/roadmap.md)
|
||||
* [Tech Stack](docs/general_info/tech_stack.md)
|
||||
* [Changelog](CHANGELOG.md)
|
||||
* [Bing Jailbreak Info](documents/general_info/bing_jailbreak_info.md)
|
||||
* [Bing Jailbreak Info](docs/general_info/bing_jailbreak_info.md)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Features</strong></summary>
|
||||
|
||||
* [User Auth System](documents/features/user_auth_system.md)
|
||||
* [Proxy](documents/features/proxy.md)
|
||||
* [User Auth System](docs/features/user_auth_system.md)
|
||||
* [Proxy](docs/features/proxy.md)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Cloud Deployment</strong></summary>
|
||||
|
||||
* [Heroku](documents/deployment/heroku.md)
|
||||
* [Heroku](docs/deployment/heroku.md)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Contributions</strong></summary>
|
||||
|
||||
* [Code of Conduct](documents/contributions/code_of_conduct.md)
|
||||
* [Contributor Guidelines](documents/contributions/contributor_guidelines.md)
|
||||
* [Documentation Guidelines](documents/contributions/documentation_guidelines.md)
|
||||
* [Code Standards and Conventions](documents/contributions/coding_conventions.md)
|
||||
* [Testing](documents/contributions/testing.md)
|
||||
* [Contributor Guidelines](CONTRIBUTING.md)
|
||||
* [Documentation Guidelines](docs/contributions/documentation_guidelines.md)
|
||||
* [Code Standards and Conventions](docs/contributions/coding_conventions.md)
|
||||
* [Testing](docs/contributions/testing.md)
|
||||
* [Security](SECURITY.md)
|
||||
* [Contributors](CONTRIBUTORS.md)
|
||||
* [Trello Board](https://trello.com/b/17z094kq/chatgpt-clone)
|
||||
</details>
|
||||
|
||||
@@ -130,7 +129,7 @@ Please read the documentation before you do!
|
||||
|
||||
For new features, components, or extensions, please open an issue and discuss before sending a PR.
|
||||
|
||||
- Join the [Discord community](https://discord.gg/NGaa9RPCft)
|
||||
- Join the [Discord community](https://discord.gg/uDyZ5Tzhct)
|
||||
|
||||
This project exists in its current state thanks to all the people who contribute
|
||||
---
|
||||
|
||||
@@ -46,10 +46,10 @@ We would like to express our gratitude to the security researchers and community
|
||||
We do not currently have a bug bounty program in place. However, we welcome and appreciate any security-related contributions through pull requests (PRs) that address vulnerabilities in our codebase.
|
||||
We believe in the power of collaboration to improve the security of our project and invite you to join us in making it more robust.
|
||||
|
||||
##
|
||||
|
||||
## [Go Back to ReadMe](README.md)
|
||||
|
||||
|
||||
**Reference**
|
||||
- https://cheatsheetseries.owasp.org/cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.html
|
||||
|
||||
##
|
||||
|
||||
## [Go Back to ReadMe](README.md)
|
||||
|
||||
@@ -59,9 +59,9 @@ OPENAI_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-0301,text-davinci-003,gpt-4
|
||||
##########################
|
||||
|
||||
# Also used for Sydney and jailbreak
|
||||
# As of 5/23/23, to use Bing, you will need your full cookie string from bing.com. Use dev tools or an extension while
|
||||
# logged into the site to view it in your network request Cookie header value. For full instructions, see my comment here:
|
||||
# https://github.com/waylaidwanderer/node-chatgpt-api/issues/378#issuecomment-1559868368
|
||||
# To get your Access token for Bing, login to https://www.bing.com
|
||||
# Use dev tools or an extension while logged into the site to copy the content of the _U cookie.
|
||||
#If this fails, follow these instructions https://github.com/danny-avila/chatgpt-clone/issues/370#issuecomment-1560382302 to provide the full cookie strings.
|
||||
# Set to "user_provided" to allow the user to provide its token from the UI.
|
||||
# Leave it blank to disable this endpoint.
|
||||
BINGAI_TOKEN="user_provided"
|
||||
|
||||
@@ -81,6 +81,7 @@ const askClient = async ({
|
||||
|
||||
try {
|
||||
usage.completion_tokens = (enc.encode(res.response)).length;
|
||||
enc.free();
|
||||
usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
|
||||
res.usage = usage;
|
||||
} catch (e) {
|
||||
|
||||
89
api/app/clients/chatgpt-client.tokens.js
Normal file
89
api/app/clients/chatgpt-client.tokens.js
Normal file
@@ -0,0 +1,89 @@
|
||||
require('dotenv').config();
|
||||
|
||||
const run = async () => {
|
||||
const { ChatGPTClient } = await import('@waylaidwanderer/chatgpt-api');
|
||||
const text = `
|
||||
The standard Lorem Ipsum passage, used since the 1500s
|
||||
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
Section 1.10.32 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
|
||||
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"
|
||||
1914 translation by H. Rackham
|
||||
|
||||
"But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
|
||||
Section 1.10.33 of "de Finibus Bonorum et Malorum", written by Cicero in 45 BC
|
||||
|
||||
"At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat."
|
||||
1914 translation by H. Rackham
|
||||
|
||||
"On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains."
|
||||
`;
|
||||
const model = 'gpt-3.5-turbo';
|
||||
const maxContextTokens = model === 'gpt-4' ? 8191 : model === 'gpt-4-32k' ? 32767 : 4095; // 1 less than maximum
|
||||
const clientOptions = {
|
||||
reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null,
|
||||
maxContextTokens,
|
||||
modelOptions: {
|
||||
model,
|
||||
},
|
||||
proxy: process.env.PROXY || null,
|
||||
debug: true
|
||||
};
|
||||
|
||||
let apiKey = process.env.OPENAI_KEY;
|
||||
|
||||
const maxMemory = 0.05 * 1024 * 1024 * 1024;
|
||||
|
||||
// Calculate initial percentage of memory used
|
||||
const initialMemoryUsage = process.memoryUsage().heapUsed;
|
||||
|
||||
|
||||
function printProgressBar(percentageUsed) {
|
||||
const filledBlocks = Math.round(percentageUsed / 2); // Each block represents 2%
|
||||
const emptyBlocks = 50 - filledBlocks; // Total blocks is 50 (each represents 2%), so the rest are empty
|
||||
const progressBar = '[' + '█'.repeat(filledBlocks) + ' '.repeat(emptyBlocks) + '] ' + percentageUsed.toFixed(2) + '%';
|
||||
console.log(progressBar);
|
||||
}
|
||||
|
||||
const iterations = 16000;
|
||||
console.time('loopTime');
|
||||
// Trying to catch the error doesn't help; all future calls will immediately crash
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
try {
|
||||
console.log(`Iteration ${i}`);
|
||||
const client = new ChatGPTClient(apiKey, clientOptions);
|
||||
|
||||
client.getTokenCount(text);
|
||||
// const encoder = client.constructor.getTokenizer('cl100k_base');
|
||||
// console.log(`Iteration ${i}: call encode()...`);
|
||||
// encoder.encode(text, 'all');
|
||||
// encoder.free();
|
||||
|
||||
const memoryUsageDuringLoop = process.memoryUsage().heapUsed;
|
||||
const percentageUsed = memoryUsageDuringLoop / maxMemory * 100;
|
||||
printProgressBar(percentageUsed);
|
||||
|
||||
if (i === (iterations - 1)) {
|
||||
console.log(' done');
|
||||
// encoder.free();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`caught error! in Iteration ${i}`);
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
console.timeEnd('loopTime');
|
||||
// Calculate final percentage of memory used
|
||||
const finalMemoryUsage = process.memoryUsage().heapUsed;
|
||||
// const finalPercentageUsed = finalMemoryUsage / maxMemory * 100;
|
||||
console.log(`Initial memory usage: ${initialMemoryUsage / 1024 / 1024} megabytes`);
|
||||
console.log(`Final memory usage: ${finalMemoryUsage / 1024 / 1024} megabytes`);
|
||||
setTimeout(() => {
|
||||
const memoryUsageAfterTimeout = process.memoryUsage().heapUsed;
|
||||
console.log(`Post timeout: ${memoryUsageAfterTimeout / 1024 / 1024} megabytes`);
|
||||
} , 10000);
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-backend",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"description": "",
|
||||
"main": "server/index.js",
|
||||
"scripts": {
|
||||
@@ -21,7 +21,7 @@
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@keyv/mongo": "^2.1.8",
|
||||
"@waylaidwanderer/chatgpt-api": "^1.36.0",
|
||||
"@waylaidwanderer/chatgpt-api": "^1.37.0",
|
||||
"axios": "^1.3.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cookie": "^0.5.0",
|
||||
@@ -38,7 +38,7 @@
|
||||
"keyv": "^4.5.2",
|
||||
"keyv-file": "^0.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"meilisearch": "^0.32.3",
|
||||
"meilisearch": "^0.33.0",
|
||||
"mongoose": "^7.1.1",
|
||||
"nodemailer": "^6.9.1",
|
||||
"openai": "^3.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-frontend",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function Conversation({ conversation, retainView }) {
|
||||
|
||||
if (currentConversation?.conversationId !== conversationId) {
|
||||
aProps.className =
|
||||
'group relative flex cursor-pointer items-center gap-3 break-all rounded-md py-3 px-3 hover:bg-[#2A2B32] hover:pr-4';
|
||||
'group relative flex cursor-pointer items-center gap-3 break-all rounded-md py-3 px-3 hover:bg-gray-800 hover:pr-4';
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -123,7 +123,7 @@ export default function Conversation({ conversation, retainView }) {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute inset-y-0 right-0 z-10 w-8 rounded-r-md bg-gradient-to-l from-gray-900 group-hover:from-[#2A2B32]" />
|
||||
<div className="absolute inset-y-0 right-0 z-10 w-8 rounded-r-md bg-gradient-to-l from-gray-900 group-hover:from-gray-700/70" />
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useRecoilValue } from 'recoil';
|
||||
import SetTokenDialog from '../SetTokenDialog';
|
||||
|
||||
import store from '../../../store';
|
||||
import { cn } from '~/utils/index.jsx';
|
||||
|
||||
const alternateName = {
|
||||
openAI: 'OpenAI',
|
||||
@@ -15,7 +16,7 @@ const alternateName = {
|
||||
google: 'PaLM'
|
||||
};
|
||||
|
||||
export default function ModelItem({ endpoint, value }) {
|
||||
export default function ModelItem({ endpoint, value, isSelected }) {
|
||||
const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false);
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
|
||||
@@ -33,8 +34,11 @@ export default function ModelItem({ endpoint, value }) {
|
||||
<>
|
||||
<DropdownMenuRadioItem
|
||||
value={value}
|
||||
className={cn(
|
||||
'group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800',
|
||||
isSelected && 'dark:bg-gray-800 bg-gray-50 active'
|
||||
)}
|
||||
id={endpoint}
|
||||
className="group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
{icon}
|
||||
{alternateName[endpoint] || endpoint}
|
||||
@@ -42,7 +46,10 @@ export default function ModelItem({ endpoint, value }) {
|
||||
<div className="flex w-4 flex-1" />
|
||||
{isUserProvided ? (
|
||||
<button
|
||||
className="invisible m-0 mr-1 flex-initial rounded-md p-0 text-xs font-medium text-gray-400 hover:text-gray-700 group-hover:visible dark:font-normal dark:text-gray-400 dark:hover:text-gray-200"
|
||||
className={cn(
|
||||
'invisible m-0 mr-1 flex-initial rounded-md p-0 text-xs font-medium text-gray-400 hover:text-gray-700 group-hover:visible dark:font-normal dark:text-gray-400 dark:hover:text-gray-200',
|
||||
isSelected && 'visible text-gray-700 dark:text-gray-200'
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setSetTokenDialogOpen(true);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import EndpointItem from './EndpointItem.jsx';
|
||||
|
||||
export default function EndpointItems({ endpoints, onSelect }) {
|
||||
export default function EndpointItems({ endpoints, onSelect, selectedEndpoint }) {
|
||||
return (
|
||||
<>
|
||||
{endpoints.map((endpoint) => (
|
||||
<EndpointItem key={endpoint} value={endpoint} onSelect={onSelect} endpoint={endpoint} />
|
||||
<EndpointItem isSelected={selectedEndpoint === endpoint} key={endpoint} value={endpoint} onSelect={onSelect} endpoint={endpoint} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function NewConversationMenu() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="min-w-[300px] dark:bg-gray-700 z-[100]"
|
||||
className="min-w-[300px] dark:bg-gray-900 z-[100]"
|
||||
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<DropdownMenuLabel
|
||||
@@ -150,11 +150,11 @@ export default function NewConversationMenu() {
|
||||
<DropdownMenuRadioGroup
|
||||
value={endpoint}
|
||||
onValueChange={onSelectEndpoint}
|
||||
className="overflow-y-auto"
|
||||
className="overflow-y-auto gap-1 flex flex-col"
|
||||
>
|
||||
{showEndpoints &&
|
||||
(availableEndpoints.length ? (
|
||||
<EndpointItems endpoints={availableEndpoints} onSelect={onSelectEndpoint} />
|
||||
<EndpointItems selectedEndpoint={endpoint} endpoints={availableEndpoints} onSelect={onSelectEndpoint} />
|
||||
) : (
|
||||
<DropdownMenuLabel className="dark:text-gray-300">
|
||||
No endpoint available.
|
||||
|
||||
@@ -43,16 +43,26 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
||||
const helpText = {
|
||||
bingAI: (
|
||||
<small className="break-all text-gray-600">
|
||||
{`As of 5/23/23, to use Bing, you will need your full cookie string from bing.com. Use dev tools or an extension while
|
||||
logged into the site to view it in your network request Cookie header value. For full instructions, see my `}
|
||||
{`To get your Access token for Bing, login to `}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://www.bing.com"
|
||||
rel="noreferrer"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
https://www.bing.com
|
||||
</a>
|
||||
{`. Use dev tools or an extension while logged into the site to copy the content of the _U cookie.
|
||||
If this fails, follow these `}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/waylaidwanderer/node-chatgpt-api/issues/378#issuecomment-1559868368"
|
||||
rel="noreferrer"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
comment here
|
||||
instructions
|
||||
</a>
|
||||
{` to provide the full cookie strings.`}
|
||||
</small>
|
||||
),
|
||||
chatGPTBrowser: (
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function SubmitButton({
|
||||
disabled={disabled}
|
||||
className="group absolute bottom-0 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500"
|
||||
>
|
||||
<div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[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">
|
||||
<div className="m-1 mr-0 rounded-md pt-[11px] pb-[9px] pl-[9.5px] pr-[7px] 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">
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
|
||||
@@ -11,15 +11,7 @@ import { useMessageHandler } from '~/utils/handleSubmit';
|
||||
import { useGetConversationByIdQuery } from '~/data-provider';
|
||||
import { cn } from '~/utils/';
|
||||
import store from '~/store';
|
||||
|
||||
function isJson(str) {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
import getError from '~/utils/getError';
|
||||
|
||||
export default function Message({
|
||||
conversation,
|
||||
@@ -73,24 +65,6 @@ export default function Message({
|
||||
}
|
||||
};
|
||||
|
||||
const getError = (text) => {
|
||||
const errorMessage = text.length > 512 ? text.slice(0, 512) + '...' : text;
|
||||
const match = text.match(/\{[^{}]*\}/);
|
||||
var json = match ? match[0] : '';
|
||||
if (isJson(json)) {
|
||||
json = JSON.parse(json);
|
||||
if (json.code === 'invalid_api_key') {
|
||||
return 'Invalid API key. Please check your API key and try again. You can access your API key by clicking on the model logo in the top-left corner of the textbox.';
|
||||
} else if (json.type === 'insufficient_quota') {
|
||||
return "We're sorry, but the default API key has reached its limit. To continue using this service, please set up your own API key. You can do this by clicking on the model logo in the top-left corner of the textbox.";
|
||||
} else {
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`;
|
||||
}
|
||||
} else {
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`;
|
||||
}
|
||||
};
|
||||
|
||||
const props = {
|
||||
className:
|
||||
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800'
|
||||
@@ -103,7 +77,7 @@ export default function Message({
|
||||
|
||||
if (!isCreatedByUser)
|
||||
props.className =
|
||||
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]';
|
||||
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-gray-1000';
|
||||
|
||||
if (message.bg && searchResult) {
|
||||
props.className = message.bg.split('hover')[0];
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { forwardRef, useContext } from 'react';
|
||||
import DarkModeIcon from '../svg/DarkModeIcon';
|
||||
import LightModeIcon from '../svg/LightModeIcon';
|
||||
import { ThemeContext } from '~/hooks/ThemeContext';
|
||||
|
||||
const DarkMode = forwardRef(() => {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
|
||||
const clickHandler = () => setTheme(theme === 'dark' ? 'light' : 'dark');
|
||||
const mode = theme === 'dark' ? 'Light mode' : 'Dark mode';
|
||||
|
||||
return (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{theme === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
|
||||
{mode}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
export default DarkMode;
|
||||
@@ -3,11 +3,12 @@ import { Fragment, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import SearchBar from './SearchBar';
|
||||
import TrashIcon from '../svg/TrashIcon';
|
||||
import GearIcon from '../svg/GearIcon';
|
||||
import Settings from './Settings';
|
||||
import { Download } from 'lucide-react';
|
||||
import NavLink from './NavLink';
|
||||
import ExportModel from './ExportConversation/ExportModel';
|
||||
import ClearConvos from './ClearConvos';
|
||||
import DarkMode from './DarkMode';
|
||||
import Logout from './Logout';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { cn } from '~/utils/';
|
||||
@@ -18,6 +19,7 @@ import store from '~/store';
|
||||
export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||
const [showExports, setShowExports] = useState(false);
|
||||
const [showClearConvos, setShowClearConvos] = useState(false);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const { user } = useAuthContext();
|
||||
|
||||
const conversation = useRecoilValue(store.conversation) || {};
|
||||
@@ -47,7 +49,7 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||
<img
|
||||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar || `https://avatars.dicebear.com/api/initials/${user?.name}.svg`
|
||||
user?.avatar || `https://api.dicebear.com/6.x/initials/svg?seed=${user?.name || 'User'}&fontFamily=Verdana&fontSize=36`
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
@@ -77,8 +79,8 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||
<Menu.Item as="div">
|
||||
<NavLink
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700',
|
||||
exportable ? 'cursor-pointer text-white' : 'cursor-not-allowed text-gray-400'
|
||||
'flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700 rounded-none',
|
||||
exportable ? 'cursor-pointer text-white' : 'cursor-not-allowed text-white/50'
|
||||
)}
|
||||
svg={() => <Download size={16} />}
|
||||
text="Export conversation"
|
||||
@@ -86,17 +88,22 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||
/>
|
||||
</Menu.Item>
|
||||
<div className="my-1.5 h-px bg-white/20" role="none" />
|
||||
<Menu.Item as="div">
|
||||
<DarkMode />
|
||||
</Menu.Item>
|
||||
<Menu.Item as="div">
|
||||
<NavLink
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700 rounded-none"
|
||||
svg={() => <TrashIcon />}
|
||||
text="Clear conversations"
|
||||
clickHandler={() => setShowClearConvos(true)}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<Menu.Item as="div">
|
||||
<NavLink
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700 rounded-none"
|
||||
svg={() => <GearIcon />}
|
||||
text="Settings"
|
||||
clickHandler={() => setShowSettings(true)}
|
||||
/>
|
||||
</Menu.Item>
|
||||
<div className="my-1.5 h-px bg-white/20" role="none" />
|
||||
<Menu.Item as="div">
|
||||
<Logout />
|
||||
@@ -108,6 +115,7 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
|
||||
</Menu>
|
||||
{showExports && <ExportModel open={showExports} onOpenChange={setShowExports} />}
|
||||
{showClearConvos && <ClearConvos open={showClearConvos} onOpenChange={setShowClearConvos} />}
|
||||
{showSettings && <Settings open={showSettings} onOpenChange={setShowSettings} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { Search } from 'lucide-react';
|
||||
import { forwardRef, useState, useEffect } from 'react';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import store from '~/store';
|
||||
|
||||
const SearchBar = forwardRef((props, ref) => {
|
||||
const { clearSearch } = props;
|
||||
const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery);
|
||||
const [showClearIcon, setShowClearIcon] = useState(false);
|
||||
|
||||
const handleKeyUp = (e) => {
|
||||
const { value } = e.target;
|
||||
@@ -18,17 +19,27 @@ const SearchBar = forwardRef((props, ref) => {
|
||||
const onChange = (e) => {
|
||||
const { value } = e.target;
|
||||
setSearchQuery(value);
|
||||
setShowClearIcon(value.length > 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery.length === 0) {
|
||||
setShowClearIcon(false);
|
||||
} else {
|
||||
setShowClearIcon(true);
|
||||
}
|
||||
}, [searchQuery])
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700 relative"
|
||||
>
|
||||
{<Search className="h-4 w-4" />}
|
||||
{<Search className="h-4 w-4 absolute left-3" />}
|
||||
<input
|
||||
type="text"
|
||||
className="m-0 mr-0 w-full border-none bg-transparent p-0 text-sm leading-tight outline-none"
|
||||
className="m-0 mr-0 w-full border-none bg-transparent p-0 text-sm leading-tight outline-none pl-7"
|
||||
value={searchQuery}
|
||||
onChange={onChange}
|
||||
onKeyDown={(e) => {
|
||||
@@ -37,8 +48,15 @@ const SearchBar = forwardRef((props, ref) => {
|
||||
placeholder="Search messages"
|
||||
onKeyUp={handleKeyUp}
|
||||
/>
|
||||
<X
|
||||
className={`h-5 w-5 absolute right-3 cursor-pointer ${showClearIcon ? 'opacity-100' : 'opacity-0'} transition-opacity duration-1000`}
|
||||
onClick={() => {
|
||||
setSearchQuery('');
|
||||
clearSearch();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default SearchBar;
|
||||
export default SearchBar;
|
||||
170
client/src/components/Nav/Settings.jsx
Normal file
170
client/src/components/Nav/Settings.jsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Dialog } from '../ui/Dialog.tsx';
|
||||
import * as Tabs from '@radix-ui/react-tabs';
|
||||
import { DialogContent, DialogHeader, DialogTitle } from '../ui/Dialog.tsx';
|
||||
import { useEffect, useState, useContext } from 'react';
|
||||
import { cn } from '~/utils/';
|
||||
import { useClearConversationsMutation } from '~/data-provider';
|
||||
import { ThemeContext } from '~/hooks/ThemeContext';
|
||||
import store from '~/store';
|
||||
import { CheckIcon } from 'lucide-react';
|
||||
|
||||
export default function Settings({ open, onOpenChange }) {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
const { newConversation } = store.useConversation();
|
||||
const { refreshConversations } = store.useConversations();
|
||||
const clearConvosMutation = useClearConversationsMutation();
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [confirmClear, setConfirmClear] = useState(false);
|
||||
|
||||
const clearConvos = () => {
|
||||
if (confirmClear) {
|
||||
console.log('Clearing conversations...');
|
||||
clearConvosMutation.mutate();
|
||||
setConfirmClear(false);
|
||||
} else {
|
||||
setConfirmClear(true);
|
||||
}
|
||||
};
|
||||
|
||||
const changeTheme = (e) => {
|
||||
setTheme(e.target.value);
|
||||
};
|
||||
|
||||
// check if mobile dynamically and update
|
||||
useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
if (window.innerWidth <= 768) {
|
||||
setIsMobile(true);
|
||||
} else {
|
||||
setIsMobile(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (clearConvosMutation.isSuccess) {
|
||||
newConversation();
|
||||
refreshConversations();
|
||||
}
|
||||
}, [clearConvosMutation.isSuccess]);
|
||||
|
||||
useEffect(() => {
|
||||
// If the user clicks in the dialog when confirmClear is true, set it to false
|
||||
const handleClick = (e) => {
|
||||
if (confirmClear) {
|
||||
if (e.target.id === 'clearConvosBtn' || e.target.id === 'clearConvosTxt') {
|
||||
return;
|
||||
}
|
||||
|
||||
setConfirmClear(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('click', handleClick);
|
||||
return () => window.removeEventListener('click', handleClick);
|
||||
}, [confirmClear]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={cn('shadow-2xl dark:bg-gray-900 dark:text-white')}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-gray-800 dark:text-white">Settings</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="px-6">
|
||||
<Tabs.Root
|
||||
defaultValue="general"
|
||||
className="flex flex-col gap-6 md:flex-row"
|
||||
orientation="vertical"
|
||||
>
|
||||
<Tabs.List
|
||||
aria-label="Settings"
|
||||
role="tablist"
|
||||
aria-orientation="vertical"
|
||||
className={cn(
|
||||
'-ml-[8px] flex min-w-[180px] flex-shrink-0 flex-col',
|
||||
isMobile && 'flex-row rounded-lg bg-gray-100 p-1 dark:bg-gray-800/30'
|
||||
)}
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
<Tabs.Trigger
|
||||
className={cn(
|
||||
'radix-state-active:bg-gray-800 radix-state-active:text-white flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm',
|
||||
isMobile &&
|
||||
'dark:radix-state-active:text-white group flex-1 items-center justify-center text-sm dark:text-gray-500'
|
||||
)}
|
||||
value="general"
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
strokeWidth="0"
|
||||
viewBox="0 0 20 20"
|
||||
className="group-radix-state-active:fill-white h-4 h-5 w-4 w-5 fill-white dark:fill-gray-500"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
General
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="general" role="tabpanel" className="w-full md:min-h-[300px]">
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>Theme</div>
|
||||
<select
|
||||
className="w-24 rounded border border-black/10 bg-transparent text-sm dark:border-white/20 dark:bg-gray-900"
|
||||
onChange={changeTheme}
|
||||
value={theme}
|
||||
>
|
||||
<option value="system">System</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="light">Light</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>Clear all chats</div>
|
||||
<button
|
||||
className="btn relative bg-red-600 text-white hover:bg-red-800"
|
||||
type="button"
|
||||
id="clearConvosBtn"
|
||||
onClick={clearConvos}
|
||||
>
|
||||
{confirmClear ? (
|
||||
<div
|
||||
className="flex w-full items-center justify-center gap-2"
|
||||
id="clearConvosTxt"
|
||||
>
|
||||
<CheckIcon className="h-5 w-5" /> Confirm Clear
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="flex w-full items-center justify-center gap-2"
|
||||
id="clearConvosTxt"
|
||||
>
|
||||
Clear
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
19
client/src/components/svg/GearIcon.jsx
Normal file
19
client/src/components/svg/GearIcon.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
export default function GearIcon() {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -28,7 +28,7 @@ const AlertDialogOverlay = React.forwardRef<
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
'animate-in fade-in fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity',
|
||||
'animate-in fade-in fixed inset-0 z-50 bg-gray-500/90 dark:bg-gray-800/90 transition-opacity',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -24,7 +24,7 @@ const DialogOverlay = React.forwardRef<
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
className={cn(
|
||||
'data-[state=closed]:animate-out data-[state=open]:fade-in data-[state=closed]:fade-out fixed inset-0 z-[999] bg-black/50 backdrop-blur-sm transition-all duration-100',
|
||||
'data-[state=closed]:animate-out data-[state=open]:fade-in data-[state=closed]:fade-out fixed inset-0 z-[999] bg-gray-500/90 dark:bg-gray-800/90 transition-all duration-100',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -42,14 +42,14 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0 fixed z-[999] grid w-full gap-4 rounded-b-lg bg-white p-6 sm:max-w-lg sm:rounded-lg',
|
||||
'animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0 fixed z-[999] grid w-full gap-4 rounded-b-lg bg-white pb-6 sm:rounded-lg md:w-[680px]',
|
||||
'dark:bg-slate-900',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
|
||||
<DialogPrimitive.Close className="absolute right-4 top-[1.88rem] rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
|
||||
<X className="h-4 w-4 text-black dark:text-white" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
@@ -59,13 +59,19 @@ const DialogContent = React.forwardRef<
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col space-y-2 border-b border-black/10 p-6 text-center dark:border-white/10 sm:text-left',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogHeader.displayName = 'DialogHeader';
|
||||
|
||||
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn('flex flex-col-reverse sm:flex-row sm:justify-between sm:space-x-2', className)}
|
||||
className={cn('flex flex-col-reverse sm:flex-row sm:justify-between sm:space-x-2 px-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -10,28 +10,22 @@ import {
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
const DialogTemplate = forwardRef((props, ref) => {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
main,
|
||||
buttons,
|
||||
leftButtons,
|
||||
selection,
|
||||
className
|
||||
} = props;
|
||||
const { title, description, main, buttons, leftButtons, selection, className } = props;
|
||||
const { selectHandler, selectClasses, selectText } = selection || {};
|
||||
|
||||
const defaultSelect =
|
||||
'bg-gray-900 text-white transition-colors hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900';
|
||||
return (
|
||||
<DialogContent ref={ref} className={cn('shadow-2xl dark:bg-gray-800', className || '')}>
|
||||
<DialogContent ref={ref} className={cn('shadow-2xl dark:bg-gray-900', className || '')}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-gray-800 dark:text-white">{title}</DialogTitle>
|
||||
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
||||
{description}
|
||||
</DialogDescription>
|
||||
{description && (
|
||||
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
||||
{description}
|
||||
</DialogDescription>
|
||||
)}
|
||||
</DialogHeader>
|
||||
{main ? main : null}
|
||||
<div className="px-6">{main ? main : null}</div>
|
||||
<DialogFooter>
|
||||
<div>{leftButtons ? leftButtons : null}</div>
|
||||
<div className="flex gap-2">
|
||||
|
||||
@@ -26,10 +26,14 @@ export const ThemeProvider = ({ initialTheme, children }) => {
|
||||
|
||||
const rawSetTheme = (rawTheme) => {
|
||||
const root = window.document.documentElement;
|
||||
const isDark = rawTheme === 'dark';
|
||||
let isDark = rawTheme === 'dark';
|
||||
|
||||
if (rawTheme === 'system') {
|
||||
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
root.classList.remove(isDark ? 'light' : 'dark');
|
||||
root.classList.add(rawTheme);
|
||||
root.classList.add(isDark ? 'dark' : 'light');
|
||||
|
||||
localStorage.setItem('color-theme', rawTheme);
|
||||
};
|
||||
|
||||
@@ -132,6 +132,24 @@
|
||||
transition: all 1s ease-in-out;
|
||||
} */
|
||||
|
||||
select {
|
||||
--tw-shadow: 0 0 transparent;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%238e8ea0' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
||||
background-position: right .5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
background-color: #fff;
|
||||
border-color: #8e8ea0;
|
||||
border-radius: 0;
|
||||
border-width: 1px;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
padding: .5rem .75rem;
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: overlay;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const even =
|
||||
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800 hover:bg-gray-100/25 hover:text-gray-700 dark:hover:bg-gray-900 dark:hover:text-gray-200';
|
||||
const odd =
|
||||
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654] hover:bg-gray-100/40 hover:text-gray-700 dark:hover:bg-[#3b3d49] dark:hover:text-gray-200';
|
||||
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-gray-1000 hover:bg-gray-100/40 hover:text-gray-700 dark:hover:bg-[#3b3d49] dark:hover:text-gray-200';
|
||||
|
||||
export default function buildTree(messages, groupAll = false) {
|
||||
if (messages === null) return null;
|
||||
|
||||
29
client/src/utils/getError.ts
Normal file
29
client/src/utils/getError.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
const isJson = (str) => {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const getError = (text) => {
|
||||
const errorMessage = text.length > 512 ? text.slice(0, 512) + '...' : text;
|
||||
const match = text.match(/\{[^{}]*\}/);
|
||||
let json = match ? match[0] : '';
|
||||
if (isJson(json)) {
|
||||
json = JSON.parse(json);
|
||||
if (json.code === 'invalid_api_key') {
|
||||
return 'Invalid API key. Please check your API key and try again. You can do this by clicking on the model logo in the left corner of the textbox and selecting "Set Token" for the current selected endpoint. Thank you for your understanding.';
|
||||
} else if (json.type === 'insufficient_quota') {
|
||||
return 'We apologize for any inconvenience caused. The default API key has reached its limit. To continue using this service, please set up your own API key. You can do this by clicking on the model logo in the left corner of the textbox and selecting "Set Token" for the current selected endpoint. Thank you for your understanding.';
|
||||
} else {
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`;
|
||||
}
|
||||
} else {
|
||||
return `Oops! Something went wrong. Please try again in a few moments. Here's the specific error message we encountered: ${errorMessage}`;
|
||||
}
|
||||
};
|
||||
|
||||
export default getError;
|
||||
@@ -39,7 +39,8 @@ module.exports = {
|
||||
'600': '#565869',
|
||||
'700': '#40414f', // Replacing .dark .dark:bg-gray-700 and .dark .dark:hover:bg-gray-700:hover
|
||||
'800': '#343541', // Replacing .dark .dark:bg-gray-800, .bg-gray-800, and .dark .dark:hover:bg-gray-800\/90
|
||||
'900': '#202123' // Replacing .dark .dark:bg-gray-900, .bg-gray-900, and .dark .dark:hover:bg-gray-900:hover
|
||||
'900': '#202123', // Replacing .dark .dark:bg-gray-900, .bg-gray-900, and .dark .dark:hover:bg-gray-900:hover
|
||||
'1000': '#444654'
|
||||
},
|
||||
green: {
|
||||
50: "#f1f9f7",
|
||||
|
||||
@@ -102,3 +102,6 @@ Use the conventions found in the `data-provider` directory for handling data ser
|
||||
|
||||
Use [Recoil](https://recoiljs.org/) for state management, but *DO NOT pollute the global state with unnecessary data*. Instead, use local state or props for data that is only used within a component or passed down from parent to child.
|
||||
|
||||
##
|
||||
|
||||
## [Go Back to ReadMe](../../README.md)
|
||||
@@ -32,15 +32,16 @@
|
||||
|
||||
**Create a MongoDB database**
|
||||
|
||||
- Navigate to https://www.mongodb.com/ and Sign In or Create an account
|
||||
- Create a new project
|
||||
- Build a Database using the free plan and name the cluster (example: chatgpt-clone)
|
||||
- Use the "Username and Password" method for authentication
|
||||
- Add your current IP to the access list
|
||||
- In the Database Deployment tab, click on Connect
|
||||
- "Choose a connection method" select "Connect your application"
|
||||
- Driver = Node.js / Version = 4.1 or later
|
||||
- Copy the connection string, fill in your password and remove `&w=majority` from default connection string.
|
||||
Navigate to https://www.mongodb.com/ and Sign In or Create an account
|
||||
|
||||
- Create a new project
|
||||
- Build a Database using the free plan and name the cluster (example: chatgpt-clone)
|
||||
- Use the "Username and Password" method for authentication
|
||||
- Add your current IP to the access list
|
||||
- In the Database Deployment tab, click on Connect
|
||||
- "Choose a connection method" select "Connect your application"
|
||||
- Driver = Node.js / Version = 4.1 or later
|
||||
- Copy the connection string, fill in your password and remove `&w=majority` from default connection string.
|
||||
|
||||
|
||||
##
|
||||
@@ -52,12 +53,14 @@
|
||||
|
||||
**Get your Bing Access Token**
|
||||
|
||||
- Using MS Edge, navigate to bing.com
|
||||
- Make sure you are logged in
|
||||
- Open the DevTools by pressing F12 on your keyboard
|
||||
- Click on the tab "Application" (On the left of the DevTools)
|
||||
- Expand the "Cookies" (Under "Storage")
|
||||
- Copy the value of the "\_U" cookie
|
||||
Please follow the **[updated instructions.](https://github.com/danny-avila/chatgpt-clone/issues/370#issuecomment-1560382302)**
|
||||
|
||||
~~Using MS Edge, navigate to bing.com~~
|
||||
- ~~Make sure you are logged in~~
|
||||
- ~~Open the DevTools by pressing F12 on your keyboard~~
|
||||
- ~~Click on the tab "Application" (On the left of the DevTools)~~
|
||||
- ~~Expand the "Cookies" (Under "Storage")~~
|
||||
- ~~Copy the value of the "\_U" cookie~~
|
||||
|
||||
##
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# Linux Installation
|
||||
Thanks to @DavidDev1334 !
|
||||
## **Recommended : [Docker Install](docker_install.md)**
|
||||
|
||||
##
|
||||
|
||||
## **Manual Installation**
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before installing ChatGPT-Clone, make sure your machine has the following prerequisites installed:
|
||||
@@ -12,13 +15,13 @@ Before installing ChatGPT-Clone, make sure your machine has the following prereq
|
||||
|
||||
## Installation Steps
|
||||
|
||||
1. Clone the repository:
|
||||
## 1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/danny-avila/chatgpt-clone.git
|
||||
```
|
||||
|
||||
2. Extract the content in your desired location:
|
||||
## 2. Extract the content in your desired location:
|
||||
|
||||
```bash
|
||||
cd chatgpt-clone
|
||||
@@ -27,7 +30,7 @@ unzip chatgpt-clone.zip -d /usr/local/
|
||||
|
||||
Note: The above command extracts the files to "/usr/local/chatgpt-clone". If you want to install the files to a different location, modify the instructions accordingly.
|
||||
|
||||
3. Enable the Conversation search feature: (optional)
|
||||
## 3. Enable the Conversation search feature: (optional)
|
||||
|
||||
- Download MeiliSearch latest release from: https://github.com/meilisearch/meilisearch/releases
|
||||
- Copy it to "/usr/local/chatgpt-clone/"
|
||||
@@ -41,7 +44,7 @@ Note: The above command extracts the files to "/usr/local/chatgpt-clone". If you
|
||||
|
||||
Note: Replace "YOUR_MASTER_KEY" with the generated master key, which you saved earlier.
|
||||
|
||||
4. Install Node.js:
|
||||
## 4. Install Node.js:
|
||||
|
||||
Open a terminal and run the following commands:
|
||||
|
||||
@@ -50,7 +53,7 @@ curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
```
|
||||
|
||||
5. Create a MongoDB database:
|
||||
## 5. Create a MongoDB database:
|
||||
|
||||
- Navigate to https://www.mongodb.com/ and sign in or create an account.
|
||||
- Create a new project.
|
||||
@@ -62,18 +65,21 @@ sudo apt-get install -y nodejs
|
||||
- Driver = Node.js / Version = 4.1 or later.
|
||||
- Copy the connection string and save it somewhere (you will need it later).
|
||||
|
||||
6. Get your OpenAI API key - Visit https://platform.openai.com/account/api-keys and save your API key somewhere safe (you will need it later)
|
||||
## 6. Get your OpenAI API key
|
||||
- Visit https://platform.openai.com/account/api-keys and save your API key somewhere safe (you will need it later)
|
||||
|
||||
7. Get your Bing Access Token
|
||||
## 7. Get your Bing Access Token
|
||||
|
||||
Please follow the **[updated instructions.](https://github.com/danny-avila/chatgpt-clone/issues/370#issuecomment-1560382302)**
|
||||
|
||||
~~Using MS Edge, navigate to bing.com~~
|
||||
- ~~Make sure you are logged in~~
|
||||
- ~~Open the DevTools by pressing F12 on your keyboard~~
|
||||
- ~~Click on the tab "Application" (On the left of the DevTools)~~
|
||||
- ~~Expand the "Cookies" (Under "Storage")~~
|
||||
- ~~Copy the value of the "\_U" cookie~~
|
||||
|
||||
- Using a web browser, navigate to bing.com
|
||||
- Make sure you are logged in
|
||||
- Open the browser DevTools by pressing F12 on your keyboard
|
||||
- Click on the tab "Application" (On the left of the DevTools)
|
||||
- Expand the "Cookies" (Under "Storage")
|
||||
- You need to copy the value of the "_U" cookie, save it somewhere, you will need it later
|
||||
|
||||
8. Create the ".env" File
|
||||
## 8. Create the ".env" File
|
||||
|
||||
You will need all your credentials, (API keys, access tokens, and MongoDB Connection String, MeiliSearch Master Key)
|
||||
|
||||
@@ -93,7 +99,7 @@ Setup the app:
|
||||
1. Run `npm ci`
|
||||
2. Run `npm run frontend`
|
||||
|
||||
Start the app:
|
||||
## Start the app:
|
||||
1. Run `npm run backend`
|
||||
2. Run `meilisearch --master-key put_your_meilesearch_Master_Key_here` (Only if SEARCH=TRUE)
|
||||
3. Visit http://localhost:3080 (default port) & enjoy
|
||||
@@ -1,10 +1,11 @@
|
||||
# Mac Install
|
||||
Thanks to @heathriel!
|
||||
## **Recommended : [Docker Install](docker_install.md)**
|
||||
|
||||
##
|
||||
|
||||
**Recommended - [Docker Install](docker_install.md)**
|
||||
## **Manual Installation**
|
||||
|
||||
## **Install the prerequisites**:
|
||||
## Install the prerequisites:
|
||||
- Install Homebrew (if not already installed) by following the instructions on https://brew.sh/
|
||||
- Install Node.js and npm by running `brew install node`
|
||||
- Install MongoDB (if not using Docker) by running `brew tap mongodb/brew` and `brew install mongodb-community`
|
||||
@@ -22,14 +23,14 @@ Thanks to @heathriel!
|
||||
- Copy the connection string and save it somewhere(you will need it later)
|
||||
|
||||
|
||||
## **Instructions:**
|
||||
## Instructions:
|
||||
|
||||
- Open Terminal and clone the repository by running git clone https://github.com/danny-avila/chatgpt-clone.git
|
||||
- Change into the cloned directory by running cd chatgpt-clone
|
||||
- If using MongoDB Atlas, remove &w=majority from the default connection string
|
||||
Follow the instructions for setting up proxies, access tokens, and user system:
|
||||
|
||||
### **Access Tokens:**
|
||||
### Access Tokens:
|
||||
|
||||
**Get your OpenAI API key**
|
||||
|
||||
@@ -40,16 +41,19 @@ Follow the instructions for setting up proxies, access tokens, and user system:
|
||||
- To get your Access token for ChatGPT 'Free Version', log in to chat.openai.com, then visit https://chat.openai.com/api/auth/session.
|
||||
- Warning: There may be a high chance of your account being banned with this method. Continue doing so at your own risk.
|
||||
|
||||
**BingAI Instructions:**
|
||||
|
||||
- To get the Bing Access Token, navigate to bing.com using a web browser such as Chrome or Safari, and ensure you're logged in.
|
||||
- Open the Developer Tools (in Chrome or Safari, press Cmd + Option + I).
|
||||
- Click on the "Application" tab (Chrome) or "Storage" tab (Safari).
|
||||
- Expand the "Cookies" section under "Storage".
|
||||
- Copy the value of the "_U" cookie and save it somewhere. You'll need it later.
|
||||
**Get your Bing Access Token**
|
||||
|
||||
Please follow the **[updated instructions.](https://github.com/danny-avila/chatgpt-clone/issues/370#issuecomment-1560382302)**
|
||||
|
||||
~~Using MS Edge, navigate to bing.com~~
|
||||
- ~~Make sure you are logged in~~
|
||||
- ~~Open the DevTools by pressing F12 on your keyboard~~
|
||||
- ~~Click on the tab "Application" (On the left of the DevTools)~~
|
||||
- ~~Expand the "Cookies" (Under "Storage")~~
|
||||
- ~~Copy the value of the "\_U" cookie~~
|
||||
|
||||
|
||||
## **Setup Instruction**
|
||||
## Setup Instruction
|
||||
- Create a .env file in the api directory by running cp api/.env.example api/.env and edit the file with your preferred text editor, adding the required API keys, access tokens, and MongoDB connection string
|
||||
- Run npm ci from root directory `npm ci`
|
||||
- Build the client by running `npm run frontend`
|
||||
@@ -85,7 +89,7 @@ MEILISEARCH_KEY=your_master_key_goes_here
|
||||
- In the chatgpt-clone directory, start the application by running `npm run backend`
|
||||
Visit http://localhost:3080 (default port) & enjoy
|
||||
|
||||
## **Optional but recommended:**
|
||||
## Optional but recommended:
|
||||
|
||||
- Create a script to automate the starting process by creating a new file named start_chatgpt.sh in the chatgpt-clone directory and pasting the following code:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Windows Install
|
||||
|
||||
### Recommended:
|
||||
### **[Docker](docker.md)**
|
||||
### **[Docker](docker_install.md)**
|
||||
or
|
||||
### **[Automated Installer (Windows)](https://github.com/fuegovic/chatgpt-clone-local-installer)**
|
||||
(Includes a Startup and Update Utility)
|
||||
@@ -46,13 +46,16 @@ or
|
||||
- **Get your OpenAI API key**
|
||||
- here: https://platform.openai.com/account/api-keys and save it somewhere safe (you will need it later)
|
||||
|
||||
- **Get your Bing Access Token**
|
||||
- Using MS Edge, navigate to bing.com
|
||||
- Make sure you are logged in
|
||||
- Open the DevTools by pressing F12 on your keyboard
|
||||
- Click on the tab "Application" (On the left of the DevTools)
|
||||
- Expand the "Cookies" (Under "Storage")
|
||||
- You need to copy the value of the "\_U" cookie, save it somewhere, you will need it later
|
||||
**Get your Bing Access Token**
|
||||
|
||||
Please follow the **[updated instructions.](https://github.com/danny-avila/chatgpt-clone/issues/370#issuecomment-1560382302)**
|
||||
|
||||
~~Using MS Edge, navigate to bing.com~~
|
||||
- ~~Make sure you are logged in~~
|
||||
- ~~Open the DevTools by pressing F12 on your keyboard~~
|
||||
- ~~Click on the tab "Application" (On the left of the DevTools)~~
|
||||
- ~~Expand the "Cookies" (Under "Storage")~~
|
||||
- ~~Copy the value of the "\_U" cookie~~
|
||||
|
||||
- **Create the ".env" File**
|
||||
You will need all your credentials, (API keys, access tokens, and Mongo Connection String, MeileSearch Master Key)
|
||||
@@ -12,7 +12,41 @@ test.describe('Landing suite', () => {
|
||||
test('Landing title', async () => {
|
||||
const page = await myBrowser.newPage();
|
||||
await page.goto('http://localhost:3080/');
|
||||
const pageTitle = await page.textContent('#landing-title')
|
||||
const pageTitle = await page.textContent('#landing-title');
|
||||
expect(pageTitle.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('Create Conversation', async () => {
|
||||
const page = await myBrowser.newPage();
|
||||
await page.goto('http://localhost:3080/');
|
||||
|
||||
async function getItems() {
|
||||
const navDiv = await page.waitForSelector('nav > div');
|
||||
if (!navDiv) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const items = await navDiv.$$('a.group');
|
||||
return items || [];
|
||||
}
|
||||
|
||||
// Wait for the page to load and the SVG loader to disappear
|
||||
await page.waitForSelector('nav > div');
|
||||
await page.waitForSelector('nav > div > div > svg', { state: 'detached' });
|
||||
|
||||
let beforeAdding = (await getItems()).length;
|
||||
|
||||
const input = await page.locator('form').getByRole('textbox');
|
||||
await input.click();
|
||||
await input.fill('Hi!');
|
||||
|
||||
// Send the message
|
||||
await page.locator('form').getByRole('button').nth(1).click();
|
||||
|
||||
// Wait for the message to be sent
|
||||
await page.waitForTimeout(15000);
|
||||
let afterAdding = (await getItems()).length;
|
||||
|
||||
expect(afterAdding).toBeGreaterThan(beforeAdding);
|
||||
});
|
||||
});
|
||||
|
||||
59
e2e/specs/nav.spec.js
Normal file
59
e2e/specs/nav.spec.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test.describe('Navigation suite', () => {
|
||||
let myBrowser;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
myBrowser = await browser.newContext({
|
||||
storageState: 'e2e/auth.json',
|
||||
});
|
||||
});
|
||||
|
||||
test('Navigation bar', async () => {
|
||||
const page = await myBrowser.newPage();
|
||||
await page.goto('http://localhost:3080/');
|
||||
|
||||
await page.locator('[id="headlessui-menu-button-\\:r0\\:"]').click();
|
||||
const navBar = await page.locator('[id="headlessui-menu-button-\\:r0\\:"]').isVisible();
|
||||
expect(navBar).toBeTruthy();
|
||||
});
|
||||
|
||||
test('Settings modal', async () => {
|
||||
const page = await myBrowser.newPage();
|
||||
await page.goto('http://localhost:3080/');
|
||||
await page.locator('[id="headlessui-menu-button-\\:r0\\:"]').click();
|
||||
await page.getByText('Settings').click();
|
||||
|
||||
const modal = await page.getByRole('dialog', { name: 'Settings' }).isVisible();
|
||||
expect(modal).toBeTruthy();
|
||||
|
||||
const modalTitle = await page.getByRole('heading', { name: 'Settings' }).textContent();
|
||||
expect(modalTitle.length).toBeGreaterThan(0);
|
||||
expect(modalTitle).toEqual('Settings');
|
||||
|
||||
const modalTabList = await page.getByRole('tablist', { name: 'Settings' }).isVisible();
|
||||
expect(modalTabList).toBeTruthy();
|
||||
|
||||
const generalTabPanel = await page.getByRole('tabpanel', { name: 'General' }).isVisible();
|
||||
expect(generalTabPanel).toBeTruthy();
|
||||
|
||||
const modalClearConvos = await page.getByRole('button', { name: 'Clear' }).isVisible();
|
||||
expect(modalClearConvos).toBeTruthy();
|
||||
|
||||
const modalTheme = await page.getByRole('combobox');
|
||||
expect(modalTheme.isVisible()).toBeTruthy();
|
||||
|
||||
async function changeMode(theme) {
|
||||
// change the value to 'dark' and 'light' and see if the theme changes
|
||||
await modalTheme.selectOption({ label: theme });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check if the HTML element has the theme class
|
||||
const html = await page.$eval('html', (element, theme) => element.classList.contains(theme.toLowerCase()), theme);
|
||||
expect(html).toBeTruthy();
|
||||
}
|
||||
|
||||
await changeMode('Dark');
|
||||
await changeMode('Light');
|
||||
});
|
||||
});
|
||||
24
e2e/specs/popup.spec.js
Normal file
24
e2e/specs/popup.spec.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test.describe('Endpoints Presets suite', () => {
|
||||
let myBrowser;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
myBrowser = await browser.newContext({
|
||||
storageState: 'e2e/auth.json',
|
||||
});
|
||||
});
|
||||
|
||||
test('Endpoints Suite', async () => {
|
||||
const page = await myBrowser.newPage();
|
||||
await page.goto('http://localhost:3080/');
|
||||
await page.getByRole('button', { name: 'New Topic' }).click();
|
||||
|
||||
const endpointItem = await page.getByRole('menuitemradio', { name: 'OpenAI' })
|
||||
await endpointItem.click();
|
||||
|
||||
await page.getByRole('button', { name: 'New Topic' }).click();
|
||||
// Check if the active class is set on the selected endpoint
|
||||
expect(await endpointItem.getAttribute('class')).toContain('active');
|
||||
});
|
||||
});
|
||||
98
package-lock.json
generated
98
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "chatgpt-clone",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "chatgpt-clone",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"api",
|
||||
@@ -35,12 +35,12 @@
|
||||
},
|
||||
"api": {
|
||||
"name": "chat-backend",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@keyv/mongo": "^2.1.8",
|
||||
"@waylaidwanderer/chatgpt-api": "^1.36.0",
|
||||
"@waylaidwanderer/chatgpt-api": "^1.37.0",
|
||||
"axios": "^1.3.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cookie": "^0.5.0",
|
||||
@@ -57,7 +57,7 @@
|
||||
"keyv": "^4.5.2",
|
||||
"keyv-file": "^0.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"meilisearch": "^0.32.3",
|
||||
"meilisearch": "^0.33.0",
|
||||
"mongoose": "^7.1.1",
|
||||
"nodemailer": "^6.9.1",
|
||||
"openai": "^3.1.0",
|
||||
@@ -76,7 +76,7 @@
|
||||
},
|
||||
"client": {
|
||||
"name": "chat-frontend",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
@@ -1646,12 +1646,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz",
|
||||
"integrity": "sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==",
|
||||
"version": "7.22.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz",
|
||||
"integrity": "sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.5"
|
||||
"@babel/types": "^7.22.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -2774,16 +2774,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx": {
|
||||
"version": "7.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.21.5.tgz",
|
||||
"integrity": "sha512-ELdlq61FpoEkHO6gFRpfj0kUgSwQTGoaEU8eMRoS8Dv3v6e7BjEAj5WMtIBRdHUeAioMhKP5HyxNzNnP+heKbA==",
|
||||
"version": "7.22.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.3.tgz",
|
||||
"integrity": "sha512-JEulRWG2f04a7L8VWaOngWiK6p+JOSpB+DAtwfJgOaej1qdbNxqtK7MwTBHjUA10NeFcszlFNqCdbRcirzh2uQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.18.6",
|
||||
"@babel/helper-module-imports": "^7.21.4",
|
||||
"@babel/helper-plugin-utils": "^7.21.5",
|
||||
"@babel/plugin-syntax-jsx": "^7.21.4",
|
||||
"@babel/types": "^7.21.5"
|
||||
"@babel/types": "^7.22.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -3245,9 +3245,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz",
|
||||
"integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==",
|
||||
"version": "7.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz",
|
||||
"integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.21.5",
|
||||
@@ -3756,9 +3756,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/postcss-progressive-custom-properties": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-2.2.0.tgz",
|
||||
"integrity": "sha512-qtJ2Jgf5bQW65OK7JaR0dw+XL3tc3BN99g+I5cRdik++HpyZitrKKxIwDGb3OHp2Yo3PZKuiX8pXljqmLHT/eg==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-2.3.0.tgz",
|
||||
"integrity": "sha512-Zd8ojyMlsL919TBExQ1I0CTpBDdyCpH/yOdqatZpuC3sd22K4SwC7+Yez3Q/vmXMWSAl+shjNeFZ7JMyxMjK+Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -6495,6 +6495,15 @@
|
||||
"@testing-library/dom": ">=7.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@timefox/bic-sydney": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@timefox/bic-sydney/-/bic-sydney-1.1.4.tgz",
|
||||
"integrity": "sha512-ONeS0weT+ZoE471TDdzPqkKRk+VFr7sEL5+qEq1nIur6XMuVZ8cvlBicUNHfhYKIavkOM8xmBnk2dfVFQ54aiQ==",
|
||||
"dependencies": {
|
||||
"fetch-undici": "^3.0.1",
|
||||
"undici": "^5.22.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
@@ -7029,12 +7038,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@waylaidwanderer/chatgpt-api": {
|
||||
"version": "1.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.36.0.tgz",
|
||||
"integrity": "sha512-3QRNqLXT4ZhuH5Rd6siKR8E04SWFps7k8JqR2sniro4axFvPZEnBiEGhP97xGP9akYF3N8lMMTiNwJMsLSOeHg==",
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.37.0.tgz",
|
||||
"integrity": "sha512-fJfNvfZliuzRlTEY2kb3u6psfAaFI88YSU1RJ6J865BRk90Km3k62br/npCGLCtbREklTR18bMYhNXVM/BuFow==",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@fastify/cors": "^8.2.0",
|
||||
"@timefox/bic-sydney": "^1.1.2",
|
||||
"@waylaidwanderer/fastify-sse-v2": "^3.1.0",
|
||||
"@waylaidwanderer/fetch-event-source": "^3.0.1",
|
||||
"boxen": "^7.0.1",
|
||||
@@ -7042,7 +7052,7 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"fastify": "^4.11.0",
|
||||
"fetch-undici": "^3.0.1",
|
||||
"https-proxy-agent": "^6.0.0",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"inquirer": "^9.1.4",
|
||||
"inquirer-autocomplete-prompt": "^3.0.0",
|
||||
"keyv": "^4.5.2",
|
||||
@@ -7349,9 +7359,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.0.2.tgz",
|
||||
"integrity": "sha512-k2/tQ1+8Zf50dEUJWklUP80LcE/+Ph+OJ6cf2Ff2fD/c/TtCe6ofnCoNMz9UnyxOQYlaAALZtEWETzn+1JjfHg==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
|
||||
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
@@ -12620,9 +12630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-6.2.0.tgz",
|
||||
"integrity": "sha512-4xhCnMpxR9fupa7leh9uJK2P/qjYIeaM9uZ9c1bi1JDSwX2VH9NDk/oKSToNX4gBKa2WT31Mldne7e26ckohLQ==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.0.tgz",
|
||||
"integrity": "sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.0.2",
|
||||
"debug": "4"
|
||||
@@ -16822,9 +16832,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-from-markdown": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz",
|
||||
"integrity": "sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz",
|
||||
"integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^3.0.0",
|
||||
"@types/unist": "^2.0.0",
|
||||
@@ -17018,11 +17028,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/meilisearch": {
|
||||
"version": "0.32.3",
|
||||
"resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.32.3.tgz",
|
||||
"integrity": "sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==",
|
||||
"version": "0.33.0",
|
||||
"resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.33.0.tgz",
|
||||
"integrity": "sha512-bYPb9WyITnJfzf92e7QFK8Rc50DmshFWxypXCs3ILlpNh8pT15A7KSu9Xgnnk/K3G/4vb3wkxxtFS4sxNkWB8w==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "^3.1.5"
|
||||
"cross-fetch": "^3.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/memory-pager": {
|
||||
@@ -17223,9 +17233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/micromark-extension-gfm-task-list-item": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.4.tgz",
|
||||
"integrity": "sha512-9XlIUUVnYXHsFF2HZ9jby4h3npfX10S1coXTnV035QGPgrtNYQq3J6IfIvcCIUAJrrqBVi5BqA/LmaOMJqPwMQ==",
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz",
|
||||
"integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==",
|
||||
"dependencies": {
|
||||
"micromark-factory-space": "^1.0.0",
|
||||
"micromark-util-character": "^1.0.0",
|
||||
@@ -19165,9 +19175,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-double-position-gradients": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-4.0.3.tgz",
|
||||
"integrity": "sha512-Td1+C+kFCadnhRBMMf6D/eiQxjp33eAgwgMcLNYzZPcgXt1iU6vi/qEJ/YObp4nwn3QOtudFBMUOVHoGqmpfiA==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-4.0.4.tgz",
|
||||
"integrity": "sha512-nUAbUXURemLXIrl4Xoia2tiu5z/n8sY+BVDZApoeT9BlpByyrp02P/lFCRrRvZ/zrGRE+MOGLhk8o7VcMCtPtQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -19180,7 +19190,7 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@csstools/postcss-progressive-custom-properties": "^2.2.0",
|
||||
"@csstools/postcss-progressive-custom-properties": "^2.3.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chatgpt-clone",
|
||||
"version": "0.4.6",
|
||||
"version": "0.4.7",
|
||||
"description": "",
|
||||
"workspaces": [
|
||||
"api",
|
||||
|
||||
Reference in New Issue
Block a user