Compare commits
26 Commits
fix/use-cj
...
v0.4.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd5afc09a2 | ||
|
|
c0845ad0b1 | ||
|
|
11b98d3d13 | ||
|
|
4f17e69f1b | ||
|
|
b912e7a3dd | ||
|
|
743a9315ff | ||
|
|
ea2135a237 | ||
|
|
6a1983bc6c | ||
|
|
07796d9e48 | ||
|
|
634849ec12 | ||
|
|
112c6c5b19 | ||
|
|
b8c3ae5e8f | ||
|
|
07fa0f39fd | ||
|
|
4eda4542b7 | ||
|
|
dbfef342e2 | ||
|
|
735eb159db | ||
|
|
bf911074cf | ||
|
|
7ec061c694 | ||
|
|
a6f3eb4c0d | ||
|
|
dc5f9d8474 | ||
|
|
c1349fbfaa | ||
|
|
6f9da5f7df | ||
|
|
10de50416b | ||
|
|
4beb06aa4b | ||
|
|
791b515937 | ||
|
|
8d4ef16b7f |
@@ -10,6 +10,7 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
"plugin:jest/recommended",
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
@@ -64,7 +65,7 @@ module.exports = {
|
||||
{
|
||||
files: ['rollup.config.js', '.eslintrc.js', 'jest.config.js'],
|
||||
env: {
|
||||
node: true
|
||||
node: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -95,7 +96,7 @@ module.exports = {
|
||||
parserOptions: {
|
||||
project: './client/tsconfig.json'
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'jest'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
|
||||
64
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- 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-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Please give as many details as possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Please list the steps needed to reproduce the issue.
|
||||
placeholder: "1. Step 1\n2. Step 2\n3. Step 3"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Mobile (iOS)
|
||||
- Mobile (Android)
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
- 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
|
||||
@@ -1,40 +1,35 @@
|
||||
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] 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
|
||||
|
||||
# How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
|
||||
|
||||
- [ ] Test A
|
||||
- [ ] Test B
|
||||
|
||||
**Test Configuration**:
|
||||
* Firmware version:
|
||||
* Hardware:
|
||||
* Toolchain:
|
||||
* SDK:
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
##
|
||||
|
||||
## [Go Back to ReadMe](../../README.md)
|
||||
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
||||
|
||||
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] 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
|
||||
|
||||
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration:
|
||||
##
|
||||
|
||||
|
||||
### **Test Configuration**:
|
||||
##
|
||||
|
||||
|
||||
## Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
12
README.md
12
README.md
@@ -112,21 +112,11 @@
|
||||
* [Documentation Guidelines](documents/contributions/documentation_guidelines.md)
|
||||
* [Code Standards and Conventions](documents/contributions/coding_conventions.md)
|
||||
* [Testing](documents/contributions/testing.md)
|
||||
* [Pull Request Template](documents/contributions/pull_request_template.md)
|
||||
* [Security](SECURITY.md)
|
||||
* [Contributors](CONTRIBUTORS.md)
|
||||
* [Trello Board](https://trello.com/b/17z094kq/chatgpt-clone)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Report Templates</strong></summary>
|
||||
|
||||
* [Bug Report Template](documents/report_templates/bug_report_template.md)
|
||||
* [Custom Issue Template](documents/report_templates/custom_issue_template.md)
|
||||
* [Feature Request Template](documents/report_templates/feature_request_template.md)
|
||||
</details>
|
||||
|
||||
##
|
||||
### [Alternative Documentation](https://chatgpt-clone.gitbook.io/chatgpt-clone-docs/get-started/docker)
|
||||
|
||||
##
|
||||
|
||||
|
||||
12
SECURITY.md
12
SECURITY.md
@@ -9,7 +9,7 @@ If you discover a security vulnerability within our project, please follow these
|
||||
### Communication channels
|
||||
- **Option 1: GitHub Security Advisory System**: We encourage you to use GitHub's Security Advisory system to report any security vulnerabilities you find. This allows us to receive vulnerability reports directly through GitHub. You can find more information on how to submit a security advisory report in the [GitHub Security Advisories documentation](https://docs.github.com/en/code-security/getting-started-with-security-vulnerability-alerts/about-github-security-advisories).
|
||||
- **Option 2: Github issues**: You can initiate first contact via Github Issues. **Please note that initial contact through Discord should not include any sensitive details.**
|
||||
- **Option 3: Discord Server**: You can join our [Discord community](https://discord.gg/NGaa9RPCft) and initiate first contact in the `#issues` channel. **Please note that initial contact through Discord should not include any sensitive details.**
|
||||
- **Option 3: Discord Server**: You can join our [Discord community](https://discord.gg/5rbRxn4uME) and initiate first contact in the `#issues` channel. **Please note that initial contact through Discord should not include any sensitive details.**
|
||||
|
||||
_After initial contact, we will use this initial contact to establish a private communication channel for further discussion._
|
||||
|
||||
@@ -24,7 +24,7 @@ We will make every effort to acknowledge your report within 72 hours and keep yo
|
||||
## Security Updates and Patching
|
||||
We are committed to maintaining the security of our open-source project named ChatGPT-Clone and promptly addressing any identified vulnerabilities. To ensure the security of our project, we follow these practices:
|
||||
- We prioritize security updates for the current major release of our software.
|
||||
- We actively monitor the GitHub Security Advisory system and the `#security-reports` channel on Discord for any vulnerability reports.
|
||||
- We actively monitor the GitHub Security Advisory system and the `#issues` channel on Discord for any vulnerability reports.
|
||||
- We promptly review and validate reported vulnerabilities and take appropriate actions to address them.
|
||||
- We release security patches and updates in a timely manner to mitigate any identified vulnerabilities.
|
||||
|
||||
@@ -45,3 +45,11 @@ We would like to express our gratitude to the security researchers and community
|
||||
## Bug Bounty Program
|
||||
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
|
||||
|
||||
@@ -59,8 +59,9 @@ OPENAI_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-0301,text-davinci-003,gpt-4
|
||||
##########################
|
||||
|
||||
# Also used for Sydney and jailbreak
|
||||
|
||||
# BingAI Tokens: the "_U" cookies value from bing.com
|
||||
# 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
|
||||
# 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"
|
||||
|
||||
@@ -23,10 +23,10 @@ const askBing = async ({
|
||||
|
||||
const bingAIClient = new BingAIClient({
|
||||
// "_U" cookie from bing.com
|
||||
userToken:
|
||||
process.env.BINGAI_TOKEN == 'user_provided' ? token : process.env.BINGAI_TOKEN ?? null,
|
||||
// userToken:
|
||||
// process.env.BINGAI_TOKEN == 'user_provided' ? token : process.env.BINGAI_TOKEN ?? null,
|
||||
// If the above doesn't work, provide all your cookies as a string instead
|
||||
// cookies: '',
|
||||
cookies: process.env.BINGAI_TOKEN == 'user_provided' ? token : process.env.BINGAI_TOKEN ?? null,
|
||||
debug: false,
|
||||
cache: store,
|
||||
host: process.env.BINGAI_HOST || null,
|
||||
|
||||
@@ -8,6 +8,7 @@ const browserClient = async ({
|
||||
model,
|
||||
token,
|
||||
onProgress,
|
||||
onEventMessage,
|
||||
abortController,
|
||||
userId
|
||||
}) => {
|
||||
@@ -30,7 +31,7 @@ const browserClient = async ({
|
||||
};
|
||||
|
||||
const client = new ChatGPTBrowserClient(clientOptions, store);
|
||||
let options = { onProgress, abortController };
|
||||
let options = { onProgress, onEventMessage, abortController };
|
||||
|
||||
if (!!parentMessageId && !!conversationId) {
|
||||
options = { ...options, parentMessageId, conversationId };
|
||||
|
||||
@@ -2,6 +2,7 @@ require('dotenv').config();
|
||||
const { KeyvFile } = require('keyv-file');
|
||||
const { genAzureEndpoint } = require('../../utils/genAzureEndpoints');
|
||||
const tiktoken = require('@dqbd/tiktoken');
|
||||
const tiktokenModels = require('../../utils/tiktokenModels');
|
||||
const encoding_for_model = tiktoken.encoding_for_model;
|
||||
|
||||
const askClient = async ({
|
||||
@@ -26,9 +27,8 @@ const askClient = async ({
|
||||
};
|
||||
|
||||
const azure = process.env.AZURE_OPENAI_API_KEY ? true : false;
|
||||
if (promptPrefix == null) {
|
||||
promptText = 'You are ChatGPT, a large language model trained by OpenAI.';
|
||||
} else {
|
||||
let promptText = 'You are ChatGPT, a large language model trained by OpenAI.';
|
||||
if (promptPrefix) {
|
||||
promptText = promptPrefix;
|
||||
}
|
||||
const maxContextTokens = model === 'gpt-4' ? 8191 : model === 'gpt-4-32k' ? 32767 : 4095; // 1 less than maximum
|
||||
@@ -68,25 +68,26 @@ const askClient = async ({
|
||||
...(parentMessageId && conversationId ? { parentMessageId, conversationId } : {})
|
||||
};
|
||||
|
||||
const enc = encoding_for_model(model);
|
||||
const text_tokens = enc.encode(text);
|
||||
const prompt_tokens = enc.encode(promptText);
|
||||
// console.log("Prompt tokens = ", prompt_tokens.length);
|
||||
// console.log("Message Tokens = ", text_tokens.length);
|
||||
|
||||
let usage = {};
|
||||
let enc = null;
|
||||
try {
|
||||
enc = encoding_for_model(tiktokenModels.has(model) ? model : 'gpt-3.5-turbo');
|
||||
usage.prompt_tokens = (enc.encode(promptText)).length + (enc.encode(text)).length;
|
||||
} catch (e) {
|
||||
console.log('Error encoding prompt text', e);
|
||||
}
|
||||
|
||||
const res = await client.sendMessage(text, { ...options, userId });
|
||||
// return res;
|
||||
// create a new response object that includes the token counts
|
||||
const newRes = {
|
||||
...res,
|
||||
usage: {
|
||||
prompt_tokens: prompt_tokens.length,
|
||||
completion_tokens: text_tokens.length,
|
||||
total_tokens: prompt_tokens.length + text_tokens.length
|
||||
}
|
||||
};
|
||||
|
||||
return newRes;
|
||||
try {
|
||||
usage.completion_tokens = (enc.encode(res.response)).length;
|
||||
usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
|
||||
res.usage = usage;
|
||||
} catch (e) {
|
||||
console.log('Error encoding response text', e);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
module.exports = { askClient };
|
||||
|
||||
@@ -266,7 +266,7 @@ class GoogleAgent {
|
||||
const user = opts.user || null;
|
||||
const conversationId = opts.conversationId || crypto.randomUUID();
|
||||
const parentMessageId = opts.parentMessageId || '00000000-0000-0000-0000-000000000000';
|
||||
const userMessageId = crypto.randomUUID();
|
||||
const userMessageId = opts.overrideParentMessageId || crypto.randomUUID();
|
||||
const responseMessageId = crypto.randomUUID();
|
||||
const messages = await this.loadHistory(conversationId, this.options?.parentMessageId);
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ const convoSchema = mongoose.Schema(
|
||||
examples: [{ type: mongoose.Schema.Types.Mixed }],
|
||||
...conversationPreset,
|
||||
// for bingAI only
|
||||
bingConversationId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
jailbreakConversationId: {
|
||||
type: String,
|
||||
default: null
|
||||
|
||||
@@ -23,14 +23,12 @@
|
||||
"@keyv/mongo": "^2.1.8",
|
||||
"@waylaidwanderer/chatgpt-api": "^1.36.0",
|
||||
"axios": "^1.3.4",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cookie": "^0.5.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint": "^8.41.0",
|
||||
"express": "^4.18.2",
|
||||
"googleapis": "^118.0.0",
|
||||
"handlebars": "^4.7.7",
|
||||
|
||||
@@ -40,7 +40,7 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||
jailbreakConversationId: req.body?.jailbreakConversationId ?? null,
|
||||
systemMessage: req.body?.systemMessage ?? null,
|
||||
context: req.body?.context ?? null,
|
||||
toneStyle: req.body?.toneStyle ?? 'fast',
|
||||
toneStyle: req.body?.toneStyle ?? 'creative',
|
||||
token: req.body?.token ?? null
|
||||
};
|
||||
else
|
||||
@@ -51,7 +51,7 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||
conversationSignature: req.body?.conversationSignature ?? null,
|
||||
clientId: req.body?.clientId ?? null,
|
||||
invocationId: req.body?.invocationId ?? null,
|
||||
toneStyle: req.body?.toneStyle ?? 'fast',
|
||||
toneStyle: req.body?.toneStyle ?? 'creative',
|
||||
token: req.body?.token ?? null
|
||||
};
|
||||
|
||||
@@ -110,7 +110,7 @@ const ask = async ({
|
||||
|
||||
try {
|
||||
let lastSavedTimestamp = 0;
|
||||
const { onProgress: progressCallback, getPartialText } = createOnProgress({
|
||||
const { onProgress: progressCallback } = createOnProgress({
|
||||
onProgress: ({ text }) => {
|
||||
const currentTimestamp = Date.now();
|
||||
if (currentTimestamp - lastSavedTimestamp > 500) {
|
||||
@@ -129,10 +129,15 @@ const ask = async ({
|
||||
}
|
||||
});
|
||||
const abortController = new AbortController();
|
||||
let bingConversationId = null;
|
||||
if (!isNewConversation) {
|
||||
const convo = await getConvo(req.user.id, conversationId);
|
||||
bingConversationId = convo.bingConversationId;
|
||||
}
|
||||
let response = await askBing({
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
conversationId: bingConversationId ?? conversationId,
|
||||
...endpointOption,
|
||||
onProgress: progressCallback.call(null, {
|
||||
res,
|
||||
@@ -147,7 +152,7 @@ const ask = async ({
|
||||
const newConversationId = endpointOption?.jailbreak
|
||||
? response.jailbreakConversationId
|
||||
: response.conversationId || conversationId;
|
||||
const newUserMassageId =
|
||||
const newUserMessageId =
|
||||
response.parentMessageId || response.details.requestId || userMessageId;
|
||||
const newResponseMessageId = response.messageId || response.details.messageId;
|
||||
|
||||
@@ -156,10 +161,11 @@ const ask = async ({
|
||||
response.response || response.details.spokenText || '**Bing refused to answer.**';
|
||||
|
||||
let responseMessage = {
|
||||
conversationId: newConversationId,
|
||||
conversationId,
|
||||
bingConversationId: newConversationId,
|
||||
messageId: responseMessageId,
|
||||
newMessageId: newResponseMessageId,
|
||||
parentMessageId: overrideParentMessageId || newUserMassageId,
|
||||
parentMessageId: overrideParentMessageId || newUserMessageId,
|
||||
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
|
||||
text: await handleText(response, true),
|
||||
suggestions:
|
||||
@@ -173,31 +179,7 @@ const ask = async ({
|
||||
await saveMessage(responseMessage);
|
||||
responseMessage.messageId = newResponseMessageId;
|
||||
|
||||
// STEP2 update the convosation.
|
||||
|
||||
// First update conversationId if needed
|
||||
// Note!
|
||||
// Bing API will not use our conversationId at the first time,
|
||||
// so change the placeholder conversationId to the real one.
|
||||
// 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.
|
||||
|
||||
let conversationUpdate = { conversationId: newConversationId, endpoint: 'bingAI' };
|
||||
if (conversationId != newConversationId)
|
||||
if (isNewConversation) {
|
||||
// change the conversationId to new one
|
||||
conversationUpdate = {
|
||||
...conversationUpdate,
|
||||
conversationId: conversationId,
|
||||
newConversationId: newConversationId
|
||||
};
|
||||
} else {
|
||||
// create new conversation
|
||||
conversationUpdate = {
|
||||
...conversationUpdate,
|
||||
...endpointOption
|
||||
};
|
||||
}
|
||||
let conversationUpdate = { conversationId, bingConversationId: newConversationId, endpoint: 'bingAI' };
|
||||
|
||||
if (endpointOption?.jailbreak) {
|
||||
conversationUpdate.jailbreak = true;
|
||||
@@ -210,20 +192,16 @@ const ask = async ({
|
||||
}
|
||||
|
||||
await saveConvo(req.user.id, conversationUpdate);
|
||||
conversationId = newConversationId;
|
||||
|
||||
// STEP3 update the user message
|
||||
userMessage.conversationId = newConversationId;
|
||||
userMessage.messageId = newUserMassageId;
|
||||
userMessage.messageId = newUserMessageId;
|
||||
|
||||
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
|
||||
if (!overrideParentMessageId)
|
||||
await saveMessage({
|
||||
...userMessage,
|
||||
messageId: userMessageId,
|
||||
newMessageId: newUserMassageId
|
||||
newMessageId: newUserMessageId
|
||||
});
|
||||
userMessageId = newUserMassageId;
|
||||
userMessageId = newUserMessageId;
|
||||
|
||||
sendMessage(res, {
|
||||
title: await getConvoTitle(req.user.id, conversationId),
|
||||
|
||||
@@ -76,7 +76,6 @@ const ask = async ({
|
||||
userMessage,
|
||||
endpointOption,
|
||||
conversationId,
|
||||
preSendRequest = true,
|
||||
overrideParentMessageId = null,
|
||||
req,
|
||||
res
|
||||
@@ -92,10 +91,8 @@ const ask = async ({
|
||||
'X-Accel-Buffering': 'no'
|
||||
});
|
||||
|
||||
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
|
||||
|
||||
let responseMessageId = crypto.randomUUID();
|
||||
|
||||
let getPartialMessage = null;
|
||||
try {
|
||||
let lastSavedTimestamp = 0;
|
||||
const { onProgress: progressCallback, getPartialText } = createOnProgress({
|
||||
@@ -116,15 +113,30 @@ const ask = async ({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getPartialMessage = getPartialText;
|
||||
const abortController = new AbortController();
|
||||
let response = await browserClient({
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
...endpointOption,
|
||||
onProgress: progressCallback.call(null, { res, text }),
|
||||
abortController,
|
||||
userId
|
||||
userId,
|
||||
onProgress: progressCallback.call(null, { res, text }),
|
||||
onEventMessage: (eventMessage) => {
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(eventMessage.data);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage(res, {
|
||||
message: { ...userMessage, conversationId: data.conversation_id },
|
||||
created: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('CLIENT RESPONSE', response);
|
||||
@@ -212,8 +224,8 @@ const ask = async ({
|
||||
parentMessageId: overrideParentMessageId || userMessageId,
|
||||
unfinished: false,
|
||||
cancelled: false,
|
||||
error: true,
|
||||
text: error.message
|
||||
// error: true,
|
||||
text: `${getPartialMessage() ?? ''}\n\nError message: "${error.message}"`
|
||||
};
|
||||
await saveMessage(errorMessage);
|
||||
handleError(res, errorMessage);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const crypto = require('crypto');
|
||||
const { titleConvo } = require('../../../app/');
|
||||
const GoogleClient = require('../../../app/google/GoogleClient');
|
||||
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
|
||||
@@ -7,7 +8,7 @@ const { handleError, sendMessage, createOnProgress } = require('./handlers');
|
||||
const requireJwtAuth = require('../../../middleware/requireJwtAuth');
|
||||
|
||||
router.post('/', requireJwtAuth, async (req, res) => {
|
||||
const { endpoint, text, parentMessageId, conversationId } = req.body;
|
||||
const { endpoint, text, parentMessageId, conversationId: oldConversationId } = req.body;
|
||||
if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' });
|
||||
if (endpoint !== 'google') return handleError(res, { text: 'Illegal request' });
|
||||
|
||||
@@ -31,6 +32,8 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||
return handleError(res, { text: `Illegal request: model` });
|
||||
}
|
||||
|
||||
const conversationId = oldConversationId || crypto.randomUUID();
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
return await ask({
|
||||
text,
|
||||
@@ -54,6 +57,7 @@ const ask = async ({ text, endpointOption, parentMessageId = null, conversationI
|
||||
let userMessageId;
|
||||
let responseMessageId;
|
||||
let lastSavedTimestamp = 0;
|
||||
const { overrideParentMessageId = null } = req.body;
|
||||
|
||||
try {
|
||||
const getIds = (data) => {
|
||||
@@ -63,6 +67,8 @@ const ask = async ({ text, endpointOption, parentMessageId = null, conversationI
|
||||
if (!conversationId) {
|
||||
conversationId = data.conversationId;
|
||||
}
|
||||
|
||||
sendMessage(res, { message: userMessage, created: true });
|
||||
};
|
||||
|
||||
const { onProgress: progressCallback } = createOnProgress({
|
||||
@@ -74,7 +80,7 @@ const ask = async ({ text, endpointOption, parentMessageId = null, conversationI
|
||||
messageId: responseMessageId,
|
||||
sender: 'PaLM2',
|
||||
conversationId,
|
||||
parentMessageId: userMessageId,
|
||||
parentMessageId: overrideParentMessageId || userMessageId,
|
||||
text: partialText,
|
||||
unfinished: true,
|
||||
cancelled: false,
|
||||
@@ -113,12 +119,28 @@ const ask = async ({ text, endpointOption, parentMessageId = null, conversationI
|
||||
let response = await client.sendMessage(text, {
|
||||
getIds,
|
||||
user: req.user.id,
|
||||
parentMessageId,
|
||||
conversationId,
|
||||
onProgress: progressCallback.call(null, { res, text, parentMessageId: userMessageId }),
|
||||
parentMessageId,
|
||||
overrideParentMessageId,
|
||||
onProgress: progressCallback.call(null, {
|
||||
res,
|
||||
text,
|
||||
parentMessageId: overrideParentMessageId || userMessageId
|
||||
}),
|
||||
abortController
|
||||
});
|
||||
|
||||
if (overrideParentMessageId) {
|
||||
response.parentMessageId = overrideParentMessageId;
|
||||
}
|
||||
|
||||
await saveConvo(req.user.id, {
|
||||
...endpointOption,
|
||||
...endpointOption.modelOptions,
|
||||
conversationId,
|
||||
endpoint: 'google'
|
||||
});
|
||||
|
||||
await saveMessage(response);
|
||||
sendMessage(res, {
|
||||
title: await getConvoTitle(req.user.id, conversationId),
|
||||
@@ -132,7 +154,7 @@ const ask = async ({ text, endpointOption, parentMessageId = null, conversationI
|
||||
if (parentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||
const title = await titleConvo({ text, response });
|
||||
await saveConvo(req.user.id, {
|
||||
conversationId: conversationId,
|
||||
conversationId,
|
||||
title
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ const ask = async ({
|
||||
console.log('CLIENT RESPONSE', response);
|
||||
|
||||
const newConversationId = response.conversationId || conversationId;
|
||||
const newUserMassageId = response.parentMessageId || userMessageId;
|
||||
const newUserMessageId = response.parentMessageId || userMessageId;
|
||||
const newResponseMessageId = response.messageId;
|
||||
|
||||
// STEP1 generate response message
|
||||
@@ -200,7 +200,7 @@ const ask = async ({
|
||||
conversationId: newConversationId,
|
||||
messageId: responseMessageId,
|
||||
newMessageId: newResponseMessageId,
|
||||
parentMessageId: overrideParentMessageId || newUserMassageId,
|
||||
parentMessageId: overrideParentMessageId || newUserMessageId,
|
||||
text: await handleText(response),
|
||||
sender: endpointOption?.chatGptLabel || 'ChatGPT',
|
||||
unfinished: false,
|
||||
@@ -234,16 +234,16 @@ const ask = async ({
|
||||
|
||||
// STEP3 update the user message
|
||||
userMessage.conversationId = newConversationId;
|
||||
userMessage.messageId = newUserMassageId;
|
||||
userMessage.messageId = newUserMessageId;
|
||||
|
||||
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
|
||||
if (!overrideParentMessageId)
|
||||
await saveMessage({
|
||||
...userMessage,
|
||||
messageId: userMessageId,
|
||||
newMessageId: newUserMassageId
|
||||
newMessageId: newUserMessageId
|
||||
});
|
||||
userMessageId = newUserMassageId;
|
||||
userMessageId = newUserMessageId;
|
||||
|
||||
sendMessage(res, {
|
||||
title: await getConvoTitle(req.user.id, conversationId),
|
||||
|
||||
@@ -2,7 +2,7 @@ const User = require('../../models/User');
|
||||
const Token = require('../../models/schema/tokenSchema');
|
||||
const sendEmail = require('../../utils/sendEmail');
|
||||
const crypto = require('crypto');
|
||||
const bcrypt = require('bcrypt');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const DebugControl = require('../../utils/debug.js');
|
||||
const { registerSchema } = require('../../strategies/validators');
|
||||
const migrateDataToFirstUser = require('../../utils/migrateDataToFirstUser');
|
||||
@@ -90,20 +90,13 @@ const registerUser = async (user) => {
|
||||
// todo: implement refresh token
|
||||
// const refreshToken = newUser.generateRefreshToken();
|
||||
// newUser.refreshToken.push({ refreshToken });
|
||||
bcrypt.genSalt(10, (err, salt) => {
|
||||
bcrypt.hash(newUser.password, salt, (errh, hash) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
// set pasword to hash
|
||||
newUser.password = hash;
|
||||
newUser.save();
|
||||
});
|
||||
});
|
||||
console.log('newUser', newUser);
|
||||
const salt = bcrypt.genSaltSync(10);
|
||||
const hash = bcrypt.hashSync(newUser.password, salt);
|
||||
newUser.password = hash;
|
||||
newUser.save();
|
||||
|
||||
if (isFirstRegisteredUser) {
|
||||
migrateDataToFirstUser(newUser);
|
||||
// console.log(migrate);
|
||||
}
|
||||
response = { status: 200, user: newUser };
|
||||
return response;
|
||||
@@ -127,7 +120,7 @@ const requestPasswordReset = async (email) => {
|
||||
if (token) await token.deleteOne();
|
||||
|
||||
let resetToken = crypto.randomBytes(32).toString('hex');
|
||||
const hash = await bcrypt.hash(resetToken, 10);
|
||||
const hash = await bcrypt.hashSync(resetToken, 10);
|
||||
|
||||
await new Token({
|
||||
userId: user._id,
|
||||
@@ -156,13 +149,13 @@ const resetPassword = async (userId, token, password) => {
|
||||
return new Error('Invalid or expired password reset token');
|
||||
}
|
||||
|
||||
const isValid = await bcrypt.compare(token, passwordResetToken.token);
|
||||
const isValid = bcrypt.compareSync(token, passwordResetToken.token);
|
||||
|
||||
if (!isValid) {
|
||||
return new Error('Invalid or expired password reset token');
|
||||
}
|
||||
|
||||
const hash = await bcrypt.hash(password, 10);
|
||||
const hash = bcrypt.hashSync(password, 10);
|
||||
|
||||
await User.updateOne({ _id: userId }, { $set: { password: hash } }, { new: true });
|
||||
|
||||
|
||||
40
api/utils/tiktokenModels.js
Normal file
40
api/utils/tiktokenModels.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const models = [
|
||||
'text-davinci-003',
|
||||
'text-davinci-002',
|
||||
'text-davinci-001',
|
||||
'text-curie-001',
|
||||
'text-babbage-001',
|
||||
'text-ada-001',
|
||||
'davinci',
|
||||
'curie',
|
||||
'babbage',
|
||||
'ada',
|
||||
'code-davinci-002',
|
||||
'code-davinci-001',
|
||||
'code-cushman-002',
|
||||
'code-cushman-001',
|
||||
'davinci-codex',
|
||||
'cushman-codex',
|
||||
'text-davinci-edit-001',
|
||||
'code-davinci-edit-001',
|
||||
'text-embedding-ada-002',
|
||||
'text-similarity-davinci-001',
|
||||
'text-similarity-curie-001',
|
||||
'text-similarity-babbage-001',
|
||||
'text-similarity-ada-001',
|
||||
'text-search-davinci-doc-001',
|
||||
'text-search-curie-doc-001',
|
||||
'text-search-babbage-doc-001',
|
||||
'text-search-ada-doc-001',
|
||||
'code-search-babbage-code-001',
|
||||
'code-search-ada-code-001',
|
||||
'gpt2',
|
||||
'gpt-4',
|
||||
'gpt-4-0314',
|
||||
'gpt-4-32k',
|
||||
'gpt-4-32k-0314',
|
||||
'gpt-3.5-turbo',
|
||||
'gpt-3.5-turbo-0301'
|
||||
];
|
||||
|
||||
module.exports = new Set(models);
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
/*
|
||||
a preset is a set of plugins used to support particular language features.
|
||||
The two presets Babel uses by default: es2015, react
|
||||
*/
|
||||
"presets": [
|
||||
"@babel/preset-env", //compiling ES2015+ syntax
|
||||
"@babel/preset-react" //for react
|
||||
],
|
||||
/*
|
||||
Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.
|
||||
*/
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-runtime",
|
||||
[
|
||||
"babel-plugin-root-import",
|
||||
{
|
||||
"rootPathPrefix": "~/",
|
||||
"rootPathSuffix": "./src"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
23
client/babel.config.cjs
Normal file
23
client/babel.config.cjs
Normal file
@@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
["@babel/preset-env", { "targets": { "node": "current" } }], //compiling ES2015+ syntax
|
||||
['@babel/preset-react', {runtime: 'automatic'}],
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
/*
|
||||
Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.
|
||||
*/
|
||||
plugins: [
|
||||
"@babel/plugin-transform-runtime",
|
||||
'babel-plugin-transform-import-meta',
|
||||
'babel-plugin-transform-vite-meta-env',
|
||||
'babel-plugin-replace-ts-export-assignment',
|
||||
[
|
||||
"babel-plugin-root-import",
|
||||
{
|
||||
"rootPathPrefix": "~/",
|
||||
"rootPathSuffix": "./src"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
12
client/env.d.ts
vendored
Normal file
12
client/env.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_SERVER_URL_DEV: string;
|
||||
readonly VITE_SERVER_URL_PROD: string;
|
||||
readonly VITE_SHOW_GOOGLE_LOGIN_OPTION: string;
|
||||
readonly VITE_CLIENT_URL_DEV: string;
|
||||
readonly VITE_CLIENT_URL_PROD: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
47
client/jest.config.cjs
Normal file
47
client/jest.config.cjs
Normal file
@@ -0,0 +1,47 @@
|
||||
module.exports = {
|
||||
roots: ['<rootDir>/src'],
|
||||
testEnvironment: 'jsdom',
|
||||
testEnvironmentOptions: {
|
||||
url: 'http://localhost:3080',
|
||||
},
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{js,jsx,ts,tsx}',
|
||||
'!<rootDir>/node_modules/',
|
||||
'!src/**/*.css.d.ts',
|
||||
'!src/**/*.d.ts',
|
||||
],
|
||||
coveragePathIgnorePatterns: [
|
||||
'<rootDir>/node_modules/',
|
||||
'<rootDir>/test/setupTests.js',
|
||||
],
|
||||
// Todo: Add coverageThreshold once we have enough coverage
|
||||
// Note: eventually we want to have these values set to 80%
|
||||
// coverageThreshold: {
|
||||
// global: {
|
||||
// functions: 9,
|
||||
// lines: 40,
|
||||
// statements: 40,
|
||||
// branches: 12,
|
||||
// },
|
||||
// },
|
||||
moduleNameMapper: {
|
||||
'\\.(css)$': 'identity-obj-proxy',
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'jest-file-loader',
|
||||
'layout-test-utils': '<rootDir>/test/layout-test-utils',
|
||||
'^@src/(.*)$': '<rootDir>/src/$1',
|
||||
'^modules/(.*)$': '<rootDir>/src/modules/$1',
|
||||
},
|
||||
restoreMocks: true,
|
||||
testResultsProcessor: 'jest-junit',
|
||||
coverageReporters: ['text', 'cobertura', 'lcov'],
|
||||
transform: {
|
||||
'\\.[jt]sx?$': 'babel-jest',
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'jest-file-loader',
|
||||
},
|
||||
preset: 'ts-jest',
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', '<rootDir>/test/setupTests.js'],
|
||||
clearMocks: true,
|
||||
};
|
||||
7
client/junit.xml
Normal file
7
client/junit.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="jest tests" tests="1" failures="0" errors="0" time="0.704">
|
||||
<testsuite name="undefined" errors="0" failures="0" skipped="0" timestamp="2023-05-22T20:54:42" time="0.541" tests="1">
|
||||
<testcase classname=" renders login form" name=" renders login form" time="0.035">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
@@ -2,11 +2,12 @@
|
||||
"name": "chat-frontend",
|
||||
"version": "0.4.6",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite",
|
||||
"preview-prod": "vite preview"
|
||||
"preview-prod": "vite preview",
|
||||
"test": "jest --watch",
|
||||
"test:ci": "jest --ci"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -38,7 +39,7 @@
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tanstack/react-query": "^4.28.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/node": "^18.15.10",
|
||||
"@types/node": "^20.2.3",
|
||||
"@types/react": "^18.0.30",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@zattoo/use-double-click": "1.2.0",
|
||||
@@ -48,7 +49,7 @@
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"downloadjs": "^1.4.7",
|
||||
"esbuild": "0.17.15",
|
||||
"esbuild": "0.17.19",
|
||||
"export-from-json": "^1.7.2",
|
||||
"filenamify": "^6.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
@@ -80,24 +81,38 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.20.7",
|
||||
"@babel/core": "^7.20.12",
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/plugin-transform-runtime": "^7.19.6",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.21.0",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@tanstack/react-query-devtools": "^4.29.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/node": "^18.15.10",
|
||||
"@types/react": "^18.0.30",
|
||||
"@testing-library/dom": "^9.3.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/node": "^18.16.13",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"babel-jest": "^29.5.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-replace-ts-export-assignment": "^0.0.2",
|
||||
"babel-plugin-root-import": "^6.6.0",
|
||||
"babel-plugin-transform-import-meta": "^2.2.0",
|
||||
"babel-plugin-transform-vite-meta-env": "^1.0.3",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"css-loader": "^6.7.3",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"jest": "^29.5.0",
|
||||
"jest-canvas-mock": "^2.5.1",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jest-file-loader": "^1.0.3",
|
||||
"jest-junit": "^16.0.0",
|
||||
"path": "^0.12.7",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-loader": "^7.1.0",
|
||||
@@ -105,6 +120,7 @@
|
||||
"source-map-loader": "^4.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.2.6",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.2.1",
|
||||
|
||||
8
client/src/components/Auth/__tests__/Login.spec.tsx
Normal file
8
client/src/components/Auth/__tests__/Login.spec.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { render } from 'layout-test-utils';
|
||||
import Login from '../Login';
|
||||
|
||||
test('renders login form', () => {
|
||||
const { getByLabelText } = render(<Login />);
|
||||
expect(getByLabelText(/email/i)).toBeInTheDocument();
|
||||
expect(getByLabelText(/password/i)).toBeInTheDocument();
|
||||
});
|
||||
@@ -47,7 +47,7 @@ function Settings(props) {
|
||||
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="toneStyle-dropdown" className="text-left text-sm font-medium">
|
||||
Tone Style <small className="opacity-40">(default: fast)</small>
|
||||
Tone Style <small className="opacity-40">(default: creative)</small>
|
||||
</Label>
|
||||
<SelectDropDown
|
||||
id="toneStyle-dropdown"
|
||||
@@ -112,7 +112,7 @@ function Settings(props) {
|
||||
<a
|
||||
href="https://github.com/danny-avila/chatgpt-clone/blob/main/client/defaultSystemMessage.md"
|
||||
target="_blank"
|
||||
className="text-blue-500 transition-colors duration-200 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-500"
|
||||
className="text-blue-500 transition-colors duration-200 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-500" rel="noreferrer"
|
||||
>
|
||||
System Message
|
||||
</a>{' '}
|
||||
|
||||
@@ -15,7 +15,7 @@ const alternateName = {
|
||||
google: 'PaLM'
|
||||
};
|
||||
|
||||
export default function ModelItem({ endpoint, value, onSelect }) {
|
||||
export default function ModelItem({ endpoint, value }) {
|
||||
const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false);
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
|
||||
@@ -33,6 +33,7 @@ export default function ModelItem({ endpoint, value, onSelect }) {
|
||||
<>
|
||||
<DropdownMenuRadioItem
|
||||
value={value}
|
||||
id={endpoint}
|
||||
className="group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
{icon}
|
||||
|
||||
@@ -126,6 +126,7 @@ export default function NewConversationMenu() {
|
||||
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
id="new-conversation-menu"
|
||||
variant="outline"
|
||||
className={`group relative mb-[-12px] ml-0 mt-[-8px] items-center rounded-md border-0 p-1 outline-none focus:ring-0 focus:ring-offset-0 dark:data-[state=open]:bg-opacity-50 md:left-1 md:ml-[-12px] md:pl-1`}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import DialogTemplate from '../../ui/DialogTemplate';
|
||||
import { Dialog } from '../../ui/Dialog.tsx';
|
||||
import { Input } from '../../ui/Input.tsx';
|
||||
import { Label } from '../../ui/Label.tsx';
|
||||
import { cn } from '~/utils/';
|
||||
import * as Checkbox from '@radix-ui/react-checkbox';
|
||||
import { CheckIcon } from '@radix-ui/react-icons';
|
||||
import FileUpload from '../NewConversationMenu/FileUpload';
|
||||
@@ -24,9 +21,6 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
||||
const [showPanel, setShowPanel] = useState(false);
|
||||
const { getToken, saveToken } = store.useToken(endpoint);
|
||||
|
||||
const defaultTextProps =
|
||||
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-400 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
|
||||
|
||||
const submit = () => {
|
||||
saveToken(token);
|
||||
onOpenChange(false);
|
||||
@@ -49,13 +43,21 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
||||
const helpText = {
|
||||
bingAI: (
|
||||
<small className="break-all text-gray-600">
|
||||
The Bing Access Token is the "_U" cookie from bing.com. Use dev tools or an extension while
|
||||
logged into the site to view it.
|
||||
{`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 `}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://github.com/waylaidwanderer/node-chatgpt-api/issues/378#issuecomment-1559868368"
|
||||
rel="noreferrer"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
comment here
|
||||
</a>
|
||||
</small>
|
||||
),
|
||||
chatGPTBrowser: (
|
||||
<small className="break-all text-gray-600">
|
||||
To get your Access token For ChatGPT 'Free Version', login to{' '}
|
||||
{`To get your Access token For ChatGPT 'Free Version', login to `}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://chat.openai.com"
|
||||
@@ -96,8 +98,8 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
|
||||
>
|
||||
Create a Service Account
|
||||
</a>
|
||||
. Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
|
||||
Lastly, create a JSON key to import here.
|
||||
{`. Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
|
||||
Lastly, create a JSON key to import here.`}
|
||||
</small>
|
||||
)
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import OpenAIOptions from './OpenAIOptions';
|
||||
import ChatGPTOptions from './ChatGPTOptions';
|
||||
import BingAIOptions from './BingAIOptions';
|
||||
import GoogleOptions from './GoogleOptions';
|
||||
// import BingStyles from './BingStyles';
|
||||
import NewConversationMenu from './NewConversationMenu';
|
||||
import AdjustToneButton from './AdjustToneButton';
|
||||
import Footer from './Footer';
|
||||
@@ -21,7 +20,6 @@ export default function TextChat({ isSearchView = false }) {
|
||||
const conversation = useRecoilValue(store.conversation);
|
||||
const latestMessage = useRecoilValue(store.latestMessage);
|
||||
const [text, setText] = useRecoilState(store.text);
|
||||
// const [text, setText] = useState('');
|
||||
|
||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||
const isSubmitting = useRecoilValue(store.isSubmitting);
|
||||
@@ -30,8 +28,6 @@ export default function TextChat({ isSearchView = false }) {
|
||||
const disabled = false;
|
||||
|
||||
const { ask, stopGenerating } = useMessageHandler();
|
||||
|
||||
// const bingStylesRef = useRef(null);
|
||||
const [showBingToneSetting, setShowBingToneSetting] = useState(false);
|
||||
|
||||
const isNotAppendable = latestMessage?.unfinished & !isSubmitting || latestMessage?.error;
|
||||
@@ -39,25 +35,15 @@ export default function TextChat({ isSearchView = false }) {
|
||||
// auto focus to input, when enter a conversation.
|
||||
useEffect(() => {
|
||||
if (conversation?.conversationId !== 'search') inputRef.current?.focus();
|
||||
// setText('');
|
||||
}, [conversation?.conversationId]);
|
||||
|
||||
// // controls the height of Bing tone style tabs
|
||||
// useEffect(() => {
|
||||
// if (!inputRef.current) {
|
||||
// return; // wait for the ref to be available
|
||||
// }
|
||||
|
||||
// const resizeObserver = new ResizeObserver(() => {
|
||||
// const newHeight = inputRef.current.clientHeight;
|
||||
// if (newHeight >= 24) {
|
||||
// // 24 is the default height of the input
|
||||
// bingStylesRef.current.style.bottom = 15 + newHeight + 'px';
|
||||
// }
|
||||
// });
|
||||
// resizeObserver.observe(inputRef.current);
|
||||
// return () => resizeObserver.disconnect();
|
||||
// }, [inputRef]);
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [isSubmitting]);
|
||||
|
||||
const submitMessage = () => {
|
||||
ask({ text });
|
||||
@@ -156,6 +142,8 @@ export default function TextChat({ isSearchView = false }) {
|
||||
>
|
||||
<NewConversationMenu />
|
||||
<TextareaAutosize
|
||||
// set test id for e2e testing
|
||||
data-testid="text-input"
|
||||
tabIndex="0"
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue, useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { SSE } from '~/data-provider/sse.mjs';
|
||||
import createPayload from '~/data-provider/createPayload';
|
||||
import { useAbortRequestWithMessage } from '~/data-provider';
|
||||
import store from '~/store';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
|
||||
@@ -117,8 +116,11 @@ export default function MessageHandler() {
|
||||
const { requestMessage, responseMessage, conversation } = data;
|
||||
|
||||
// update the messages
|
||||
if (isRegenerate) setMessages([...messages, responseMessage]);
|
||||
else setMessages([...messages, requestMessage, responseMessage]);
|
||||
if (isRegenerate) {
|
||||
setMessages([...messages, responseMessage]);
|
||||
} else {
|
||||
setMessages([...messages, requestMessage, responseMessage]);
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
|
||||
// refresh title
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { cn } from '~/utils/';
|
||||
import Clipboard from '../svg/Clipboard';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
import EditIcon from '../svg/EditIcon';
|
||||
@@ -13,14 +14,13 @@ export default function HoverButtons({
|
||||
message,
|
||||
regenerate
|
||||
}) {
|
||||
const { endpoint, jailbreak = false } = conversation;
|
||||
const { endpoint } = conversation;
|
||||
const [isCopied, setIsCopied] = React.useState(false);
|
||||
|
||||
const branchingSupported =
|
||||
// azureOpenAI, openAI, chatGPTBrowser support branching, so edit enabled
|
||||
!!['azureOpenAI', 'openAI', 'chatGPTBrowser', 'google'].find((e) => e === endpoint) ||
|
||||
// Sydney in bingAI supports branching, so edit enabled
|
||||
(endpoint === 'bingAI' && jailbreak);
|
||||
// azureOpenAI, openAI, chatGPTBrowser support branching, so edit enabled // 5/21/23: Bing is allowing editing and Message regenerating
|
||||
!!['azureOpenAI', 'openAI', 'chatGPTBrowser', 'google', 'bingAI'].find((e) => e === endpoint);
|
||||
// Sydney in bingAI supports branching, so edit enabled
|
||||
|
||||
const editEnabled =
|
||||
!message?.error &&
|
||||
@@ -30,7 +30,7 @@ export default function HoverButtons({
|
||||
branchingSupported;
|
||||
|
||||
// for now, once branching is supported, regerate will be enabled
|
||||
const regenerateEnabled =
|
||||
let regenerateEnabled =
|
||||
// !message?.error &&
|
||||
!message?.isCreatedByUser &&
|
||||
!message?.searchResult &&
|
||||
@@ -53,7 +53,7 @@ export default function HoverButtons({
|
||||
) : null}
|
||||
{regenerateEnabled ? (
|
||||
<button
|
||||
className="hover-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible"
|
||||
className="hover-button active rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible"
|
||||
onClick={regenerate}
|
||||
type="button"
|
||||
title="regenerate"
|
||||
@@ -64,7 +64,10 @@ export default function HoverButtons({
|
||||
) : null}
|
||||
|
||||
<button
|
||||
className="hover-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible"
|
||||
className={cn(
|
||||
'hover-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible',
|
||||
message?.isCreatedByUser ? '' : 'active'
|
||||
)}
|
||||
onClick={() => copyToClipboard(setIsCopied)}
|
||||
type="button"
|
||||
title={isCopied ? 'Copied to clipboard' : 'Copy to clipboard'}
|
||||
|
||||
@@ -16,6 +16,15 @@ export type TExample = {
|
||||
output: string;
|
||||
};
|
||||
|
||||
export enum EModelEndpoint {
|
||||
azureOpenAI = 'azureOpenAI',
|
||||
openAI = 'openAI',
|
||||
bingAI = 'bingAI',
|
||||
chatGPT = 'chatGPT',
|
||||
chatGPTBrowser = 'chatGPTBrowser',
|
||||
google = 'google'
|
||||
}
|
||||
|
||||
export type TSubmission = {
|
||||
clientId?: string;
|
||||
context?: string;
|
||||
@@ -42,15 +51,6 @@ export type TSubmission = {
|
||||
frequence_penalty?: number;
|
||||
};
|
||||
|
||||
export enum EModelEndpoint {
|
||||
azureOpenAI = 'azureOpenAI',
|
||||
openAI = 'openAI',
|
||||
bingAI = 'bingAI',
|
||||
chatGPT = 'chatGPT',
|
||||
chatGPTBrowser = 'chatGPTBrowser',
|
||||
google = 'google'
|
||||
}
|
||||
|
||||
export type TConversation = {
|
||||
conversationId: string;
|
||||
title: string;
|
||||
@@ -157,7 +157,7 @@ export type TSearchResults = {
|
||||
pageNumber: string;
|
||||
pageSize: string | number;
|
||||
pages: string | number;
|
||||
filter: {};
|
||||
filter: object;
|
||||
};
|
||||
|
||||
export type TEndpoints = {
|
||||
@@ -175,11 +175,11 @@ export type TUpdateTokenCountResponse = {
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type TMessageTreeNode = {};
|
||||
export type TMessageTreeNode = object;
|
||||
|
||||
export type TSearchMessage = {};
|
||||
export type TSearchMessage = object;
|
||||
|
||||
export type TSearchMessageTreeNode = {};
|
||||
export type TSearchMessageTreeNode = object;
|
||||
|
||||
export type TRegisterUser = {
|
||||
name: string;
|
||||
|
||||
@@ -1,40 +1,8 @@
|
||||
.switch-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.switch-result {
|
||||
display: block !important;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* .sibling-switch {
|
||||
left: 114px;
|
||||
top: unset;
|
||||
bottom: 4px;
|
||||
visibility: visible;
|
||||
z-index: 2;
|
||||
} */
|
||||
.sibling-switch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hover-button {
|
||||
.hover-button.active {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.input-panel-button {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.input-panel-button svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.input-panel {
|
||||
}
|
||||
|
||||
.nav-close-button {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@@ -96,3 +64,47 @@
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.switch-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.switch-result {
|
||||
display: block !important;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
/* .sibling-switch {
|
||||
left: 114px;
|
||||
top: unset;
|
||||
bottom: 4px;
|
||||
visibility: visible;
|
||||
z-index: 2;
|
||||
} */
|
||||
.sibling-switch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hover-button {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.input-panel-button {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.input-panel-button svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.input-panel {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const cleanupPreset = ({ preset: _preset, endpointsConfig = {} }) => {
|
||||
jailbreak: _preset?.jailbreak ?? false,
|
||||
context: _preset?.context ?? null,
|
||||
systemMessage: _preset?.systemMessage ?? null,
|
||||
toneStyle: _preset?.toneStyle ?? 'fast',
|
||||
toneStyle: _preset?.toneStyle ?? 'creative',
|
||||
title: _preset?.title ?? 'New Preset'
|
||||
};
|
||||
} else if (endpoint === 'chatGPTBrowser') {
|
||||
|
||||
@@ -48,7 +48,7 @@ const buildDefaultConversation = ({
|
||||
jailbreak: lastConversationSetup?.jailbreak ?? false,
|
||||
context: lastConversationSetup?.context ?? null,
|
||||
systemMessage: lastConversationSetup?.systemMessage ?? null,
|
||||
toneStyle: lastConversationSetup?.toneStyle ?? 'fast',
|
||||
toneStyle: lastConversationSetup?.toneStyle ?? 'creative',
|
||||
jailbreakConversationId: lastConversationSetup?.jailbreakConversationId ?? null,
|
||||
conversationSignature: null,
|
||||
clientId: null,
|
||||
@@ -80,7 +80,7 @@ const buildDefaultConversation = ({
|
||||
return conversation;
|
||||
};
|
||||
|
||||
const getDefaultConversation = ({ conversation, prevConversation, endpointsConfig, preset }) => {
|
||||
const getDefaultConversation = ({ conversation, endpointsConfig, preset }) => {
|
||||
const { endpoint: targetEndpoint } = preset || {};
|
||||
|
||||
if (targetEndpoint) {
|
||||
@@ -123,7 +123,9 @@ const getDefaultConversation = ({ conversation, prevConversation, endpointsConfi
|
||||
conversation = buildDefaultConversation({ conversation, endpoint, endpointsConfig });
|
||||
return conversation;
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// if anything happens, reset to default model
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ const useMessageHandler = () => {
|
||||
currentConversation?.model ??
|
||||
endpointsConfig[endpoint]?.availableModels?.[0] ??
|
||||
'chat-bison',
|
||||
chatGptLabel: currentConversation?.chatGptLabel ?? null,
|
||||
modelLabel: currentConversation?.modelLabel ?? null,
|
||||
promptPrefix: currentConversation?.promptPrefix ?? null,
|
||||
examples: currentConversation?.examples ?? [
|
||||
{ input: { content: '' }, output: { content: '' } }
|
||||
@@ -68,7 +68,7 @@ const useMessageHandler = () => {
|
||||
jailbreak: currentConversation?.jailbreak ?? false,
|
||||
systemMessage: currentConversation?.systemMessage ?? null,
|
||||
context: currentConversation?.context ?? null,
|
||||
toneStyle: currentConversation?.toneStyle ?? 'fast',
|
||||
toneStyle: currentConversation?.toneStyle ?? 'creative',
|
||||
jailbreakConversationId: currentConversation?.jailbreakConversationId ?? null,
|
||||
conversationSignature: currentConversation?.conversationSignature ?? null,
|
||||
clientId: currentConversation?.clientId ?? null,
|
||||
|
||||
21
client/test/layout-test-utils.tsx
Normal file
21
client/test/layout-test-utils.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { render as rtlRender } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { AuthContextProvider } from '~/hooks/AuthContext';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
const client = new QueryClient();
|
||||
|
||||
function renderWithProvidersWrapper(ui, { ...options } = {}) {
|
||||
function Wrapper({ children }) {
|
||||
return (
|
||||
<QueryClientProvider client={client}>
|
||||
<Router>
|
||||
<AuthContextProvider>{children}</AuthContextProvider>
|
||||
</Router>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
return rtlRender(ui, { wrapper: Wrapper, ...options });
|
||||
}
|
||||
export * from '@testing-library/react';
|
||||
export { renderWithProvidersWrapper as render };
|
||||
18
client/test/setupTests.js
Normal file
18
client/test/setupTests.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/* This file is automatically executed before running tests
|
||||
* https://create-react-app.dev/docs/running-tests/#initializing-test-environment
|
||||
*/
|
||||
|
||||
// react-testing-library renders your components to document.body,
|
||||
// this adds jest-dom's custom assertions
|
||||
// https://github.com/testing-library/jest-dom#table-of-contents
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
// Mock canvas when run unit test cases with jest.
|
||||
// 'react-lottie' uses canvas
|
||||
import 'jest-canvas-mock';
|
||||
@@ -2,14 +2,10 @@
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": false,
|
||||
"esModuleInterop": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
@@ -19,13 +15,20 @@
|
||||
"isolatedModules": true,
|
||||
"noImplicitAny": false,
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"declarationDir": "./types",
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
}
|
||||
"types": ["node", "jest", "@testing-library/jest-dom"],
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src/**/*", "setupTests.js", "vite.config.ts", "env.d.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
9
client/tsconfig.node.json
Normal file
9
client/tsconfig.node.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -14,10 +14,6 @@ export default defineConfig({
|
||||
target: 'http://localhost:3080',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/auth': {
|
||||
target: 'http://localhost:3080',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/oauth': {
|
||||
target: 'http://localhost:3080',
|
||||
changeOrigin: true
|
||||
|
||||
@@ -43,7 +43,7 @@ Contains the logic for each route, including calling the appropriate service fun
|
||||
- Create a separate controller file for each route to handle the request/response logic.
|
||||
- Name controller files using the PascalCase convention and append "Controller" to the file name (e.g., UserController.js).
|
||||
- Use controller methods to encapsulate logic related to the route handling.
|
||||
- eep controllers thin by delegating complex operations to service or model files.
|
||||
- Keep controllers thin by delegating complex operations to service or model files.
|
||||
|
||||
#### Services
|
||||
|
||||
@@ -101,4 +101,4 @@ Use the conventions found in the `data-provider` directory for handling data ser
|
||||
### State Management
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
5
documents/dev/README.md
Normal file
5
documents/dev/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This directory contains files used for developer work
|
||||
|
||||
- Dockerfile-app: used to build the DockerHub image
|
||||
- eslintrc-stripped.js: alternate linting rules, used in development
|
||||
- meilisearch.yml: Dockerfile for building meilisearch image independently from project
|
||||
@@ -2,21 +2,38 @@
|
||||
Thanks to @heathriel!
|
||||
##
|
||||
|
||||
**Install the prerequisites**:
|
||||
**Recommended - [Docker Install](docker_install.md)**
|
||||
|
||||
## **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`
|
||||
- Install Docker (optional) by following the instructions on https://docs.docker.com/desktop/mac/install/
|
||||
- Obtain an OpenAI API key, BingAI and ChatGPT access tokens as described in the original instructions
|
||||
-
|
||||
**Instructions:**
|
||||
|
||||
- **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
|
||||
- Then in the Database Deployment tab click on Connect
|
||||
- In "Choose a connection method" select "Connect your application"
|
||||
- Driver = Node.js / Version = 4.1 or later
|
||||
- Copy the connection string and save it somewhere(you will need it later)
|
||||
|
||||
|
||||
## **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**
|
||||
|
||||
- here: https://platform.openai.com/account/api-keys and save it somewhere safe (you will need it later)
|
||||
|
||||
**ChatGPT Free Instructions:**
|
||||
|
||||
@@ -31,33 +48,13 @@ Follow the instructions for setting up proxies, access tokens, and user system:
|
||||
- Expand the "Cookies" section under "Storage".
|
||||
- Copy the value of the "_U" cookie and save it somewhere. You'll need it later.
|
||||
|
||||
**Set up proxy in the local environment (for Mac):**
|
||||
|
||||
**Option 1: Set system-level environment variable**
|
||||
|
||||
- Open Terminal and run export PROXY="http://127.0.0.1:7890"
|
||||
- Change http://127.0.0.1:7890 to your proxy server
|
||||
|
||||
**Option 2: Set in .env file**
|
||||
|
||||
- Open the .env file in the api directory with a text editor
|
||||
- Add PROXY="http://127.0.0.1:7890" to the file
|
||||
- Change http://127.0.0.1:7890 to your proxy server
|
||||
|
||||
**Set up proxy in the Docker environment (for Mac):**
|
||||
|
||||
- Open the docker-compose.yml file with a text editor
|
||||
- Under services, find the api section, and then locate the environment section
|
||||
- Add the line - "PROXY=http://127.0.0.1:7890" under the environment section
|
||||
- Change http://127.0.0.1:7890 to your proxy server
|
||||
|
||||
|
||||
|
||||
## **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 root directory `npm ci`
|
||||
- Run npm ci from root directory `npm ci`
|
||||
- Build the client by running `npm run frontend`
|
||||
|
||||
**Download MeiliSearch for macOS:**
|
||||
**Download MeiliSearch for macOS (optional):**
|
||||
- You can download the latest MeiliSearch binary for macOS from their GitHub releases page: https://github.com/meilisearch/MeiliSearch/releases. Look for the file named meilisearch-macos-amd64 (or the equivalent for your system architecture) and download it.
|
||||
|
||||
**Make the binary executable:**
|
||||
@@ -85,10 +82,10 @@ MEILISEARCH_KEY=your_master_key_goes_here
|
||||
|
||||
- With MeiliSearch running and configured, the ChatGPT-Clone project should now have the Conversation search feature enabled.
|
||||
|
||||
- In the chatgpt-clone directory, start the application by running cd api && npm start
|
||||
- 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:
|
||||
|
||||
@@ -111,9 +108,12 @@ npm run backend
|
||||
```
|
||||
./start_chatgpt.sh
|
||||
```
|
||||
##
|
||||
**Note:**
|
||||
- To share within the network or serve as a public server, set HOST to 0.0.0.0 in the .env file.
|
||||
|
||||
|
||||
## **Update**
|
||||
- run `git pull` from the root dir
|
||||
- Run npm ci from root directory `npm ci`
|
||||
- Build the client by running `npm run frontend`
|
||||
|
||||
##
|
||||
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
# Windows Install
|
||||
|
||||
### Recommended:
|
||||
### **[Docker](docker.md)**
|
||||
or
|
||||
### **[Automated Installer (Windows)](https://github.com/fuegovic/chatgpt-clone-local-installer)**
|
||||
(Includes a Startup and Update Utility)
|
||||
##
|
||||
|
||||
|
||||
## Manual Installation
|
||||
### Install the prerequisites on your machine
|
||||
|
||||
- **Download chatgpt-clone**
|
||||
-
|
||||
|
||||
- Download the latest release here: https://github.com/danny-avila/chatgpt-clone/releases/
|
||||
- Or by clicking on the green code button in the top of the page and selecting "Download ZIP"
|
||||
- Or (Recommended if you have Git installed) pull the latest release from the main branch
|
||||
- If you downloaded a zip file, extract the content in "C:/chatgpt-clone/"
|
||||
- **IMPORTANT : If you install the files somewhere else modify the instructions accordingly**
|
||||
|
||||
- ** **Enable the Conversation search feature:**** (optional)
|
||||
- **Enable the Conversation search feature:** (optional)
|
||||
|
||||
- Download MeiliSearch latest release from : https://github.com/meilisearch/meilisearch/releases
|
||||
- Copy it to "C:/chatgpt-clone/"
|
||||
@@ -81,25 +83,24 @@ To use the app:
|
||||
- **Make a batch file to automate the starting process**
|
||||
- Open a text editor
|
||||
- Paste the following code in a new document
|
||||
- The meilisearch executable needs to be at the root of the chatgpt-clone directory
|
||||
- Put your MeiliSearch master key instead of "your_master_key_goes_here"
|
||||
- Save the file as "C:/chatgpt-clone/chatgpt-clone.bat"
|
||||
- you can make a shortcut of this batch file and put it anywhere
|
||||
|
||||
```
|
||||
REM the meilisearch executable needs to be at the root of the chatgpt-clone directory
|
||||
|
||||
start "MeiliSearch" cmd /k "meilisearch --master-key your_master_key_goes_here
|
||||
|
||||
REM ↑↑↑ meilisearch is the name of the meilisearch executable, put your own master key there
|
||||
|
||||
start "ChatGPT-Clone" cmd /k "npm run backend"
|
||||
|
||||
REM this batch file goes at the root of the chatgpt-clone directory (C:/chatgpt-clone/)
|
||||
```
|
||||
|
||||
### Update the app version
|
||||
### **Update**
|
||||
- run `git pull` from the root dir
|
||||
- Run npm ci from root directory `npm ci`
|
||||
- Build the client by running `npm run frontend`
|
||||
|
||||
If you update the chatgpt-clone project files, mannually redo the `npm ci` and `npm run frontend` steps
|
||||
|
||||
##
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help improve code's quality
|
||||
title: "[bug] "
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[feature] "
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Feature description**
|
||||
...
|
||||
|
||||
**What you expect**
|
||||
...
|
||||
|
||||
**Addition(s)**
|
||||
...
|
||||
45
e2e/specs/messages.spec.js
Normal file
45
e2e/specs/messages.spec.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const endpoints = ['google', 'openAI', 'azureOpenAI', 'bingAI', 'chatGPTBrowser', 'gptPlugins'];
|
||||
|
||||
test.describe('Messaging suite', () => {
|
||||
let myBrowser;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
myBrowser = await browser.newContext({
|
||||
storageState: 'e2e/auth.json'
|
||||
});
|
||||
});
|
||||
|
||||
test('textbox should be focused after receiving message', async () => {
|
||||
test.setTimeout(120000);
|
||||
const page = await myBrowser.newPage();
|
||||
const message = 'hi';
|
||||
const endpoint = endpoints[0];
|
||||
|
||||
await page.goto('http://localhost:3080/chat/new');
|
||||
await page.locator('#new-conversation-menu').click();
|
||||
await page.locator(`#${endpoint}`).click();
|
||||
await page.locator('form').getByRole('textbox').click();
|
||||
await page.locator('form').getByRole('textbox').fill(message);
|
||||
|
||||
const responsePromise = [
|
||||
page.waitForResponse(async (response) => {
|
||||
return response.url().includes(`/api/ask/${endpoint}`) && response.status() === 200;
|
||||
}),
|
||||
page.locator('form').getByRole('textbox').press('Enter')
|
||||
];
|
||||
|
||||
const [response] = await Promise.all(responsePromise);
|
||||
const responseBody = await response.body();
|
||||
const messageSuccess = responseBody.includes(`"final":true`);
|
||||
expect(messageSuccess).toBe(true);
|
||||
|
||||
// Check if textbox is focused
|
||||
await page.waitForTimeout(250);
|
||||
const isTextboxFocused = await page.evaluate(() => {
|
||||
return document.activeElement === document.querySelector('[data-testid="text-input"]');
|
||||
});
|
||||
expect(isTextboxFocused).toBeTruthy();
|
||||
});
|
||||
});
|
||||
23000
package-lock.json
generated
23000
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@
|
||||
"e2e:debug": "cross-env PWDEBUG=1 playwright test --config=e2e/playwright.config.js",
|
||||
"e2e:report": "npx playwright show-report e2e/playwright-report",
|
||||
"e2e:auth": "npx playwright codegen --save-storage=e2e/auth.json http://localhost:3080/",
|
||||
"e2e:test-auth": "npx playwright codegen --load-storage=e2e/auth.json http://localhost:3080/",
|
||||
"prepare": "husky install",
|
||||
"format": "prettier-eslint --write \"{,!(node_modules)/**/}*.{js,jsx,ts,tsx}\""
|
||||
},
|
||||
@@ -35,7 +36,7 @@
|
||||
"@typescript-eslint/parser": "^5.59.6",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
|
||||
Reference in New Issue
Block a user