Merge pull request #16 from danny-avila/updateAI

feat: add sydney (jailbroken bing) and more bing styling with citations
This commit is contained in:
Danny Avila 2023-03-09 20:34:28 -05:00 committed by GitHub
commit 12cf3405e4
28 changed files with 377 additions and 88 deletions

View file

@ -8,14 +8,23 @@ https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5
## Updates ## Updates
<details open> <details open>
<summary><strong>2023-03-09</strong></summary>
Released v.0.0.2
Adds Sydney (jailbroken Bing AI) to the model menu. Thank you [DavesDevFails](https://github.com/DavesDevFails) for bringing it to my attention in this [issue](https://github.com/danny-avila/chatgpt-clone/issues/13). Bing/Sydney now correctly cite links, more styling to come. Fix some overlooked bugs, and model menu doesn't close upon deleting a customGpt.
I've re-enabled the ChatGPT browser client (free version) since it might be working for most people, it no longer works for me. Sydney is the best free route anyway.
</details>
<details>
<details>
<summary><strong>2023-03-07</strong></summary> <summary><strong>2023-03-07</strong></summary>
Due to increased interest in the repo, I've dockerized the app as of this update for quick setup! See setup instructions below. I realize this still takes some time with installing docker dependencies, so it's on the roadmap to have a deployed demo. Besides this, I've made major improvements for a lot of the existing features across the board, mainly UI/UX. Due to increased interest in the repo, I've dockerized the app as of this update for quick setup! See setup instructions below. I realize this still takes some time with installing docker dependencies, so it's on the roadmap to have a deployed demo. Besides this, I've made major improvements for a lot of the existing features across the board, mainly UI/UX.
Also worth noting, the method to access the Free Version is no longer working, so I've removed it from model selection until further notice. Also worth noting, the method to access the Free Version is no longer working, so I've removed it from model selection until further notice.
</details> </details>
<details>
<summary><strong>Previous Updates</strong></summary> <summary><strong>Previous Updates</strong></summary>
<details> <details>
@ -80,12 +89,12 @@ Here are my recently completed and planned features:
- [x] Customize prompt prefix/label (custom ChatGPT using official API) - [x] Customize prompt prefix/label (custom ChatGPT using official API)
- [x] Server convo pagination (limit fetch and load more with 'show more' button) - [x] Server convo pagination (limit fetch and load more with 'show more' button)
- [x] Config file for easy startup (docker compose) - [x] Config file for easy startup (docker compose)
- [ ] Bing AI Styling (for suggested responses, convo end, etc.) - **In progress**
- [ ] Add warning before clearing convos - [ ] Add warning before clearing convos
- [ ] Build test suite for CI/CD - [ ] Build test suite for CI/CD
- [ ] Conversation Search (by title) - [ ] Conversation Search (by title)
- [ ] Resubmit/edit sent messages - [ ] Resubmit/edit sent messages
- [ ] Semantic Search Option (requires more tokens) - [ ] Semantic Search Option (requires more tokens)
- [ ] Bing AI Styling (for suggested responses, convo end, etc.)
- [ ] Prompt Templates/Search - [ ] Prompt Templates/Search
- [ ] Refactor/clean up code (tech debt) - [ ] Refactor/clean up code (tech debt)
- [ ] Optional use of local storage for credentials - [ ] Optional use of local storage for credentials
@ -172,7 +181,7 @@ The Bing Access Token is the "_U" cookie from bing.com. Use dev tools or an exte
</details> </details>
### Updating ### Updating
- As the project is still a work-in-progress, you should pull the latest and run some of the steps above again - As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data.
## Use Cases ## ## Use Cases ##

View file

@ -10,7 +10,7 @@ const askBing = async ({ text, progressCallback, convo }) => {
// If the above doesn't work, provide all your cookies as a string instead // If the above doesn't work, provide all your cookies as a string instead
// cookies: '', // cookies: '',
debug: false, debug: false,
store: new KeyvFile({ filename: './data/cache.json' }) cache: { store: new KeyvFile({ filename: './data/cache.json' }) }
}); });
let options = { let options = {

View file

@ -5,7 +5,8 @@ const clientOptions = {
// Warning: This will expose your access token to a third party. Consider the risks before using this. // Warning: This will expose your access token to a third party. Consider the risks before using this.
reverseProxyUrl: 'https://chatgpt.duti.tech/api/conversation', reverseProxyUrl: 'https://chatgpt.duti.tech/api/conversation',
// Access token from https://chat.openai.com/api/auth/session // Access token from https://chat.openai.com/api/auth/session
accessToken: process.env.CHATGPT_TOKEN accessToken: process.env.CHATGPT_TOKEN,
// debug: true
}; };
const browserClient = async ({ text, progressCallback, convo }) => { const browserClient = async ({ text, progressCallback, convo }) => {

29
api/app/citeText.js Normal file
View file

@ -0,0 +1,29 @@
const citationRegex = /\[\^\d+?\^]/g;
const citeText = (res, noLinks = false) => {
let result = res.text || res;
const citations = Array.from(new Set(result.match(citationRegex)));
if (citations?.length === 0) return result;
if (noLinks) {
citations.forEach((citation) => {
const digit = citation.match(/\d+?/g)[0];
result = result.replaceAll(citation, `<sup>[${digit}](#) </sup>`);
});
return result;
}
let sources = res.details.sourceAttributions;
if (sources?.length === 0) return result;
sources = sources.map((source) => source.seeMoreUrl);
citations.forEach((citation) => {
const digit = citation.match(/\d+?/g)[0];
result = result.replaceAll(citation, `<sup>[${digit}](${sources[digit - 1]}) </sup>`);
});
return result;
};
module.exports = citeText;

13
api/app/getCitations.js Normal file
View file

@ -0,0 +1,13 @@
// const regex = / \[\d+\..*?\]\(.*?\)/g;
const regex = / \[.*?]\(.*?\)/g;
const getCitations = (res) => {
const textBlocks = res.details.adaptiveCards[0].body;
if (!textBlocks) return '';
let links = textBlocks[textBlocks.length - 1]?.text.match(regex);
if (links?.length === 0 || !links) return '';
links = links.map((link) => link.trim());
return links.join('\n');
};
module.exports = getCitations;

View file

@ -2,7 +2,10 @@ const { askClient } = require('./chatgpt-client');
const { browserClient } = require('./chatgpt-browser'); const { browserClient } = require('./chatgpt-browser');
const customClient = require('./chatgpt-custom'); const customClient = require('./chatgpt-custom');
const { askBing } = require('./bingai'); const { askBing } = require('./bingai');
const { askSydney } = require('./sydney');
const titleConvo = require('./titleConvo'); const titleConvo = require('./titleConvo');
const getCitations = require('./getCitations');
const citeText = require('./citeText');
const detectCode = require('./detectCode'); const detectCode = require('./detectCode');
module.exports = { module.exports = {
@ -10,6 +13,9 @@ module.exports = {
browserClient, browserClient,
customClient, customClient,
askBing, askBing,
askSydney,
titleConvo, titleConvo,
getCitations,
citeText,
detectCode detectCode
}; };

36
api/app/sydney.js Normal file
View file

@ -0,0 +1,36 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const askSydney = async ({ text, progressCallback, convo }) => {
const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api'));
const sydneyClient = new BingAIClient({
// "_U" cookie from bing.com
userToken: process.env.BING_TOKEN,
// If the above doesn't work, provide all your cookies as a string instead
// cookies: '',
debug: false,
cache: { store: new KeyvFile({ filename: './data/cache.json' }) }
});
let options = {
jailbreakConversationId: true,
onProgress: async (partialRes) => await progressCallback(partialRes),
};
if (convo.parentMessageId) {
options = { ...options, jailbreakConversationId: convo.jailbreakConversationId, parentMessageId: convo.parentMessageId };
}
console.log('sydney options', options);
const res = await sydneyClient.sendMessage(text, options
);
return res;
// for reference:
// https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/demos/use-bing-client.js
};
module.exports = { askSydney };

View file

@ -15,6 +15,9 @@ const convoSchema = mongoose.Schema({
type: String, type: String,
default: 'New conversation' default: 'New conversation'
}, },
jailbreakConversationId: {
type: String
},
conversationSignature: { conversationSignature: {
type: String type: String
}, },
@ -44,6 +47,15 @@ const convoSchema = mongoose.Schema({
const Conversation = const Conversation =
mongoose.models.Conversation || mongoose.model('Conversation', convoSchema); mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
const getConvo = async (conversationId) => {
try {
return await Conversation.findOne({ conversationId }).exec();
} catch (error) {
console.log(error);
return { message: 'Error getting single conversation' };
}
};
module.exports = { module.exports = {
saveConvo: async ({ conversationId, title, ...convo }) => { saveConvo: async ({ conversationId, title, ...convo }) => {
try { try {
@ -92,12 +104,14 @@ module.exports = {
return { message: 'Error getting conversations' }; return { message: 'Error getting conversations' };
} }
}, },
getConvo: async (conversationId) => { getConvo,
getConvoTitle: async (conversationId) => {
try { try {
return await Conversation.findOne({ conversationId }).exec(); const convo = await getConvo(conversationId);
return convo.title;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return { message: 'Error getting single conversation' }; return { message: 'Error getting conversation title' };
} }
}, },
deleteConvos: async (filter) => { deleteConvos: async (filter) => {

View file

@ -1,10 +1,11 @@
const { saveMessage, deleteMessages } = require('./Message'); const { saveMessage, deleteMessages } = require('./Message');
const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt'); const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt');
const { getConvo, saveConvo } = require('./Conversation'); const { getConvoTitle, getConvo, saveConvo } = require('./Conversation');
module.exports = { module.exports = {
saveMessage, saveMessage,
deleteMessages, deleteMessages,
getConvoTitle,
getConvo, getConvo,
saveConvo, saveConvo,
getCustomGpts, getCustomGpts,

14
api/package-lock.json generated
View file

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@keyv/mongo": "^2.1.8", "@keyv/mongo": "^2.1.8",
"@vscode/vscode-languagedetection": "^1.0.22", "@vscode/vscode-languagedetection": "^1.0.22",
"@waylaidwanderer/chatgpt-api": "^1.15.1", "@waylaidwanderer/chatgpt-api": "^1.28.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",
@ -1492,9 +1492,9 @@
} }
}, },
"node_modules/@waylaidwanderer/chatgpt-api": { "node_modules/@waylaidwanderer/chatgpt-api": {
"version": "1.26.1", "version": "1.28.2",
"resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.26.1.tgz", "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.2.tgz",
"integrity": "sha512-cv9NqC0owO2EGCkVg4VQO0lcA5pDgv2VJrBE/0P6En27/v0gIC+7MedowX3htIUi4GLDkgyyDDDimst2i8ReMw==", "integrity": "sha512-efNvZr8uosiYD69zFq50OPM36s+tyRMixlHpwDzn2q9UuZrdHC++kmm23OAnDxv3/+vA4UwCsZXn+92c35NHBQ==",
"dependencies": { "dependencies": {
"@dqbd/tiktoken": "^0.4.0", "@dqbd/tiktoken": "^0.4.0",
"@fastify/cors": "^8.2.0", "@fastify/cors": "^8.2.0",
@ -5781,9 +5781,9 @@
"integrity": "sha512-rQ/BgMyLuIXSmbA0MSkIPHtcOw14QkeDbAq19sjvaS9LTRr905yij0S8lsyqN5JgOsbtIx7pAcyOxFMzPmqhZQ==" "integrity": "sha512-rQ/BgMyLuIXSmbA0MSkIPHtcOw14QkeDbAq19sjvaS9LTRr905yij0S8lsyqN5JgOsbtIx7pAcyOxFMzPmqhZQ=="
}, },
"@waylaidwanderer/chatgpt-api": { "@waylaidwanderer/chatgpt-api": {
"version": "1.26.1", "version": "1.28.2",
"resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.26.1.tgz", "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.2.tgz",
"integrity": "sha512-cv9NqC0owO2EGCkVg4VQO0lcA5pDgv2VJrBE/0P6En27/v0gIC+7MedowX3htIUi4GLDkgyyDDDimst2i8ReMw==", "integrity": "sha512-efNvZr8uosiYD69zFq50OPM36s+tyRMixlHpwDzn2q9UuZrdHC++kmm23OAnDxv3/+vA4UwCsZXn+92c35NHBQ==",
"requires": { "requires": {
"@dqbd/tiktoken": "^0.4.0", "@dqbd/tiktoken": "^0.4.0",
"@fastify/cors": "^8.2.0", "@fastify/cors": "^8.2.0",

View file

@ -21,7 +21,7 @@
"dependencies": { "dependencies": {
"@keyv/mongo": "^2.1.8", "@keyv/mongo": "^2.1.8",
"@vscode/vscode-languagedetection": "^1.0.22", "@vscode/vscode-languagedetection": "^1.0.22",
"@waylaidwanderer/chatgpt-api": "^1.15.1", "@waylaidwanderer/chatgpt-api": "^1.28.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",

View file

@ -2,6 +2,7 @@ const express = require('express');
const crypto = require('crypto'); const crypto = require('crypto');
const router = express.Router(); const router = express.Router();
const askBing = require('./askBing'); const askBing = require('./askBing');
const askSydney = require('./askSydney');
const { const {
titleConvo, titleConvo,
askClient, askClient,
@ -13,6 +14,7 @@ const { getConvo, saveMessage, deleteMessages, saveConvo } = require('../../mode
const { handleError, sendMessage } = require('./handlers'); const { handleError, sendMessage } = require('./handlers');
router.use('/bing', askBing); router.use('/bing', askBing);
router.use('/sydney', askSydney);
router.post('/', async (req, res) => { router.post('/', async (req, res) => {
let { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body; let { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body;

View file

@ -1,9 +1,10 @@
const express = require('express'); const express = require('express');
const crypto = require('crypto'); const crypto = require('crypto');
const router = express.Router(); const router = express.Router();
const { titleConvo, askBing } = require('../../app/'); const { titleConvo, getCitations, citeText, askBing } = require('../../app/');
const { saveMessage, deleteMessages, saveConvo } = require('../../models'); const { saveMessage, deleteMessages, saveConvo } = require('../../models');
const { handleError, sendMessage } = require('./handlers'); const { handleError, sendMessage } = require('./handlers');
const citationRegex = /\[\^\d+?\^]/g;
router.post('/', async (req, res) => { router.post('/', async (req, res) => {
const { model, text, ...convo } = req.body; const { model, text, ...convo } = req.body;
@ -29,6 +30,7 @@ router.post('/', async (req, res) => {
const progressCallback = async (partial) => { const progressCallback = async (partial) => {
tokens += partial === text ? '' : partial; tokens += partial === text ? '' : partial;
// tokens = appendCode(tokens); // tokens = appendCode(tokens);
tokens = citeText(tokens, true);
sendMessage(res, { text: tokens, message: true }); sendMessage(res, { text: tokens, message: true });
}; };
@ -38,8 +40,9 @@ router.post('/', async (req, res) => {
convo convo
}); });
console.log('CLIENT RESPONSE'); console.log('BING RESPONSE');
console.dir(response, { depth: null }); // console.dir(response, { depth: null });
const hasCitations = response.response.match(citationRegex)?.length > 0;
userMessage.conversationSignature = userMessage.conversationSignature =
convo.conversationSignature || response.conversationSignature; convo.conversationSignature || response.conversationSignature;
@ -48,16 +51,27 @@ router.post('/', async (req, res) => {
await saveMessage(userMessage); await saveMessage(userMessage);
if (!convo.conversationSignature) { if (!convo.conversationSignature) {
response.title = await titleConvo(text, response.response, model); response.title = await titleConvo({
model,
message: text,
response: JSON.stringify(response.response)
});
} }
response.text = response.response; response.text = response.response;
delete response.response;
response.id = response.details.messageId; response.id = response.details.messageId;
response.suggestions = response.suggestions =
response.details.suggestedResponses && response.details.suggestedResponses &&
response.details.suggestedResponses.map((s) => s.text); response.details.suggestedResponses.map((s) => s.text);
response.sender = model; response.sender = model;
response.final = true; response.final = true;
const links = getCitations(response);
response.text =
citeText(response) +
(links?.length > 0 && hasCitations ? `\n<small>${links}</small>` : '');
await saveMessage(response); await saveMessage(response);
await saveConvo(response); await saveConvo(response);
sendMessage(res, response); sendMessage(res, response);

View file

@ -0,0 +1,96 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { titleConvo, getCitations, citeText, askSydney } = require('../../app/');
const { saveMessage, deleteMessages, saveConvo, getConvoTitle } = require('../../models');
const { handleError, sendMessage } = require('./handlers');
const citationRegex = /\[\^\d+?\^]/g;
router.post('/', async (req, res) => {
const { model, text, ...convo } = req.body;
if (!text.trim().includes(' ') && text.length < 5) {
return handleError(res, 'Prompt empty or too short');
}
const userMessageId = crypto.randomUUID();
let userMessage = { id: userMessageId, sender: 'User', text };
console.log('ask log', { model, ...userMessage, ...convo });
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
try {
let tokens = '';
const progressCallback = async (partial) => {
tokens += partial === text ? '' : partial;
// tokens = appendCode(tokens);
tokens = citeText(tokens, true);
sendMessage(res, { text: tokens, message: true });
};
let response = await askSydney({
text,
progressCallback,
convo
});
console.log('SYDNEY RESPONSE');
console.log(response.response);
// console.dir(response, { depth: null });
const hasCitations = response.response.match(citationRegex)?.length > 0;
// Save sydney response
response.id = response.messageId;
// response.parentMessageId = convo.parentMessageId ? convo.parentMessageId : response.messageId;
response.parentMessageId = response.messageId;
response.invocationId = convo.invocationId ? convo.invocationId + 1 : 1;
response.title = convo.jailbreakConversationId
? await getConvoTitle(convo.conversationId)
: await titleConvo({
model,
message: text,
response: JSON.stringify(response.response)
});
response.conversationId = convo.conversationId
? convo.conversationId
: crypto.randomUUID();
response.conversationSignature = convo.conversationSignature
? convo.conversationSignature
: crypto.randomUUID();
response.text = response.response;
delete response.response;
response.suggestions =
response.details.suggestedResponses &&
response.details.suggestedResponses.map((s) => s.text);
response.sender = model;
response.final = true;
const links = getCitations(response);
response.text =
citeText(response) +
(links?.length > 0 && hasCitations ? `\n<small>${links}</small>` : '');
// Save user message
userMessage.conversationId = response.conversationId;
userMessage.parentMessageId = response.parentMessageId;
await saveMessage(userMessage);
// Save sydney response & convo, then send
await saveMessage(response);
await saveConvo(response);
sendMessage(res, response);
res.end();
} catch (error) {
console.log(error);
await deleteMessages({ id: userMessageId });
handleError(res, error.message);
}
});
module.exports = router;

View file

@ -16,7 +16,7 @@ export default function Conversation({
title = 'New conversation', title = 'New conversation',
bingData, bingData,
chatGptLabel = null, chatGptLabel = null,
promptPrefix = null, promptPrefix = null
}) { }) {
const [renaming, setRenaming] = useState(false); const [renaming, setRenaming] = useState(false);
const [titleInput, setTitleInput] = useState(title); const [titleInput, setTitleInput] = useState(title);
@ -34,11 +34,18 @@ export default function Conversation({
const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix }; const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix };
if (bingData) { if (bingData) {
const { conversationSignature, clientId, invocationId } = bingData; const {
parentMessageId,
conversationSignature,
jailbreakConversationId,
clientId,
invocationId
} = bingData;
dispatch( dispatch(
setConversation({ setConversation({
...convo, ...convo,
parentMessageId: null, parentMessageId,
jailbreakConversationId,
conversationSignature, conversationSignature,
clientId, clientId,
invocationId invocationId
@ -49,6 +56,7 @@ export default function Conversation({
setConversation({ setConversation({
...convo, ...convo,
parentMessageId, parentMessageId,
jailbreakConversationId: null,
conversationSignature: null, conversationSignature: null,
clientId: null, clientId: null,
invocationId: null invocationId: null

View file

@ -3,7 +3,7 @@ import TrashIcon from '../svg/TrashIcon';
import CrossIcon from '../svg/CrossIcon'; import CrossIcon from '../svg/CrossIcon';
import manualSWR from '~/utils/fetchers'; import manualSWR from '~/utils/fetchers';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { setConversation, removeConvo } from '~/store/convoSlice'; import { setNewConvo, removeConvo } from '~/store/convoSlice';
import { setMessages } from '~/store/messageSlice'; import { setMessages } from '~/store/messageSlice';
export default function DeleteButton({ conversationId, renaming, cancelHandler }) { export default function DeleteButton({ conversationId, renaming, cancelHandler }) {
@ -14,7 +14,7 @@ export default function DeleteButton({ conversationId, renaming, cancelHandler }
() => { () => {
dispatch(setMessages([])); dispatch(setMessages([]));
dispatch(removeConvo(conversationId)); dispatch(removeConvo(conversationId));
dispatch(setConversation({ title: 'New chat', conversationId: null, parentMessageId: null })); dispatch(setNewConvo());
} }
); );

View file

@ -14,7 +14,9 @@ export default function Conversations({ conversations, conversationId, showMore
conversations.map((convo) => { conversations.map((convo) => {
const bingData = convo.conversationSignature const bingData = convo.conversationSignature
? { ? {
jailbreakConversationId: convo.jailbreakConversationId,
conversationSignature: convo.conversationSignature, conversationSignature: convo.conversationSignature,
parentMessageId: convo.parentMessageId || null,
clientId: convo.clientId, clientId: convo.clientId,
invocationId: convo.invocationId invocationId: convo.invocationId
} }

View file

@ -46,8 +46,10 @@ export default function TextChat({ messages }) {
setMessages([...messages, currentMsg, { sender, text: data.text || data.response }]) setMessages([...messages, currentMsg, { sender, text: data.text || data.response }])
); );
const isBing = model === 'bingai' || model === 'sydney';
if ( if (
model !== 'bingai' && !isBing &&
convo.conversationId === null && convo.conversationId === null &&
convo.parentMessageId === null convo.parentMessageId === null
) { ) {
@ -57,6 +59,7 @@ export default function TextChat({ messages }) {
title, title,
conversationId, conversationId,
parentMessageId: id, parentMessageId: id,
jailbreakConversationId: null,
conversationSignature: null, conversationSignature: null,
clientId: null, clientId: null,
invocationId: null, invocationId: null,
@ -69,15 +72,43 @@ export default function TextChat({ messages }) {
convo.conversationId === null && convo.conversationId === null &&
convo.invocationId === null convo.invocationId === null
) { ) {
const { title, conversationSignature, clientId, conversationId, invocationId } = data; console.log('Bing data:', data)
const {
title,
conversationSignature,
clientId,
conversationId,
invocationId
} = data;
dispatch( dispatch(
setConversation({ setConversation({
title, title,
parentMessageId: null,
conversationSignature,
clientId,
conversationId,
invocationId,
})
);
} else if (model === 'sydney') {
const {
title,
jailbreakConversationId,
parentMessageId,
conversationSignature,
clientId,
conversationId,
invocationId
} = data;
dispatch(
setConversation({
title,
jailbreakConversationId,
parentMessageId,
conversationSignature, conversationSignature,
clientId, clientId,
conversationId, conversationId,
invocationId, invocationId,
parentMessageId: null
}) })
); );
} }
@ -85,24 +116,6 @@ export default function TextChat({ messages }) {
dispatch(setSubmitState(false)); dispatch(setSubmitState(false));
}; };
// const convoHandler = (data) => {
// const { conversationId, id, invocationId } = data;
// const conversationData = {
// title: data.title,
// conversationId,
// parentMessageId:
// model !== 'bingai' && !convo.conversationId && !convo.parentMessageId ? id : null,
// conversationSignature:
// model === 'bingai' && !convo.conversationId ? data.conversationSignature : null,
// clientId: model === 'bingai' && !convo.conversationId ? data.clientId : null,
// // invocationId: model === 'bingai' && !convo.conversationId ? data.invocationId : null
// invocationId: invocationId ? invocationId : null
// };
// dispatch(setMessages([...messages, currentMsg, { sender: model, text: data.text || data.response }]));
// dispatch(setConversation(conversationData));
// dispatch(setSubmitState(false));
// };
const errorHandler = (event) => { const errorHandler = (event) => {
console.log('Error:', event); console.log('Error:', event);
const errorResponse = { const errorResponse = {

View file

@ -48,9 +48,12 @@ export default function Message({
const bgColors = { const bgColors = {
chatgpt: 'rgb(16, 163, 127)', chatgpt: 'rgb(16, 163, 127)',
chatgptBrowser: 'rgb(25, 207, 207)', chatgptBrowser: 'rgb(25, 207, 207)',
bingai: '' bingai: '',
sydney: '',
}; };
const isBing = sender === 'bingai' || sender === 'sydney';
let icon = `${sender}:`; let icon = `${sender}:`;
let backgroundColor = bgColors[sender]; let backgroundColor = bgColors[sender];
@ -59,13 +62,13 @@ export default function Message({
'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-[#444654]';
} }
if ((notUser && backgroundColor) || sender === 'bingai') { if ((notUser && backgroundColor) || isBing) {
icon = ( icon = (
<div <div
style={{ backgroundColor }} style={isBing ? { background: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)' } : { backgroundColor }}
className="relative flex h-[30px] w-[30px] items-center justify-center rounded-sm p-1 text-white" className="relative flex h-[30px] w-[30px] items-center justify-center rounded-sm p-1 text-white"
> >
{sender === 'bingai' ? <BingIcon /> : <GPTIcon />} {isBing ? <BingIcon /> : <GPTIcon />}
{error && ( {error && (
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white"> <span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
! !

View file

@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { setModel, setCustomGpt } from '~/store/submitSlice'; import { setModel, setCustomGpt } from '~/store/submitSlice';
import { setNewConvo } from '~/store/convoSlice';
import manualSWR from '~/utils/fetchers'; import manualSWR from '~/utils/fetchers';
import { Button } from '../ui/Button.tsx'; import { Button } from '../ui/Button.tsx';
import { Input } from '../ui/Input.tsx'; import { Input } from '../ui/Input.tsx';
@ -36,7 +37,8 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
dispatch(setModel('chatgptCustom')); dispatch(setModel('chatgptCustom'));
handleSaveState(chatGptLabel.toLowerCase()); handleSaveState(chatGptLabel.toLowerCase());
// dispatch(setDisabled(false)); // Set new conversation
dispatch(setNewConvo());
}; };
const saveHandler = (e) => { const saveHandler = (e) => {

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { setModel, setDisabled, setCustomGpt, setCustomModel } from '~/store/submitSlice'; import { setModel, setDisabled, setCustomGpt, setCustomModel } from '~/store/submitSlice';
import { setConversation } from '~/store/convoSlice'; import { setNewConvo } from '~/store/convoSlice';
import ModelDialog from './ModelDialog'; import ModelDialog from './ModelDialog';
import MenuItems from './MenuItems'; import MenuItems from './MenuItems';
import manualSWR from '~/utils/fetchers'; import manualSWR from '~/utils/fetchers';
@ -68,22 +68,15 @@ export default function ModelMenu() {
dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
dispatch(setModel('chatgptCustom')); dispatch(setModel('chatgptCustom'));
dispatch(setCustomModel(value)); dispatch(setCustomModel(value));
if (custom) { // if (custom) {
setMenuOpen((prevOpen) => !prevOpen); // setMenuOpen((prevOpen) => !prevOpen);
} // }
} else if (!modelMap[value]) { } else if (!modelMap[value]) {
dispatch(setCustomModel(null)); dispatch(setCustomModel(null));
} }
// Set new conversation // Set new conversation
dispatch( dispatch(setNewConvo());
setConversation({
title: 'New Chat',
error: false,
conversationId: null,
parentMessageId: null
})
);
}; };
const onOpenChange = (open) => { const onOpenChange = (open) => {
@ -126,8 +119,9 @@ export default function ModelMenu() {
'dark:disabled:hover:bg-transparent' 'dark:disabled:hover:bg-transparent'
]; ];
const isBing = model === 'bingai' || model === 'sydney';
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps; const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
const icon = model === 'bingai' ? <BingIcon button={true} /> : <GPTIcon button={true} />; const icon = isBing ? <BingIcon button={true} /> : <GPTIcon button={true} />;
return ( return (
<Dialog onOpenChange={onOpenChange}> <Dialog onOpenChange={onOpenChange}>

View file

@ -3,7 +3,7 @@ import TrashIcon from '../svg/TrashIcon';
import { useSWRConfig } from 'swr'; import { useSWRConfig } from 'swr';
import manualSWR from '~/utils/fetchers'; import manualSWR from '~/utils/fetchers';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { setConversation, removeAll } from '~/store/convoSlice'; import { setNewConvo, removeAll } from '~/store/convoSlice';
import { setMessages } from '~/store/messageSlice'; import { setMessages } from '~/store/messageSlice';
export default function ClearConvos() { export default function ClearConvos() {
@ -12,14 +12,7 @@ export default function ClearConvos() {
const { trigger } = manualSWR(`http://localhost:3080/api/convos/clear`, 'post', () => { const { trigger } = manualSWR(`http://localhost:3080/api/convos/clear`, 'post', () => {
dispatch(setMessages([])); dispatch(setMessages([]));
dispatch( dispatch(setNewConvo());
setConversation({
error: false,
title: 'New chat',
conversationId: null,
parentMessageId: null
})
);
mutate(`http://localhost:3080/api/convos`); mutate(`http://localhost:3080/api/convos`);
}); });

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { setConversation } from '~/store/convoSlice'; import { setNewConvo } from '~/store/convoSlice';
import { setMessages } from '~/store/messageSlice'; import { setMessages } from '~/store/messageSlice';
import { setText } from '~/store/textSlice'; import { setText } from '~/store/textSlice';
@ -10,7 +10,7 @@ export default function NewChat() {
const clickHandler = () => { const clickHandler = () => {
dispatch(setText('')); dispatch(setText(''));
dispatch(setMessages([])); dispatch(setMessages([]));
dispatch(setConversation({ title: 'New Chat', error: false, conversationId: null, parentMessageId: null })); dispatch(setNewConvo());
}; };
return ( return (

View file

@ -5,6 +5,7 @@ const initialState = {
title: 'ChatGPT Clone', title: 'ChatGPT Clone',
conversationId: null, conversationId: null,
parentMessageId: null, parentMessageId: null,
jailbreakConversationId: null,
conversationSignature: null, conversationSignature: null,
clientId: null, clientId: null,
invocationId: null, invocationId: null,
@ -28,6 +29,20 @@ const currentSlice = createSlice({
incrementPage: (state) => { incrementPage: (state) => {
state.pageNumber = state.pageNumber + 1; state.pageNumber = state.pageNumber + 1;
}, },
setNewConvo: (state) => {
state.error = false;
state.title = 'New Chat';
state.conversationId = null;
state.parentMessageId = null;
state.jailbreakConversationId = null;
state.conversationSignature = null;
state.clientId = null;
state.invocationId = null;
state.chatGptLabel = null;
state.promptPrefix = null;
state.convosLoading = false;
state.pageNumber = 1;
},
setConvos: (state, action) => { setConvos: (state, action) => {
const newConvos = action.payload.filter((convo) => { const newConvos = action.payload.filter((convo) => {
return !state.convos.some((c) => c.conversationId === convo.conversationId); return !state.convos.some((c) => c.conversationId === convo.conversationId);
@ -45,7 +60,7 @@ const currentSlice = createSlice({
} }
}); });
export const { setConversation, setConvos, setError, incrementPage, removeConvo, removeAll } = export const { setConversation, setConvos, setNewConvo, setError, incrementPage, removeConvo, removeAll } =
currentSlice.actions; currentSlice.actions;
export default currentSlice.reducer; export default currentSlice.reducer;

View file

@ -16,16 +16,21 @@ const initialState = {
_id: '2', _id: '2',
name: 'BingAI', name: 'BingAI',
value: 'bingai' value: 'bingai'
} },
// { {
// _id: '3', _id: '3',
// name: 'ChatGPT', name: 'Sydney',
// value: 'chatgptBrowser' value: 'sydney'
// } },
{
_id: '4',
name: 'ChatGPT',
value: 'chatgptBrowser'
},
], ],
modelMap: {}, modelMap: {},
// initial: { chatgpt: true, chatgptCustom: true, bingai: true, chatgptBrowser: true } initial: { chatgpt: true, chatgptCustom: true, bingai: true, sydney: true, chatgptBrowser: true }
initial: { chatgpt: true, chatgptCustom: true, bingai: true, } // initial: { chatgpt: true, chatgptCustom: true, bingai: true, }
}; };
const currentSlice = createSlice({ const currentSlice = createSlice({

View file

@ -7,6 +7,32 @@
outline: 1px solid limegreen !important; outline: 1px solid limegreen !important;
} */ } */
/* p small {
opacity: 0;
animation: fadeIn 3s ease forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
} */
p > small {
opacity: 0;
animation: fadein 3s forwards;
}
@keyframes fadein {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
blockquote, dd, dl, fieldset, figure, h1, h2, h3, h4, h5, h6, hr, p, pre { blockquote, dd, dl, fieldset, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {
margin: 0; margin: 0;
} }

View file

@ -21,9 +21,12 @@ export default function handleSubmit({
}; };
} }
if (model === 'bingai' && convo.conversationId) { const isBing = model === 'bingai' || model === 'sydney';
if (isBing && convo.conversationId) {
payload = { payload = {
...payload, ...payload,
jailbreakConversationId: convo.jailbreakConversationId,
conversationId: convo.conversationId, conversationId: convo.conversationId,
conversationSignature: convo.conversationSignature, conversationSignature: convo.conversationSignature,
clientId: convo.clientId, clientId: convo.clientId,
@ -31,7 +34,10 @@ export default function handleSubmit({
}; };
} }
const server = model === 'bingai' ? endpoint + '/bing' : endpoint; let server = endpoint;
server = model === 'bingai' ? server + '/bing' : server;
server = model === 'sydney' ? server + '/sydney' : server;
const events = new SSE(server, { const events = new SSE(server, {
payload: JSON.stringify(payload), payload: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }

View file

@ -10,6 +10,7 @@ module.exports = {
* to use its built-in optimizations accordingly. default is production * to use its built-in optimizations accordingly. default is production
*/ */
mode: 'development', mode: 'development',
// cache: false,
/** "entry" /** "entry"
* the entry point * the entry point
*/ */