Build/Refactor: lint pre-commit hook and reformat repo to spec (#314)

* build/refactor: move lint/prettier packages to project root, install husky, add pre-commit hook

* refactor: reformat files

* build: put full eslintrc back with all rules
This commit is contained in:
Dan Orlando 2023-05-18 11:09:31 -07:00 committed by GitHub
parent 8d75b25104
commit 7fdc862042
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
157 changed files with 4836 additions and 2403 deletions

113
.eslintrc.js Normal file
View file

@ -0,0 +1,113 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
commonjs: true,
es6: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
plugins: ['react', 'react-hooks', '@typescript-eslint'],
rules: {
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': 'allow-with-description' }],
indent: ['error', 2, { SwitchCase: 1 }],
'max-len': [
'error',
{
code: 150,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreComments: true
}
],
'linebreak-style': 0,
// "arrow-parens": [2, "as-needed", { requireForBlockBody: true }],
// 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-console': 'off',
'import/extensions': 'off',
'no-use-before-define': [
'error',
{
functions: false
}
],
'no-promise-executor-return': 'off',
'no-param-reassign': 'off',
'no-continue': 'off',
'no-restricted-syntax': 'off',
'react/prop-types': ['off'],
'react/display-name': ['off']
},
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
rules: {
'no-unused-vars': 'off', // off because it conflicts with '@typescript-eslint/no-unused-vars'
'react/display-name': 'off',
'@typescript-eslint/no-unused-vars': 'warn'
}
},
{
files: ['rollup.config.js', '.eslintrc.js', 'jest.config.js'],
env: {
node: true
}
},
{
files: [
'**/*.test.js',
'**/*.test.jsx',
'**/*.test.ts',
'**/*.test.tsx',
'**/*.spec.js',
'**/*.spec.jsx',
'**/*.spec.ts',
'**/*.spec.tsx',
'setupTests.js'
],
env: {
jest: true,
node: true
},
rules: {
'react/display-name': 'off',
'react/prop-types': 'off',
'react/no-unescaped-entities': 'off'
}
},
{
files: '**/*.+(ts)',
parser: '@typescript-eslint/parser',
parserOptions: {
project: './client/tsconfig.json'
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended'
]
}
],
settings: {
react: {
createClass: 'createReactClass', // Regex for Component Factory to use,
// default to "createReactClass"
pragma: 'React', // Pragma to use, default to "React"
fragment: 'Fragment', // Fragment to use (may be a property of <pragma>), default to "Fragment"
version: 'detect' // React version. "detect" automatically picks the version you have installed.
}
}
};

5
.husky/pre-commit Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

19
.prettierrc.js Normal file
View file

@ -0,0 +1,19 @@
module.exports = {
printWidth: 100,
useTabs: false,
tabWidth: 2,
semi: true,
singleQuote: true,
// bracketSpacing: false,
trailingComma: 'none',
arrowParens: 'always',
embeddedLanguageFormatting: 'auto',
insertPragma: false,
proseWrap: 'preserve',
quoteProps: 'as-needed',
requirePragma: false,
rangeStart: 0,
endOfLine: 'auto',
jsxBracketSameLine: false,
jsxSingleQuote: false,
};

View file

@ -1,39 +0,0 @@
module.exports = {
env: {
es2021: true,
node: true
},
extends: ['eslint:recommended'],
overrides: [],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
indent: ['error', 2, { SwitchCase: 1 }],
'max-len': [
'error',
{
code: 150,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreComments: true
}
],
'linebreak-style': 0,
'arrow-parens': [2, 'as-needed', { requireForBlockBody: true }],
// 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
'no-console': 'off',
'import/extensions': 'off',
'no-use-before-define': [
'error',
{
functions: false
}
],
'no-promise-executor-return': 'off',
'no-param-reassign': 'off',
'no-continue': 'off',
'no-restricted-syntax': 'off'
}
};

View file

@ -1,22 +0,0 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"singleAttributePerLine": true,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 110,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"parser": "babel"
}

View file

@ -23,7 +23,8 @@ const askBing = async ({
const bingAIClient = new BingAIClient({ const bingAIClient = new BingAIClient({
// "_U" cookie from bing.com // "_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 // If the above doesn't work, provide all your cookies as a string instead
// cookies: '', // cookies: '',
debug: false, debug: false,

View file

@ -18,9 +18,11 @@ const browserClient = async ({
const clientOptions = { 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: process.env.CHATGPT_REVERSE_PROXY || 'https://ai.fakeopen.com/api/conversation', reverseProxyUrl:
process.env.CHATGPT_REVERSE_PROXY || 'https://ai.fakeopen.com/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 == 'user_provided' ? token : process.env.CHATGPT_TOKEN ?? null, accessToken:
process.env.CHATGPT_TOKEN == 'user_provided' ? token : process.env.CHATGPT_TOKEN ?? null,
model: model, model: model,
debug: false, debug: false,
proxy: process.env.PROXY || null, proxy: process.env.PROXY || null,

View file

@ -1,7 +1,7 @@
require('dotenv').config(); require('dotenv').config();
const { KeyvFile } = require('keyv-file'); const { KeyvFile } = require('keyv-file');
const { genAzureEndpoint } = require('../../utils/genAzureEndpoints'); const { genAzureEndpoint } = require('../../utils/genAzureEndpoints');
const tiktoken = require("@dqbd/tiktoken"); const tiktoken = require('@dqbd/tiktoken');
const encoding_for_model = tiktoken.encoding_for_model; const encoding_for_model = tiktoken.encoding_for_model;
const askClient = async ({ const askClient = async ({
@ -27,7 +27,7 @@ const askClient = async ({
const azure = process.env.AZURE_OPENAI_API_KEY ? true : false; const azure = process.env.AZURE_OPENAI_API_KEY ? true : false;
if (promptPrefix == null) { if (promptPrefix == null) {
promptText = "You are ChatGPT, a large language model trained by OpenAI."; promptText = 'You are ChatGPT, a large language model trained by OpenAI.';
} else { } else {
promptText = promptPrefix; promptText = promptPrefix;
} }
@ -45,7 +45,7 @@ const askClient = async ({
}, },
chatGptLabel, chatGptLabel,
promptPrefix, promptPrefix,
proxy: process.env.PROXY || null, proxy: process.env.PROXY || null
// debug: true // debug: true
}; };
@ -77,16 +77,16 @@ const askClient = async ({
const res = await client.sendMessage(text, { ...options, userId }); const res = await client.sendMessage(text, { ...options, userId });
// return res; // return res;
// create a new response object that includes the token counts // create a new response object that includes the token counts
const newRes = { const newRes = {
...res, ...res,
usage: { usage: {
prompt_tokens: prompt_tokens.length, prompt_tokens: prompt_tokens.length,
completion_tokens: text_tokens.length, completion_tokens: text_tokens.length,
total_tokens: prompt_tokens.length + text_tokens.length total_tokens: prompt_tokens.length + text_tokens.length
} }
}; };
return newRes; return newRes;
}; };
module.exports = { askClient }; module.exports = { askClient };

View file

@ -3,7 +3,10 @@ const TextStream = require('../stream');
const { google } = require('googleapis'); const { google } = require('googleapis');
const { Agent, ProxyAgent } = require('undici'); const { Agent, ProxyAgent } = require('undici');
const { getMessages, saveMessage, saveConvo } = require('../../models'); const { getMessages, saveMessage, saveConvo } = require('../../models');
const { encoding_for_model: encodingForModel, get_encoding: getEncoding } = require('@dqbd/tiktoken'); const {
encoding_for_model: encodingForModel,
get_encoding: getEncoding
} = require('@dqbd/tiktoken');
const tokenizersCache = {}; const tokenizersCache = {};
@ -65,7 +68,8 @@ class GoogleAgent {
// The max prompt tokens is determined by the max context tokens minus the max response tokens. // The max prompt tokens is determined by the max context tokens minus the max response tokens.
// Earlier messages will be dropped until the prompt is within the limit. // Earlier messages will be dropped until the prompt is within the limit.
this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1024; this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1024;
this.maxPromptTokens = this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens; this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) { if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) {
throw new Error( throw new Error(
@ -291,7 +295,10 @@ class GoogleAgent {
try { try {
const result = await this.getCompletion(message, messages, opts.abortController); const result = await this.getCompletion(message, messages, opts.abortController);
blocked = result?.predictions?.[0]?.safetyAttributes?.blocked; blocked = result?.predictions?.[0]?.safetyAttributes?.blocked;
reply = result?.predictions?.[0]?.candidates?.[0]?.content || result?.predictions?.[0]?.content || ''; reply =
result?.predictions?.[0]?.candidates?.[0]?.content ||
result?.predictions?.[0]?.content ||
'';
if (blocked === true) { if (blocked === true) {
reply = `Google blocked a proper response to your message:\n${JSON.stringify( reply = `Google blocked a proper response to your message:\n${JSON.stringify(
result.predictions[0].safetyAttributes result.predictions[0].safetyAttributes

View file

@ -16,10 +16,7 @@ class TextStream extends Readable {
if (this.currentIndex < this.text.length) { if (this.currentIndex < this.text.length) {
setTimeout(() => { setTimeout(() => {
const remainingChars = this.text.length - this.currentIndex; const remainingChars = this.text.length - this.currentIndex;
const chunkSize = Math.min( const chunkSize = Math.min(this.randomInt(minChunkSize, maxChunkSize + 1), remainingChars);
this.randomInt(minChunkSize, maxChunkSize + 1),
remainingChars
);
const chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize); const chunk = this.text.slice(this.currentIndex, this.currentIndex + chunkSize);
this.push(chunk); this.push(chunk);

View file

@ -7,7 +7,7 @@ const getCitations = (res) => {
if (!textBlocks) return ''; if (!textBlocks) return '';
let links = textBlocks[textBlocks.length - 1]?.text.match(regex); let links = textBlocks[textBlocks.length - 1]?.text.match(regex);
if (links?.length === 0 || !links) return ''; if (links?.length === 0 || !links) return '';
links = links.map((link) => link.trim()); links = links.map(link => link.trim());
return links.join('\n'); return links.join('\n');
}; };

View file

@ -19,7 +19,7 @@ const requireLocalAuth = (req, res, next) => {
} }
if (!user) { if (!user) {
log({ log({
title: '(requireLocalAuth) Error: No user', title: '(requireLocalAuth) Error: No user'
}); });
return res.status(422).send(info); return res.status(422).send(info);
} }

View file

@ -41,7 +41,6 @@ module.exports = {
text, text,
isCreatedByUser isCreatedByUser
}; };
} catch (err) { } catch (err) {
console.error(`Error saving message: ${err}`); console.error(`Error saving message: ${err}`);
throw new Error('Failed to save message.'); throw new Error('Failed to save message.');
@ -57,7 +56,6 @@ module.exports = {
.deleteMany({ createdAt: { $gt: message.createdAt } }) .deleteMany({ createdAt: { $gt: message.createdAt } })
.exec(); .exec();
} }
} catch (err) { } catch (err) {
console.error(`Error deleting messages: ${err}`); console.error(`Error deleting messages: ${err}`);
throw new Error('Failed to delete messages.'); throw new Error('Failed to delete messages.');
@ -67,7 +65,6 @@ module.exports = {
async getMessages(filter) { async getMessages(filter) {
try { try {
return await Message.find(filter).sort({ createdAt: 1 }).exec(); return await Message.find(filter).sort({ createdAt: 1 }).exec();
} catch (err) { } catch (err) {
console.error(`Error getting messages: ${err}`); console.error(`Error getting messages: ${err}`);
throw new Error('Failed to get messages.'); throw new Error('Failed to get messages.');
@ -77,7 +74,6 @@ module.exports = {
async deleteMessages(filter) { async deleteMessages(filter) {
try { try {
return await Message.deleteMany(filter).exec(); return await Message.deleteMany(filter).exec();
} catch (err) { } catch (err) {
console.error(`Error deleting messages: ${err}`); console.error(`Error deleting messages: ${err}`);
throw new Error('Failed to delete messages.'); throw new Error('Failed to delete messages.');

View file

@ -1,6 +1,7 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const promptSchema = mongoose.Schema({ const promptSchema = mongoose.Schema(
{
title: { title: {
type: String, type: String,
required: true required: true
@ -10,9 +11,11 @@ const promptSchema = mongoose.Schema({
required: true required: true
}, },
category: { category: {
type: String, type: String
}
}, },
}, { timestamps: true }); { timestamps: true }
);
const Prompt = mongoose.models.Prompt || mongoose.model('Prompt', promptSchema); const Prompt = mongoose.models.Prompt || mongoose.model('Prompt', promptSchema);
@ -31,7 +34,7 @@ module.exports = {
}, },
getPrompts: async (filter) => { getPrompts: async (filter) => {
try { try {
return await Prompt.find(filter).exec() return await Prompt.find(filter).exec();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return { prompt: 'Error getting prompts' }; return { prompt: 'Error getting prompts' };
@ -39,10 +42,10 @@ module.exports = {
}, },
deletePrompts: async (filter) => { deletePrompts: async (filter) => {
try { try {
return await Prompt.deleteMany(filter).exec() return await Prompt.deleteMany(filter).exec();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return { prompt: 'Error deleting prompts' }; return { prompt: 'Error deleting prompts' };
} }
} }
} };

View file

@ -142,7 +142,6 @@ userSchema.methods.comparePassword = function (candidatePassword, callback) {
}; };
module.exports.hashPassword = async (password) => { module.exports.hashPassword = async (password) => {
const hashedPassword = await new Promise((resolve, reject) => { const hashedPassword = await new Promise((resolve, reject) => {
bcrypt.hash(password, 10, function (err, hash) { bcrypt.hash(password, 10, function (err, hash) {
if (err) reject(err); if (err) reject(err);

View file

@ -19,10 +19,7 @@ const createMeiliMongooseModel = function ({ index, indexName, client, attribute
static async clearMeiliIndex() { static async clearMeiliIndex() {
await index.delete(); await index.delete();
// await index.deleteAllDocuments(); // await index.deleteAllDocuments();
await this.collection.updateMany( await this.collection.updateMany({ _meiliIndex: true }, { $set: { _meiliIndex: false } });
{ _meiliIndex: true },
{ $set: { _meiliIndex: false } }
);
} }
static async resetIndex() { static async resetIndex() {
@ -67,7 +64,7 @@ const createMeiliMongooseModel = function ({ index, indexName, client, attribute
return { ...results, [key]: 1 }; return { ...results, [key]: 1 };
}, },
{ _id: 1 } { _id: 1 }
), )
); );
// Add additional data from mongodb into Meili search hits // Add additional data from mongodb into Meili search hits

View file

@ -1,22 +1,22 @@
const mongoose = require("mongoose"); const mongoose = require('mongoose');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const tokenSchema = new Schema({ const tokenSchema = new Schema({
userId: { userId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
required: true, required: true,
ref: "user", ref: 'user'
}, },
token: { token: {
type: String, type: String,
required: true, required: true
}, },
createdAt: { createdAt: {
type: Date, type: Date,
required: true, required: true,
default: Date.now, default: Date.now,
expires: 900, expires: 900
}, }
}); });
module.exports = mongoose.model("Token", tokenSchema); module.exports = mongoose.model('Token', tokenSchema);

View file

@ -3,28 +3,26 @@ const {
logoutUser, logoutUser,
registerUser, registerUser,
requestPasswordReset, requestPasswordReset,
resetPassword, resetPassword
} = require("../services/auth.service"); } = require('../services/auth.service');
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const loginController = async (req, res) => { const loginController = async (req, res) => {
try { try {
const token = req.user.generateToken(); const token = req.user.generateToken();
const user = await loginUser(req.user) const user = await loginUser(req.user);
if(user) { if (user) {
res.cookie('token', token, { res.cookie('token', token, {
expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)), expires: new Date(Date.now() + eval(process.env.SESSION_EXPIRY)),
httpOnly: false, httpOnly: false,
secure: isProduction secure: isProduction
}); });
res.status(200).send({ token, user }); res.status(200).send({ token, user });
} } else {
else {
return res.status(400).json({ message: 'Invalid credentials' }); return res.status(400).json({ message: 'Invalid credentials' });
} }
} } catch (err) {
catch (err) {
console.log(err); console.log(err);
return res.status(500).json({ message: err.message }); return res.status(500).json({ message: err.message });
} }
@ -35,22 +33,20 @@ const logoutController = async (req, res) => {
const { refreshToken } = signedCookies; const { refreshToken } = signedCookies;
try { try {
const logout = await logoutUser(req.user, refreshToken); const logout = await logoutUser(req.user, refreshToken);
console.log(logout) console.log(logout);
const { status, message } = logout; const { status, message } = logout;
if (status === 200) { if (status === 200) {
res.clearCookie('token'); res.clearCookie('token');
res.clearCookie('refreshToken'); res.clearCookie('refreshToken');
res.status(status).send({ message }); res.status(status).send({ message });
} } else {
else {
res.status(status).send({ message }); res.status(status).send({ message });
} }
} } catch (err) {
catch (err) {
console.log(err); console.log(err);
return res.status(500).json({ message: err.message }); return res.status(500).json({ message: err.message });
} }
} };
const registrationController = async (req, res) => { const registrationController = async (req, res) => {
try { try {
@ -65,13 +61,11 @@ const registrationController = async (req, res) => {
secure: isProduction secure: isProduction
}); });
res.status(status).send({ user }); res.status(status).send({ user });
} } else {
else {
const { status, message } = response; const { status, message } = response;
res.status(status).send({ message }); res.status(status).send({ message });
} }
} } catch (err) {
catch (err) {
console.log(err); console.log(err);
return res.status(500).json({ message: err.message }); return res.status(500).json({ message: err.message });
} }
@ -83,17 +77,13 @@ const getUserController = async (req, res) => {
const resetPasswordRequestController = async (req, res) => { const resetPasswordRequestController = async (req, res) => {
try { try {
const resetService = await requestPasswordReset( const resetService = await requestPasswordReset(req.body.email);
req.body.email
);
if (resetService.link) { if (resetService.link) {
return res.status(200).json(resetService); return res.status(200).json(resetService);
} } else {
else {
return res.status(400).json(resetService); return res.status(400).json(resetService);
} }
} } catch (e) {
catch (e) {
console.log(e); console.log(e);
return res.status(400).json({ message: e.message }); return res.status(400).json({ message: e.message });
} }
@ -106,14 +96,12 @@ const resetPasswordController = async (req, res) => {
req.body.token, req.body.token,
req.body.password req.body.password
); );
if(resetPasswordService instanceof Error) { if (resetPasswordService instanceof Error) {
return res.status(400).json(resetPasswordService); return res.status(400).json(resetPasswordService);
} } else {
else {
return res.status(200).json(resetPasswordService); return res.status(200).json(resetPasswordService);
} }
} } catch (e) {
catch (e) {
console.log(e); console.log(e);
return res.status(400).json({ message: e.message }); return res.status(400).json({ message: e.message });
} }
@ -176,5 +164,5 @@ module.exports = {
refreshController, refreshController,
registrationController, registrationController,
resetPasswordRequestController, resetPasswordRequestController,
resetPasswordController, resetPasswordController
}; };

View file

@ -30,13 +30,13 @@ const projectPath = path.join(__dirname, '..', '..', 'client');
app.use(passport.initialize()); app.use(passport.initialize());
require('../strategies/jwtStrategy'); require('../strategies/jwtStrategy');
require('../strategies/localStrategy'); require('../strategies/localStrategy');
if(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) { if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
require('../strategies/googleStrategy'); require('../strategies/googleStrategy');
} }
if(process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) { if (process.env.FACEBOOK_CLIENT_ID && process.env.FACEBOOK_CLIENT_SECRET) {
require('../strategies/facebookStrategy'); require('../strategies/facebookStrategy');
} }
app.use('/oauth', routes.oauth) app.use('/oauth', routes.oauth);
// api endpoint // api endpoint
app.use('/api/auth', routes.auth); app.use('/api/auth', routes.auth);
app.use('/api/search', routes.search); app.use('/api/search', routes.search);
@ -48,8 +48,6 @@ const projectPath = path.join(__dirname, '..', '..', 'client');
app.use('/api/tokenizer', routes.tokenizer); app.use('/api/tokenizer', routes.tokenizer);
app.use('/api/endpoints', routes.endpoints); app.use('/api/endpoints', routes.endpoints);
// static files // static files
app.get('/*', function (req, res) { app.get('/*', function (req, res) {
res.sendFile(path.join(projectPath, 'dist', 'index.html')); res.sendFile(path.join(projectPath, 'dist', 'index.html'));
@ -60,7 +58,8 @@ const projectPath = path.join(__dirname, '..', '..', 'client');
console.log( console.log(
`Server listening on all interface at port ${port}. Use http://localhost:${port} to access it` `Server listening on all interface at port ${port}. Use http://localhost:${port} to access it`
); );
else console.log(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); else
console.log(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`);
}); });
})(); })();

View file

@ -147,11 +147,13 @@ const ask = async ({
const newConversationId = endpointOption?.jailbreak const newConversationId = endpointOption?.jailbreak
? response.jailbreakConversationId ? response.jailbreakConversationId
: response.conversationId || conversationId; : response.conversationId || conversationId;
const newUserMassageId = response.parentMessageId || response.details.requestId || userMessageId; const newUserMassageId =
response.parentMessageId || response.details.requestId || userMessageId;
const newResponseMessageId = response.messageId || response.details.messageId; const newResponseMessageId = response.messageId || response.details.messageId;
// STEP1 generate response message // STEP1 generate response message
response.text = response.response || response.details.spokenText || '**Bing refused to answer.**'; response.text =
response.response || response.details.spokenText || '**Bing refused to answer.**';
let responseMessage = { let responseMessage = {
conversationId: newConversationId, conversationId: newConversationId,
@ -161,7 +163,8 @@ const ask = async ({
sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI', sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI',
text: await handleText(response, true), text: await handleText(response, true),
suggestions: suggestions:
response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text), response.details.suggestedResponses &&
response.details.suggestedResponses.map(s => s.text),
unfinished: false, unfinished: false,
cancelled: false, cancelled: false,
error: false error: false
@ -215,7 +218,11 @@ const ask = async ({
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one. // If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId) if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId }); await saveMessage({
...userMessage,
messageId: userMessageId,
newMessageId: newUserMassageId
});
userMessageId = newUserMassageId; userMessageId = newUserMassageId;
sendMessage(res, { sendMessage(res, {
@ -228,7 +235,11 @@ const ask = async ({
res.end(); res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage }); const title = await titleConvo({
endpoint: endpointOption?.endpoint,
text,
response: responseMessage
});
await saveConvo(req.user.id, { await saveConvo(req.user.id, {
conversationId: conversationId, conversationId: conversationId,

View file

@ -39,7 +39,7 @@ router.post('/', requireJwtAuth, async (req, res) => {
}; };
const availableModels = getChatGPTBrowserModels(); const availableModels = getChatGPTBrowserModels();
if (availableModels.find((model) => model === endpointOption.model) === undefined) if (availableModels.find(model => model === endpointOption.model) === undefined)
return handleError(res, { text: 'Illegal request: model' }); return handleError(res, { text: 'Illegal request: model' });
console.log('ask log', { console.log('ask log', {
@ -180,7 +180,11 @@ const ask = async ({
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one. // If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId) if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId }); await saveMessage({
...userMessage,
messageId: userMessageId,
newMessageId: newUserMassageId
});
userMessageId = newUserMassageId; userMessageId = newUserMassageId;
sendMessage(res, { sendMessage(res, {

View file

@ -238,7 +238,11 @@ const ask = async ({
// If response has parentMessageId, the fake userMessage.messageId should be updated to the real one. // If response has parentMessageId, the fake userMessage.messageId should be updated to the real one.
if (!overrideParentMessageId) if (!overrideParentMessageId)
await saveMessage({ ...userMessage, messageId: userMessageId, newMessageId: newUserMassageId }); await saveMessage({
...userMessage,
messageId: userMessageId,
newMessageId: newUserMassageId
});
userMessageId = newUserMassageId; userMessageId = newUserMassageId;
sendMessage(res, { sendMessage(res, {
@ -251,7 +255,12 @@ const ask = async ({
res.end(); res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ endpoint: endpointOption?.endpoint, text, response: responseMessage, oaiApiKey }); const title = await titleConvo({
endpoint: endpointOption?.endpoint,
text,
response: responseMessage,
oaiApiKey
});
await saveConvo(req.user.id, { await saveConvo(req.user.id, {
conversationId: conversationId, conversationId: conversationId,
title title

View file

@ -6,7 +6,7 @@ const {
loginController, loginController,
logoutController, logoutController,
refreshController, refreshController,
registrationController, registrationController
} = require('../controllers/auth.controller'); } = require('../controllers/auth.controller');
const requireJwtAuth = require('../../middleware/requireJwtAuth'); const requireJwtAuth = require('../../middleware/requireJwtAuth');
const requireLocalAuth = require('../../middleware/requireLocalAuth'); const requireLocalAuth = require('../../middleware/requireLocalAuth');

View file

@ -36,11 +36,12 @@ router.get('/', async function (req, res) {
} }
const google = const google =
key || palmUser ? { userProvide: palmUser, availableModels: ['chat-bison', 'text-bison'] } : false; key || palmUser
? { userProvide: palmUser, availableModels: ['chat-bison', 'text-bison'] }
: false;
const azureOpenAI = !!process.env.AZURE_OPENAI_KEY; const azureOpenAI = !!process.env.AZURE_OPENAI_KEY;
const apiKey = process.env.OPENAI_KEY || process.env.AZURE_OPENAI_API_KEY; const apiKey = process.env.OPENAI_KEY || process.env.AZURE_OPENAI_API_KEY;
const openAI = const openAI = apiKey
apiKey
? { availableModels: getOpenAIModels(), userProvide: apiKey === 'user_provided' } ? { availableModels: getOpenAIModels(), userProvide: apiKey === 'user_provided' }
: false; : false;
const bingAI = process.env.BINGAI_TOKEN const bingAI = process.env.BINGAI_TOKEN

View file

@ -19,5 +19,5 @@ module.exports = {
auth, auth,
oauth, oauth,
tokenizer, tokenizer,
endpoints, endpoints
}; };

View file

@ -27,7 +27,9 @@ router.get('/', requireJwtAuth, async function (req, res) {
console.log('cache hit', key); console.log('cache hit', key);
const cached = cache.get(key); const cached = cache.get(key);
const { pages, pageSize, messages } = cached; const { pages, pageSize, messages } = cached;
res.status(200).send({ conversations: cached[pageNumber], pages, pageNumber, pageSize, messages }); res
.status(200)
.send({ conversations: cached[pageNumber], pages, pageNumber, pageSize, messages });
return; return;
} else { } else {
cache.clear(); cache.clear();
@ -44,7 +46,7 @@ router.get('/', requireJwtAuth, async function (req, res) {
}, },
true true
) )
).hits.map(message => { ).hits.map((message) => {
const { _formatted, ...rest } = message; const { _formatted, ...rest } = message;
return { return {
...rest, ...rest,
@ -95,12 +97,12 @@ router.get('/clear', async function (req, res) {
router.get('/test', async function (req, res) { router.get('/test', async function (req, res) {
const { q } = req.query; const { q } = req.query;
const messages = (await Message.meiliSearch(q, { attributesToHighlight: ['text'] }, true)).hits.map( const messages = (
message => { await Message.meiliSearch(q, { attributesToHighlight: ['text'] }, true)
).hits.map((message) => {
const { _formatted, ...rest } = message; const { _formatted, ...rest } = message;
return { ...rest, searchResult: true, text: _formatted.text }; return { ...rest, searchResult: true, text: _formatted.text };
} });
);
res.send(messages); res.send(messages);
}); });

View file

@ -78,7 +78,7 @@ const registerUser = async (user) => {
} }
//determine if this is the first registered user (not counting anonymous_user) //determine if this is the first registered user (not counting anonymous_user)
const isFirstRegisteredUser = await User.countDocuments({}) === 0; const isFirstRegisteredUser = (await User.countDocuments({})) === 0;
try { try {
const newUser = await new User({ const newUser = await new User({
@ -88,7 +88,7 @@ const registerUser = async (user) => {
username, username,
name, name,
avatar: null, avatar: null,
role: isFirstRegisteredUser ? 'ADMIN' : 'USER', role: isFirstRegisteredUser ? 'ADMIN' : 'USER'
}); });
// todo: implement refresh token // todo: implement refresh token
@ -104,7 +104,7 @@ const registerUser = async (user) => {
newUser.save(); newUser.save();
}); });
}); });
console.log('newUser', newUser) console.log('newUser', newUser);
if (isFirstRegisteredUser) { if (isFirstRegisteredUser) {
migrateDataToFirstUser(newUser); migrateDataToFirstUser(newUser);
// console.log(migrate); // console.log(migrate);
@ -186,12 +186,11 @@ const resetPassword = async (userId, token, password) => {
return { message: 'Password reset was successful' }; return { message: 'Password reset was successful' };
}; };
module.exports = { module.exports = {
// signup, // signup,
registerUser, registerUser,
loginUser, loginUser,
logoutUser, logoutUser,
requestPasswordReset, requestPasswordReset,
resetPassword, resetPassword
}; };

View file

@ -11,7 +11,7 @@ const facebookLogin = new FacebookStrategy(
clientID: process.env.FACEBOOK_APP_ID, clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_SECRET, clientSecret: process.env.FACEBOOK_SECRET,
callbackURL: `${serverUrl}${process.env.FACEBOOK_CALLBACK_URL}`, callbackURL: `${serverUrl}${process.env.FACEBOOK_CALLBACK_URL}`,
proxy: true, proxy: true
// profileFields: [ // profileFields: [
// 'id', // 'id',
// 'email', // 'email',

View file

@ -65,4 +65,3 @@ function log({ title, parameters }) {
DebugControl.log.parameters(parameters); DebugControl.log.parameters(parameters);
} }
} }

View file

@ -3,7 +3,8 @@ const pino = require('pino');
const logger = pino({ const logger = pino({
level: 'info', level: 'info',
redact: { redact: {
paths: [ // List of Paths to redact from the logs (https://getpino.io/#/docs/redaction) paths: [
// List of Paths to redact from the logs (https://getpino.io/#/docs/redaction)
'env.OPENAI_KEY', 'env.OPENAI_KEY',
'env.BINGAI_TOKEN', 'env.BINGAI_TOKEN',
'env.CHATGPT_TOKEN', 'env.CHATGPT_TOKEN',
@ -11,13 +12,15 @@ const logger = pino({
'env.GOOGLE_CLIENT_SECRET', 'env.GOOGLE_CLIENT_SECRET',
'env.JWT_SECRET_DEV', 'env.JWT_SECRET_DEV',
'env.JWT_SECRET_PROD', 'env.JWT_SECRET_PROD',
'newUser.password'], // See example to filter object class instances 'newUser.password'
censor: '***', // Redaction character ], // See example to filter object class instances
}, censor: '***' // Redaction character
}
}); });
// Sanitize outside the logger paths. This is useful for sanitizing variables directly with Regex and patterns. // Sanitize outside the logger paths. This is useful for sanitizing variables directly with Regex and patterns.
const redactPatterns = [ // Array of regular expressions for redacting patterns const redactPatterns = [
// Array of regular expressions for redacting patterns
/api[-_]?key/i, /api[-_]?key/i,
/password/i, /password/i,
/token/i, /token/i,
@ -57,13 +60,11 @@ const levels = {
FATAL: 60 FATAL: 60
}; };
let level = levels.INFO; let level = levels.INFO;
module.exports = { module.exports = {
levels, levels,
setLevel: (l) => (level = l), setLevel: l => (level = l),
log: { log: {
trace: (msg) => { trace: (msg) => {
if (level <= levels.TRACE) return; if (level <= levels.TRACE) return;
@ -122,4 +123,3 @@ module.exports = {
} }
} }
}; };

View file

@ -1,4 +1,8 @@
function genAzureEndpoint({ azureOpenAIApiInstanceName, azureOpenAIApiDeploymentName, azureOpenAIApiVersion }) { function genAzureEndpoint({
azureOpenAIApiInstanceName,
azureOpenAIApiDeploymentName,
azureOpenAIApiVersion
}) {
return `https://${azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${azureOpenAIApiDeploymentName}/chat/completions?api-version=${azureOpenAIApiVersion}`; return `https://${azureOpenAIApiInstanceName}.openai.azure.com/openai/deployments/${azureOpenAIApiDeploymentName}/chat/completions?api-version=${azureOpenAIApiVersion}`;
} }

View file

@ -3,28 +3,27 @@ const Preset = require('../models/schema/presetSchema');
const migrateConversations = async (userId) => { const migrateConversations = async (userId) => {
try { try {
return await Conversation.updateMany({ user: null }, { $set: { user: userId }}).exec(); return await Conversation.updateMany({ user: null }, { $set: { user: userId } }).exec();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return { message: 'Error saving conversation' }; return { message: 'Error saving conversation' };
} }
} };
const migratePresets = async (userId) => { const migratePresets = async (userId) => {
try { try {
return await Preset.updateMany({ user: null }, { $set: { user: userId }}).exec(); return await Preset.updateMany({ user: null }, { $set: { user: userId } }).exec();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return { message: 'Error saving conversation' }; return { message: 'Error saving conversation' };
} }
} };
const migrateDataToFirstUser = async (user) => { const migrateDataToFirstUser = async (user) => {
const conversations = await migrateConversations(user.id); const conversations = await migrateConversations(user.id);
console.log(conversations); console.log(conversations);
const presets = await migratePresets(user.id); const presets = await migratePresets(user.id);
console.log(presets); console.log(presets);
} };
module.exports = migrateDataToFirstUser; module.exports = migrateDataToFirstUser;

View file

@ -1,7 +1,7 @@
const nodemailer = require("nodemailer"); const nodemailer = require('nodemailer');
const handlebars = require("handlebars"); const handlebars = require('handlebars');
const fs = require("fs"); const fs = require('fs');
const path = require("path"); const path = require('path');
const sendEmail = async (email, subject, payload, template) => { const sendEmail = async (email, subject, payload, template) => {
try { try {
@ -11,18 +11,18 @@ const sendEmail = async (email, subject, payload, template) => {
port: 465, port: 465,
auth: { auth: {
user: process.env.EMAIL_USERNAME, user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD, pass: process.env.EMAIL_PASSWORD
}, }
}); });
const source = fs.readFileSync(path.join(__dirname, template), "utf8"); const source = fs.readFileSync(path.join(__dirname, template), 'utf8');
const compiledTemplate = handlebars.compile(source); const compiledTemplate = handlebars.compile(source);
const options = () => { const options = () => {
return { return {
from: process.env.FROM_EMAIL, from: process.env.FROM_EMAIL,
to: email, to: email,
subject: subject, subject: subject,
html: compiledTemplate(payload), html: compiledTemplate(payload)
}; };
}; };
@ -32,7 +32,7 @@ const sendEmail = async (email, subject, payload, template) => {
return error; return error;
} else { } else {
return res.status(200).json({ return res.status(200).json({
success: true, success: true
}); });
} }
}); });

View file

@ -1,31 +0,0 @@
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node": true,
"commonjs": true,
"es6": true,
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"overrides": [
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
'react/prop-types': ['off'],
'react/display-name': ['off'],
"no-debugger":"off",
}
}

View file

@ -1,22 +0,0 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"singleAttributePerLine": true,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 110,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"parser": "babel"
}

View file

@ -98,18 +98,10 @@
"babel-plugin-root-import": "^6.6.0", "babel-plugin-root-import": "^6.6.0",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"eslint": "^8.33.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"path": "^0.12.7", "path": "^0.12.7",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"postcss-loader": "^7.1.0", "postcss-loader": "^7.1.0",
"postcss-preset-env": "^8.2.0", "postcss-preset-env": "^8.2.0",
"prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"source-map-loader": "^1.1.3", "source-map-loader": "^1.1.3",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"tailwindcss": "^3.2.6", "tailwindcss": "^3.2.6",

View file

@ -44,12 +44,7 @@ const router = createBrowserRouter([
children: [ children: [
{ {
index: true, index: true,
element: ( element: <Navigate to="/chat/new" replace={true} />
<Navigate
to="/chat/new"
replace={true}
/>
)
}, },
{ {
path: 'chat/:conversationId?', path: 'chat/:conversationId?',
@ -70,7 +65,7 @@ const App = () => {
const queryClient = new QueryClient({ const queryClient = new QueryClient({
queryCache: new QueryCache({ queryCache: new QueryCache({
onError: error => { onError: (error) => {
if (error?.response?.status === 401) { if (error?.response?.status === 401) {
setError(error); setError(error);
} }

View file

@ -1,50 +1,48 @@
import { useEffect } from "react"; import { useEffect } from 'react';
import { useForm } from "react-hook-form"; import { useForm } from 'react-hook-form';
import { TLoginUser } from "~/data-provider"; import { TLoginUser } from '~/data-provider';
import { useAuthContext } from "~/hooks/AuthContext"; import { useAuthContext } from '~/hooks/AuthContext';
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
function Login() { function Login() {
const { login, error, isAuthenticated } = useAuthContext(); const { login, error, isAuthenticated } = useAuthContext();
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors }
} = useForm<TLoginUser>(); } = useForm<TLoginUser>();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (isAuthenticated) { if (isAuthenticated) {
navigate("/chat/new"); navigate('/chat/new');
} }
}, [isAuthenticated, navigate]) }, [isAuthenticated, navigate]);
const SERVER_URL = import.meta.env.DEV const SERVER_URL = import.meta.env.DEV
? import.meta.env.VITE_SERVER_URL_DEV ? import.meta.env.VITE_SERVER_URL_DEV
: import.meta.env.VITE_SERVER_URL_PROD; : import.meta.env.VITE_SERVER_URL_PROD;
const showGoogleLogin = const showGoogleLogin = import.meta.env.VITE_SHOW_GOOGLE_LOGIN_OPTION === 'true';
import.meta.env.VITE_SHOW_GOOGLE_LOGIN_OPTION === "true";
return ( return (
<div className="flex min-h-screen flex-col items-center pt-6 justify-center sm:pt-0 bg-white"> <div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg w-96"> <div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="text-center text-3xl font-semibold mb-4">Welcome back</h1> <h1 className="mb-4 text-center text-3xl font-semibold">Welcome back</h1>
{error && ( {error && (
<div <div
className="mt-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
role="alert" role="alert"
> >
Unable to login with the information provided. Please check your Unable to login with the information provided. Please check your credentials and try
credentials and try again. again.
</div> </div>
)} )}
<form <form
className="mt-6" className="mt-6"
aria-label="Login form" aria-label="Login form"
method="POST" method="POST"
onSubmit={handleSubmit((data) => login(data))} onSubmit={handleSubmit(data => login(data))}
> >
<div className="mb-2"> <div className="mb-2">
<div className="relative"> <div className="relative">
@ -53,28 +51,28 @@ function Login() {
id="email" id="email"
autoComplete="email" autoComplete="email"
aria-label="Email" aria-label="Email"
{...register("email", { {...register('email', {
required: "Email is required", required: 'Email is required',
minLength: { minLength: {
value: 3, value: 3,
message: "Email must be at least 6 characters", message: 'Email must be at least 6 characters'
}, },
maxLength: { maxLength: {
value: 120, value: 120,
message: "Email should not be longer than 120 characters", message: 'Email should not be longer than 120 characters'
}, },
pattern: { pattern: {
value: /\S+@\S+\.\S+/, value: /\S+@\S+\.\S+/,
message: "You must enter a valid email address", message: 'You must enter a valid email address'
}, }
})} })}
aria-invalid={!!errors.email} aria-invalid={!!errors.email}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="email" htmlFor="email"
className="absolute text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Email address Email address
</label> </label>
@ -93,24 +91,24 @@ function Login() {
id="password" id="password"
autoComplete="current-password" autoComplete="current-password"
aria-label="Password" aria-label="Password"
{...register("password", { {...register('password', {
required: "Password is required", required: 'Password is required',
minLength: { minLength: {
value: 8, value: 8,
message: "Password must be at least 8 characters", message: 'Password must be at least 8 characters'
}, },
maxLength: { maxLength: {
value: 40, value: 40,
message: "Password must be less than 40 characters", message: 'Password must be less than 40 characters'
}, }
})} })}
aria-invalid={!!errors.password} aria-invalid={!!errors.password}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="password" htmlFor="password"
className="absolute text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Password Password
</label> </label>
@ -123,10 +121,7 @@ function Login() {
</span> </span>
)} )}
</div> </div>
<a <a href="/forgot-password" className="text-sm text-green-500 hover:underline">
href="/forgot-password"
className="text-sm text-green-500 hover:underline"
>
Forgot Password? Forgot Password?
</a> </a>
<div className="mt-6"> <div className="mt-6">
@ -140,27 +135,46 @@ function Login() {
</div> </div>
</form> </form>
<p className="my-4 text-center text-sm font-light text-gray-700"> <p className="my-4 text-center text-sm font-light text-gray-700">
{" "} {' '}
Don't have an account?{" "} Don't have an account?{' '}
<a <a href="/register" className="p-1 text-green-500 hover:underline">
href="/register"
className="p-1 text-green-500 hover:underline"
>
Sign up Sign up
</a> </a>
</p> </p>
{showGoogleLogin && ( {showGoogleLogin && (
<> <>
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase"> <div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
<div className="absolute text-xs bg-white px-3">Or</div> <div className="absolute bg-white px-3 text-xs">Or</div>
</div> </div>
<div className="mt-4 flex gap-x-2"> <div className="mt-4 flex gap-x-2">
<a <a
aria-label="Login with Google" aria-label="Login with Google"
className="flex w-full items-center justify-left space-x-3 rounded-md border border-gray-300 py-3 px-5 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1 hover:bg-gray-50" className="justify-left flex w-full items-center space-x-3 rounded-md border border-gray-300 px-5 py-3 hover:bg-gray-50 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1"
href={`${SERVER_URL}/oauth/google`} href={`${SERVER_URL}/oauth/google`}
> >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="google" className="w-5 h-5"><path fill="#fbbb00" d="M113.47 309.408 95.648 375.94l-65.139 1.378C11.042 341.211 0 299.9 0 256c0-42.451 10.324-82.483 28.624-117.732h.014L86.63 148.9l25.404 57.644c-5.317 15.501-8.215 32.141-8.215 49.456.002 18.792 3.406 36.797 9.651 53.408z"></path><path fill="#518ef8" d="M507.527 208.176C510.467 223.662 512 239.655 512 256c0 18.328-1.927 36.206-5.598 53.451-12.462 58.683-45.025 109.925-90.134 146.187l-.014-.014-73.044-3.727-10.338-64.535c29.932-17.554 53.324-45.025 65.646-77.911h-136.89V208.176h245.899z"></path><path fill="#28b446" d="m416.253 455.624.014.014C372.396 490.901 316.666 512 256 512c-97.491 0-182.252-54.491-225.491-134.681l82.961-67.91c21.619 57.698 77.278 98.771 142.53 98.771 28.047 0 54.323-7.582 76.87-20.818l83.383 68.262z"></path><path fill="#f14336" d="m419.404 58.936-82.933 67.896C313.136 112.246 285.552 103.82 256 103.82c-66.729 0-123.429 42.957-143.965 102.724l-83.397-68.276h-.014C71.23 56.123 157.06 0 256 0c62.115 0 119.068 22.126 163.404 58.936z"></path></svg> <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
id="google"
className="h-5 w-5"
>
<path
fill="#fbbb00"
d="M113.47 309.408 95.648 375.94l-65.139 1.378C11.042 341.211 0 299.9 0 256c0-42.451 10.324-82.483 28.624-117.732h.014L86.63 148.9l25.404 57.644c-5.317 15.501-8.215 32.141-8.215 49.456.002 18.792 3.406 36.797 9.651 53.408z"
></path>
<path
fill="#518ef8"
d="M507.527 208.176C510.467 223.662 512 239.655 512 256c0 18.328-1.927 36.206-5.598 53.451-12.462 58.683-45.025 109.925-90.134 146.187l-.014-.014-73.044-3.727-10.338-64.535c29.932-17.554 53.324-45.025 65.646-77.911h-136.89V208.176h245.899z"
></path>
<path
fill="#28b446"
d="m416.253 455.624.014.014C372.396 490.901 316.666 512 256 512c-97.491 0-182.252-54.491-225.491-134.681l82.961-67.91c21.619 57.698 77.278 98.771 142.53 98.771 28.047 0 54.323-7.582 76.87-20.818l83.383 68.262z"
></path>
<path
fill="#f14336"
d="m419.404 58.936-82.933 67.896C313.136 112.246 285.552 103.82 256 103.82c-66.729 0-123.429 42.957-143.965 102.724l-83.397-68.276h-.014C71.23 56.123 157.06 0 256 0c62.115 0 119.068 22.126 163.404 58.936z"
></path>
</svg>
<p>Login with Google</p> <p>Login with Google</p>
</a> </a>

View file

@ -1,65 +1,61 @@
import { useState } from "react"; import { useState } from 'react';
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import { useForm } from "react-hook-form"; import { useForm } from 'react-hook-form';
import { useRegisterUserMutation, TRegisterUser } from "~/data-provider"; import { useRegisterUserMutation, TRegisterUser } from '~/data-provider';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFacebook } from "@fortawesome/free-brands-svg-icons"; import { faFacebook } from '@fortawesome/free-brands-svg-icons';
import { faGoogle } from "@fortawesome/free-brands-svg-icons"; import { faGoogle } from '@fortawesome/free-brands-svg-icons';
function Registration() { function Registration() {
const SERVER_URL = import.meta.env.DEV const SERVER_URL = import.meta.env.DEV
? import.meta.env.VITE_SERVER_URL_DEV ? import.meta.env.VITE_SERVER_URL_DEV
: import.meta.env.VITE_SERVER_URL_PROD; : import.meta.env.VITE_SERVER_URL_PROD;
const showGoogleLogin = const showGoogleLogin = import.meta.env.VITE_SHOW_GOOGLE_LOGIN_OPTION === 'true';
import.meta.env.VITE_SHOW_GOOGLE_LOGIN_OPTION === "true";
const navigate = useNavigate(); const navigate = useNavigate();
const { const {
register, register,
watch, watch,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors }
} = useForm<TRegisterUser>({ mode: "onChange" }); } = useForm<TRegisterUser>({ mode: 'onChange' });
const [error, setError] = useState<boolean>(false); const [error, setError] = useState<boolean>(false);
const [errorMessage, setErrorMessage] = useState<string>(""); const [errorMessage, setErrorMessage] = useState<string>('');
const registerUser = useRegisterUserMutation(); const registerUser = useRegisterUserMutation();
const password = watch("password"); const password = watch('password');
const onRegisterUserFormSubmit = (data: TRegisterUser) => { const onRegisterUserFormSubmit = (data: TRegisterUser) => {
registerUser.mutate(data, { registerUser.mutate(data, {
onSuccess: () => { onSuccess: () => {
navigate("/chat/new"); navigate('/chat/new');
}, },
onError: (error) => { onError: (error) => {
setError(true); setError(true);
if (error.response?.data?.message) { if (error.response?.data?.message) {
setErrorMessage(error.response?.data?.message); setErrorMessage(error.response?.data?.message);
} }
}, }
}); });
}; };
return ( return (
<div className="flex min-h-screen flex-col items-center pt-6 justify-center sm:pt-0 bg-white"> <div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg w-96"> <div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="text-center text-3xl font-semibold mb-4"> <h1 className="mb-4 text-center text-3xl font-semibold">Create your account</h1>
Create your account
</h1>
{error && ( {error && (
<div <div
className="mt-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
role="alert" role="alert"
> >
There was an error attempting to register your account. Please try There was an error attempting to register your account. Please try again. {errorMessage}
again. {errorMessage}
</div> </div>
)} )}
<form <form
className="mt-6" className="mt-6"
aria-label="Registration form" aria-label="Registration form"
method="POST" method="POST"
onSubmit={handleSubmit((data) => onRegisterUserFormSubmit(data))} onSubmit={handleSubmit(data => onRegisterUserFormSubmit(data))}
> >
<div className="mb-2"> <div className="mb-2">
<div className="relative"> <div className="relative">
@ -73,24 +69,24 @@ function Registration() {
e.preventDefault(); e.preventDefault();
return false; return false;
}} }}
{...register("name", { {...register('name', {
required: "Name is required", required: 'Name is required',
minLength: { minLength: {
value: 3, value: 3,
message: "Name must be at least 3 characters", message: 'Name must be at least 3 characters'
}, },
maxLength: { maxLength: {
value: 80, value: 80,
message: "Name must be less than 80 characters", message: 'Name must be less than 80 characters'
}, }
})} })}
aria-invalid={!!errors.name} aria-invalid={!!errors.name}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="name" htmlFor="name"
className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Full Name Full Name
</label> </label>
@ -109,25 +105,25 @@ function Registration() {
type="text" type="text"
id="username" id="username"
aria-label="Username" aria-label="Username"
{...register("username", { {...register('username', {
required: "Username is required", required: 'Username is required',
minLength: { minLength: {
value: 3, value: 3,
message: "Username must be at least 3 characters", message: 'Username must be at least 3 characters'
}, },
maxLength: { maxLength: {
value: 20, value: 20,
message: "Username must be less than 20 characters", message: 'Username must be less than 20 characters'
}, }
})} })}
aria-invalid={!!errors.username} aria-invalid={!!errors.username}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
autoComplete="off" autoComplete="off"
></input> ></input>
<label <label
htmlFor="username" htmlFor="username"
className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Username Username
</label> </label>
@ -147,28 +143,28 @@ function Registration() {
id="email" id="email"
autoComplete="email" autoComplete="email"
aria-label="Email" aria-label="Email"
{...register("email", { {...register('email', {
required: "Email is required", required: 'Email is required',
minLength: { minLength: {
value: 3, value: 3,
message: "Email must be at least 6 characters", message: 'Email must be at least 6 characters'
}, },
maxLength: { maxLength: {
value: 120, value: 120,
message: "Email should not be longer than 120 characters", message: 'Email should not be longer than 120 characters'
}, },
pattern: { pattern: {
value: /\S+@\S+\.\S+/, value: /\S+@\S+\.\S+/,
message: "You must enter a valid email address", message: 'You must enter a valid email address'
}, }
})} })}
aria-invalid={!!errors.email} aria-invalid={!!errors.email}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="email" htmlFor="email"
className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Email Email
</label> </label>
@ -187,24 +183,24 @@ function Registration() {
id="password" id="password"
autoComplete="current-password" autoComplete="current-password"
aria-label="Password" aria-label="Password"
{...register("password", { {...register('password', {
required: "Password is required", required: 'Password is required',
minLength: { minLength: {
value: 8, value: 8,
message: "Password must be at least 8 characters", message: 'Password must be at least 8 characters'
}, },
maxLength: { maxLength: {
value: 40, value: 40,
message: "Password must be less than 40 characters", message: 'Password must be less than 40 characters'
}, }
})} })}
aria-invalid={!!errors.password} aria-invalid={!!errors.password}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="password" htmlFor="password"
className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Password Password
</label> </label>
@ -228,17 +224,16 @@ function Registration() {
e.preventDefault(); e.preventDefault();
return false; return false;
}} }}
{...register("confirm_password", { {...register('confirm_password', {
validate: (value) => validate: value => value === password || 'Passwords do not match'
value === password || "Passwords do not match",
})} })}
aria-invalid={!!errors.confirm_password} aria-invalid={!!errors.confirm_password}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="confirm_password" htmlFor="confirm_password"
className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Confirm Password Confirm Password
</label> </label>
@ -269,28 +264,47 @@ function Registration() {
</div> </div>
</form> </form>
<p className="my-4 text-center text-sm font-light text-gray-700"> <p className="my-4 text-center text-sm font-light text-gray-700">
{" "} {' '}
Already have an account?{" "} Already have an account?{' '}
<a <a href="/login" className="p-1 font-medium text-green-500 hover:underline">
href="/login"
className="font-medium text-green-500 p-1 hover:underline"
>
Login Login
</a> </a>
</p> </p>
{showGoogleLogin && ( {showGoogleLogin && (
<> <>
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase"> <div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
<div className="absolute text-xs bg-white px-3">Or</div> <div className="absolute bg-white px-3 text-xs">Or</div>
</div> </div>
<div className="mt-4 flex gap-x-2"> <div className="mt-4 flex gap-x-2">
<a <a
aria-label="Login with Google" aria-label="Login with Google"
href={`${SERVER_URL}/oauth/google`} href={`${SERVER_URL}/oauth/google`}
className="flex w-full items-center justify-left space-x-3 rounded-md border border-gray-300 py-3 px-5 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1 hover:bg-gray-50" className="justify-left flex w-full items-center space-x-3 rounded-md border border-gray-300 px-5 py-3 hover:bg-gray-50 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1"
> >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" id="google" className="w-5 h-5"><path fill="#fbbb00" d="M113.47 309.408 95.648 375.94l-65.139 1.378C11.042 341.211 0 299.9 0 256c0-42.451 10.324-82.483 28.624-117.732h.014L86.63 148.9l25.404 57.644c-5.317 15.501-8.215 32.141-8.215 49.456.002 18.792 3.406 36.797 9.651 53.408z"></path><path fill="#518ef8" d="M507.527 208.176C510.467 223.662 512 239.655 512 256c0 18.328-1.927 36.206-5.598 53.451-12.462 58.683-45.025 109.925-90.134 146.187l-.014-.014-73.044-3.727-10.338-64.535c29.932-17.554 53.324-45.025 65.646-77.911h-136.89V208.176h245.899z"></path><path fill="#28b446" d="m416.253 455.624.014.014C372.396 490.901 316.666 512 256 512c-97.491 0-182.252-54.491-225.491-134.681l82.961-67.91c21.619 57.698 77.278 98.771 142.53 98.771 28.047 0 54.323-7.582 76.87-20.818l83.383 68.262z"></path><path fill="#f14336" d="m419.404 58.936-82.933 67.896C313.136 112.246 285.552 103.82 256 103.82c-66.729 0-123.429 42.957-143.965 102.724l-83.397-68.276h-.014C71.23 56.123 157.06 0 256 0c62.115 0 119.068 22.126 163.404 58.936z"></path></svg> <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
id="google"
className="h-5 w-5"
>
<path
fill="#fbbb00"
d="M113.47 309.408 95.648 375.94l-65.139 1.378C11.042 341.211 0 299.9 0 256c0-42.451 10.324-82.483 28.624-117.732h.014L86.63 148.9l25.404 57.644c-5.317 15.501-8.215 32.141-8.215 49.456.002 18.792 3.406 36.797 9.651 53.408z"
></path>
<path
fill="#518ef8"
d="M507.527 208.176C510.467 223.662 512 239.655 512 256c0 18.328-1.927 36.206-5.598 53.451-12.462 58.683-45.025 109.925-90.134 146.187l-.014-.014-73.044-3.727-10.338-64.535c29.932-17.554 53.324-45.025 65.646-77.911h-136.89V208.176h245.899z"
></path>
<path
fill="#28b446"
d="m416.253 455.624.014.014C372.396 490.901 316.666 512 256 512c-97.491 0-182.252-54.491-225.491-134.681l82.961-67.91c21.619 57.698 77.278 98.771 142.53 98.771 28.047 0 54.323-7.582 76.87-20.818l83.383 68.262z"
></path>
<path
fill="#f14336"
d="m419.404 58.936-82.933 67.896C313.136 112.246 285.552 103.82 256 103.82c-66.729 0-123.429 42.957-143.965 102.724l-83.397-68.276h-.014C71.23 56.123 157.06 0 256 0c62.115 0 119.068 22.126 163.404 58.936z"
></path>
</svg>
<p>Login with Google</p> <p>Login with Google</p>
</a> </a>
{/* <button {/* <button

View file

@ -1,17 +1,17 @@
import { useState } from "react"; import { useState } from 'react';
import { useForm } from "react-hook-form"; import { useForm } from 'react-hook-form';
import { useRequestPasswordResetMutation, TRequestPasswordReset } from "~/data-provider"; import { useRequestPasswordResetMutation, TRequestPasswordReset } from '~/data-provider';
function RequestPasswordReset() { function RequestPasswordReset() {
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors }
} = useForm<TRequestPasswordReset>(); } = useForm<TRequestPasswordReset>();
const requestPasswordReset = useRequestPasswordResetMutation(); const requestPasswordReset = useRequestPasswordResetMutation();
const [success, setSuccess] = useState<boolean>(false); const [success, setSuccess] = useState<boolean>(false);
const [requestError, setRequestError] = useState<boolean>(false); const [requestError, setRequestError] = useState<boolean>(false);
const [resetLink, setResetLink] = useState<string>(""); const [resetLink, setResetLink] = useState<string>('');
const onSubmit = (data: TRequestPasswordReset) => { const onSubmit = (data: TRequestPasswordReset) => {
requestPasswordReset.mutate(data, { requestPasswordReset.mutate(data, {
@ -29,26 +29,29 @@ function RequestPasswordReset() {
}; };
return ( return (
<div className="flex min-h-screen flex-col items-center pt-6 justify-center sm:pt-0 bg-white"> <div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg w-96"> <div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="text-center text-3xl font-semibold mb-4"> <h1 className="mb-4 text-center text-3xl font-semibold">Reset your password</h1>
Reset your password
</h1>
{success && ( {success && (
<div <div
className="mt-4 bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" className="relative mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-green-700"
role="alert" role="alert"
> >
Click <a className="text-green-600 hover:underline" href={resetLink}>HERE</a> to reset your password. Click{' '}
<a className="text-green-600 hover:underline" href={resetLink}>
HERE
</a>{' '}
to reset your password.
{/* An email has been sent with instructions on how to reset your password. */} {/* An email has been sent with instructions on how to reset your password. */}
</div> </div>
)} )}
{requestError && ( {requestError && (
<div <div
className="mt-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
role="alert" role="alert"
> >
There was a problem resetting your password. There was no user found with the email address provided. Please try again. There was a problem resetting your password. There was no user found with the email
address provided. Please try again.
</div> </div>
)} )}
<form <form
@ -64,28 +67,28 @@ function RequestPasswordReset() {
id="email" id="email"
autoComplete="off" autoComplete="off"
aria-label="Email" aria-label="Email"
{...register("email", { {...register('email', {
required: "Email is required", required: 'Email is required',
minLength: { minLength: {
value: 3, value: 3,
message: "Email must be at least 6 characters", message: 'Email must be at least 6 characters'
}, },
maxLength: { maxLength: {
value: 120, value: 120,
message: "Email should not be longer than 120 characters", message: 'Email should not be longer than 120 characters'
}, },
pattern: { pattern: {
value: /\S+@\S+\.\S+/, value: /\S+@\S+\.\S+/,
message: "You must enter a valid email address", message: 'You must enter a valid email address'
}, }
})} })}
aria-invalid={!!errors.email} aria-invalid={!!errors.email}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="email" htmlFor="email"
className="absolute text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Email address Email address
</label> </label>
@ -100,8 +103,8 @@ function RequestPasswordReset() {
<div className="mt-6"> <div className="mt-6">
<button <button
type="submit" type="submit"
disabled={ !!errors.email } disabled={!!errors.email}
className="w-full py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-green-500 hover:bg-green-600 focus:outline-none active:bg-green-500" className="w-full rounded-sm border border-transparent bg-green-500 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-600 focus:outline-none active:bg-green-500"
> >
Continue Continue
</button> </button>

View file

@ -1,20 +1,20 @@
import { useState } from "react"; import { useState } from 'react';
import { useForm } from "react-hook-form"; import { useForm } from 'react-hook-form';
import {useResetPasswordMutation, TResetPassword} from "~/data-provider"; import { useResetPasswordMutation, TResetPassword } from '~/data-provider';
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from 'react-router-dom';
function ResetPassword() { function ResetPassword() {
const { const {
register, register,
handleSubmit, handleSubmit,
watch, watch,
formState: { errors }, formState: { errors }
} = useForm<TResetPassword>(); } = useForm<TResetPassword>();
const resetPassword = useResetPasswordMutation(); const resetPassword = useResetPasswordMutation();
const [resetError, setResetError] = useState<boolean>(false); const [resetError, setResetError] = useState<boolean>(false);
const [params] = useSearchParams(); const [params] = useSearchParams();
const navigate = useNavigate(); const navigate = useNavigate();
const password = watch("password"); const password = watch('password');
const onSubmit = (data: TResetPassword) => { const onSubmit = (data: TResetPassword) => {
resetPassword.mutate(data, { resetPassword.mutate(data, {
@ -26,19 +26,17 @@ function ResetPassword() {
if (resetPassword.isSuccess) { if (resetPassword.isSuccess) {
return ( return (
<div className="flex min-h-screen flex-col items-center pt-6 justify-center sm:pt-0 bg-white"> <div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg w-96"> <div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="text-center text-3xl font-semibold mb-4"> <h1 className="mb-4 text-center text-3xl font-semibold">Password Reset Success</h1>
Password Reset Success
</h1>
<div <div
className="mt-4 bg-green-100 border border-green-400 text-center mb-8 text-green-700 px-4 py-3 rounded relative" className="relative mb-8 mt-4 rounded border border-green-400 bg-green-100 px-4 py-3 text-center text-green-700"
role="alert" role="alert"
> >
You may now login with your new password. You may now login with your new password.
</div> </div>
<button <button
onClick={() => navigate("/login")} onClick={() => navigate('/login')}
aria-label="Sign in" aria-label="Sign in"
className="w-full transform rounded-sm bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-600 focus:bg-green-600 focus:outline-none" className="w-full transform rounded-sm bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-600 focus:bg-green-600 focus:outline-none"
> >
@ -46,21 +44,22 @@ function ResetPassword() {
</button> </button>
</div> </div>
</div> </div>
) );
} } else {
else {
return ( return (
<div className="flex min-h-screen flex-col items-center pt-6 justify-center sm:pt-0 bg-white"> <div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
<div className="mt-6 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg w-96"> <div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
<h1 className="text-center text-3xl font-semibold mb-4"> <h1 className="mb-4 text-center text-3xl font-semibold">Reset your password</h1>
Reset your password
</h1>
{resetError && ( {resetError && (
<div <div
className="mt-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" className="relative mt-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
role="alert" role="alert"
> >
This password reset token is no longer valid. <a className="font-semibold hover:underline text-green-600" href="/forgot-password">Click here</a> to try again. This password reset token is no longer valid.{' '}
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
Click here
</a>{' '}
to try again.
</div> </div>
)} )}
<form <form
@ -71,31 +70,41 @@ function ResetPassword() {
> >
<div className="mb-2"> <div className="mb-2">
<div className="relative"> <div className="relative">
<input type="hidden" id="token" value={params.get("token")} {...register("token", { required: "Unable to process: No valid reset token" })} /> <input
<input type="hidden" id="userId" value={params.get("userId")} {...register("userId", { required: "Unable to process: No valid user id" })} /> type="hidden"
id="token"
value={params.get('token')}
{...register('token', { required: 'Unable to process: No valid reset token' })}
/>
<input
type="hidden"
id="userId"
value={params.get('userId')}
{...register('userId', { required: 'Unable to process: No valid user id' })}
/>
<input <input
type="password" type="password"
id="password" id="password"
autoComplete="current-password" autoComplete="current-password"
aria-label="Password" aria-label="Password"
{...register("password", { {...register('password', {
required: "Password is required", required: 'Password is required',
minLength: { minLength: {
value: 8, value: 8,
message: "Password must be at least 8 characters", message: 'Password must be at least 8 characters'
}, },
maxLength: { maxLength: {
value: 40, value: 40,
message: "Password must be less than 40 characters", message: 'Password must be less than 40 characters'
}, }
})} })}
aria-invalid={!!errors.password} aria-invalid={!!errors.password}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="password" htmlFor="password"
className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Password Password
</label> </label>
@ -119,17 +128,16 @@ function ResetPassword() {
e.preventDefault(); e.preventDefault();
return false; return false;
}} }}
{...register("confirm_password", { {...register('confirm_password', {
validate: (value) => validate: value => value === password || 'Passwords do not match'
value === password || "Passwords do not match",
})} })}
aria-invalid={!!errors.confirm_password} aria-invalid={!!errors.confirm_password}
className="block rounded-t-md px-2.5 pb-2.5 pt-5 w-full text-sm text-gray-900 bg-gray-50 border-0 border-b-2 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-green-500 peer" className="peer block w-full appearance-none rounded-t-md border-0 border-b-2 border-gray-300 bg-gray-50 px-2.5 pb-2.5 pt-5 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0"
placeholder=" " placeholder=" "
></input> ></input>
<label <label
htmlFor="confirm_password" htmlFor="confirm_password"
className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-4 z-10 origin-[0] left-2.5 peer-focus:text-green-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-4" className="absolute left-2.5 top-4 z-10 origin-[0] -translate-y-4 scale-75 transform text-sm text-gray-500 duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:text-green-500"
> >
Confirm Password Confirm Password
</label> </label>
@ -155,10 +163,7 @@ function ResetPassword() {
</div> </div>
<div className="mt-6"> <div className="mt-6">
<button <button
disabled={ disabled={!!errors.password || !!errors.confirm_password}
!!errors.password ||
!!errors.confirm_password
}
type="submit" type="submit"
aria-label="Submit registration" aria-label="Submit registration"
className="w-full transform rounded-sm bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-600 focus:bg-green-600 focus:outline-none" className="w-full transform rounded-sm bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-600 focus:bg-green-600 focus:outline-none"
@ -169,8 +174,8 @@ function ResetPassword() {
</form> </form>
</div> </div>
</div> </div>
) );
} }
}; }
export default ResetPassword; export default ResetPassword;

View file

@ -1,4 +1,4 @@
import { useState, useRef, useEffect} from 'react'; import { useState, useRef, useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilState, useSetRecoilState } from 'recoil';
import { useUpdateConversationMutation } from '~/data-provider'; import { useUpdateConversationMutation } from '~/data-provider';
import RenameButton from './RenameButton'; import RenameButton from './RenameButton';
@ -38,7 +38,7 @@ export default function Conversation({ conversation, retainView }) {
switchToConversation(conversation); switchToConversation(conversation);
}; };
const renameHandler = e => { const renameHandler = (e) => {
e.preventDefault(); e.preventDefault();
setTitleInput(title); setTitleInput(title);
setRenaming(true); setRenaming(true);
@ -47,12 +47,12 @@ export default function Conversation({ conversation, retainView }) {
}, 25); }, 25);
}; };
const cancelHandler = e => { const cancelHandler = (e) => {
e.preventDefault(); e.preventDefault();
setRenaming(false); setRenaming(false);
}; };
const onRename = e => { const onRename = (e) => {
e.preventDefault(); e.preventDefault();
setRenaming(false); setRenaming(false);
if (titleInput === title) { if (titleInput === title) {
@ -73,7 +73,7 @@ export default function Conversation({ conversation, retainView }) {
} }
}, [updateConvoMutation.isSuccess]); }, [updateConvoMutation.isSuccess]);
const handleKeyDown = e => { const handleKeyDown = (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
onRename(e); onRename(e);
} }
@ -90,10 +90,7 @@ export default function Conversation({ conversation, retainView }) {
} }
return ( return (
<a <a onClick={() => clickHandler()} {...aProps}>
onClick={() => clickHandler()}
{...aProps}
>
<ConvoIcon /> <ConvoIcon />
<div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis break-all"> <div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis break-all">
{renaming === true ? ( {renaming === true ? (
@ -126,7 +123,7 @@ export default function Conversation({ conversation, retainView }) {
/> />
</div> </div>
) : ( ) : (
<div className="absolute inset-y-0 right-0 z-10 w-8 bg-gradient-to-l from-gray-900 group-hover:from-[#2A2B32] rounded-r-md" /> <div className="absolute inset-y-0 right-0 z-10 w-8 rounded-r-md bg-gradient-to-l from-gray-900 group-hover:from-[#2A2B32]" />
)} )}
</a> </a>
); );

View file

@ -14,7 +14,7 @@ export default function DeleteButton({ conversationId, renaming, cancelHandler,
const deleteConvoMutation = useDeleteConversationMutation(conversationId); const deleteConvoMutation = useDeleteConversationMutation(conversationId);
useEffect(() => { useEffect(() => {
if(deleteConvoMutation.isSuccess) { if (deleteConvoMutation.isSuccess) {
if (currentConversation?.conversationId == conversationId) newConversation(); if (currentConversation?.conversationId == conversationId) newConversation();
refreshConversations(); refreshConversations();
@ -22,18 +22,14 @@ export default function DeleteButton({ conversationId, renaming, cancelHandler,
} }
}, [deleteConvoMutation.isSuccess]); }, [deleteConvoMutation.isSuccess]);
const clickHandler = () => { const clickHandler = () => {
deleteConvoMutation.mutate({conversationId, source: 'button' }); deleteConvoMutation.mutate({ conversationId, source: 'button' });
}; };
const handler = renaming ? cancelHandler : clickHandler; const handler = renaming ? cancelHandler : clickHandler;
return ( return (
<button <button className="p-1 hover:text-white" onClick={handler}>
className="p-1 hover:text-white"
onClick={handler}
>
{renaming ? <CrossIcon /> : <TrashIcon />} {renaming ? <CrossIcon /> : <TrashIcon />}
</button> </button>
); );

View file

@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
export default function Pages({ pageNumber, pages, nextPage, previousPage }) { export default function Pages({ pageNumber, pages, nextPage, previousPage }) {
const clickHandler = func => async e => { const clickHandler = func => async (e) => {
e.preventDefault(); e.preventDefault();
await func(); await func();
}; };
return pageNumber == 1 && pages == 1 ? null : ( return pageNumber == 1 && pages == 1 ? null : (
<div className="m-auto mt-4 mb-2 flex items-center justify-center gap-2"> <div className="m-auto mb-2 mt-4 flex items-center justify-center gap-2">
<button <button
onClick={clickHandler(previousPage)} onClick={clickHandler(previousPage)}
className={ className={

View file

@ -4,7 +4,7 @@ import CheckMark from '../svg/CheckMark';
export default function RenameButton({ renaming, renameHandler, onRename, twcss }) { export default function RenameButton({ renaming, renameHandler, onRename, twcss }) {
const handler = renaming ? onRename : renameHandler; const handler = renaming ? onRename : renameHandler;
const classProp = { className: "p-1 hover:text-white" }; const classProp = { className: 'p-1 hover:text-white' };
if (twcss) { if (twcss) {
classProp.className = twcss; classProp.className = twcss;
} }

View file

@ -6,13 +6,9 @@ export default function Conversations({ conversations, conversationId, moveToTop
<> <>
{conversations && {conversations &&
conversations.length > 0 && conversations.length > 0 &&
conversations.map(convo => { conversations.map((convo) => {
return ( return (
<Conversation <Conversation key={convo.conversationId} conversation={convo} retainView={moveToTop} />
key={convo.conversationId}
conversation={convo}
retainView={moveToTop}
/>
); );
})} })}
</> </>

View file

@ -27,27 +27,26 @@ function Settings(props) {
return; return;
} }
const handleTextChange = context => { const handleTextChange = (context) => {
updateTokenCountMutation.mutate({ text: context }, { updateTokenCountMutation.mutate(
onSuccess: data => { { text: context },
{
onSuccess: (data) => {
setTokenCount(data.count); setTokenCount(data.count);
} }
}); }
);
}; };
handleTextChange(debouncedContext); handleTextChange(debouncedContext);
}, [debouncedContext]); }, [debouncedContext]);
return ( return (
<div className="max-h-[350px] overflow-y-auto"> <div className="max-h-[350px] overflow-y-auto">
<div className="grid gap-6 sm:grid-cols-2"> <div className="grid gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-center justify-start gap-6"> <div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="toneStyle-dropdown" className="text-left text-sm font-medium">
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: fast)</small>
</Label> </Label>
<SelectDropDown <SelectDropDown
@ -65,10 +64,7 @@ function Settings(props) {
/> />
</div> </div>
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="context" className="text-left text-sm font-medium">
htmlFor="context"
className="text-left text-sm font-medium"
>
Context <small className="opacity-40">(default: blank)</small> Context <small className="opacity-40">(default: blank)</small>
</Label> </Label>
<TextareaAutosize <TextareaAutosize
@ -87,10 +83,7 @@ function Settings(props) {
</div> </div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6"> <div className="col-span-1 flex flex-col items-center justify-start gap-6">
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="jailbreak" className="text-left text-sm font-medium">
htmlFor="jailbreak"
className="text-left text-sm font-medium"
>
Enable Sydney <small className="opacity-40">(default: false)</small> Enable Sydney <small className="opacity-40">(default: false)</small>
</Label> </Label>
<div className="flex h-[40px] w-full items-center space-x-3"> <div className="flex h-[40px] w-full items-center space-x-3">

View file

@ -29,7 +29,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
const triggerExamples = () => setShowExamples(prev => !prev); const triggerExamples = () => setShowExamples(prev => !prev);
const setOption = param => newValue => { const setOption = param => (newValue) => {
let update = {}; let update = {};
update[param] = newValue; update[param] = newValue;
setPreset(prevState => setPreset(prevState =>
@ -115,7 +115,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
url: '/api/presets', url: '/api/presets',
data: cleanupPreset({ preset, endpointsConfig }), data: cleanupPreset({ preset, endpointsConfig }),
withCredentials: true withCredentials: true
}).then(res => { }).then((res) => {
setPresets(res?.data); setPresets(res?.data);
}); });
}; };
@ -134,10 +134,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
}, [open]); }, [open]);
return ( return (
<Dialog <Dialog open={open} onOpenChange={onOpenChange}>
open={open}
onOpenChange={onOpenChange}
>
<DialogTemplate <DialogTemplate
title={`${title || 'Edit Preset'} - ${preset?.title}`} title={`${title || 'Edit Preset'} - ${preset?.title}`}
className="max-w-full sm:max-w-4xl" className="max-w-full sm:max-w-4xl"
@ -145,10 +142,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
<div className="flex w-full flex-col items-center gap-2"> <div className="flex w-full flex-col items-center gap-2">
<div className="grid w-full gap-6 sm:grid-cols-2"> <div className="grid w-full gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-start justify-start gap-2"> <div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label <Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
htmlFor="chatGptLabel"
className="text-left text-sm font-medium"
>
Preset Name Preset Name
</Label> </Label>
<Input <Input
@ -163,10 +157,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
/> />
</div> </div>
<div className="col-span-1 flex flex-col items-start justify-start gap-2"> <div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label <Label htmlFor="endpoint" className="text-left text-sm font-medium">
htmlFor="endpoint"
className="text-left text-sm font-medium"
>
Endpoint Endpoint
</Label> </Label>
<Dropdown <Dropdown
@ -194,11 +185,9 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
</div> </div>
<div className="my-4 w-full border-t border-gray-300 dark:border-gray-500" /> <div className="my-4 w-full border-t border-gray-300 dark:border-gray-500" />
<div className="w-full p-0"> <div className="w-full p-0">
{((preset?.endpoint === 'google' && !showExamples) || preset?.endpoint !== 'google') && ( {((preset?.endpoint === 'google' && !showExamples) ||
<Settings preset?.endpoint !== 'google') && (
preset={_preset} <Settings preset={_preset} setOption={setOption} />
setOption={setOption}
/>
)} )}
{preset?.endpoint === 'google' && showExamples && ( {preset?.endpoint === 'google' && showExamples && (
<Examples <Examples
@ -224,10 +213,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
} }
leftButtons={ leftButtons={
<> <>
<DialogButton <DialogButton onClick={exportPreset} className="dark:hover:gray-400 border-gray-700">
onClick={exportPreset}
className="dark:hover:gray-400 border-gray-700"
>
Export Export
</DialogButton> </DialogButton>
</> </>

View file

@ -22,7 +22,7 @@ const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) =
setEndpointName('PaLM'); setEndpointName('PaLM');
} }
const setOption = param => newValue => { const setOption = param => (newValue) => {
let update = {}; let update = {};
update[param] = newValue; update[param] = newValue;
setPreset(prevState => ({ setPreset(prevState => ({
@ -49,21 +49,14 @@ const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) =
return ( return (
<> <>
<Dialog <Dialog open={open} onOpenChange={onOpenChange}>
open={open}
onOpenChange={onOpenChange}
>
<DialogTemplate <DialogTemplate
title={`${title || 'View Options'} - ${endpointName}`} title={`${title || 'View Options'} - ${endpointName}`}
className="max-w-full sm:max-w-4xl" className="max-w-full sm:max-w-4xl"
main={ main={
<div className="flex w-full flex-col items-center gap-2"> <div className="flex w-full flex-col items-center gap-2">
<div className="w-full p-0"> <div className="w-full p-0">
<Settings <Settings preset={preset} readonly={true} setOption={setOption} />
preset={preset}
readonly={true}
setOption={setOption}
/>
</div> </div>
</div> </div>
} }
@ -79,10 +72,7 @@ const EndpointOptionsDialog = ({ open, onOpenChange, preset: _preset, title }) =
} }
leftButtons={ leftButtons={
<> <>
<DialogButton <DialogButton onClick={exportPreset} className="dark:hover:gray-400 border-gray-700">
onClick={exportPreset}
className="dark:hover:gray-400 border-gray-700"
>
Export Export
</DialogButton> </DialogButton>
</> </>

View file

@ -12,10 +12,7 @@ function Examples({ readonly, examples, setExample, addExample, removeExample, e
return ( return (
<> <>
<div className={`${maxHeight} overflow-y-auto`}> <div className={`${maxHeight} overflow-y-auto`}>
<div <div id="examples-grid" className="grid gap-6 sm:grid-cols-2">
id="examples-grid"
className="grid gap-6 sm:grid-cols-2"
>
{examples.map((example, idx) => ( {examples.map((example, idx) => (
<React.Fragment key={idx}> <React.Fragment key={idx}>
{/* Input */} {/* Input */}
@ -25,10 +22,7 @@ function Examples({ readonly, examples, setExample, addExample, removeExample, e
} flex flex-col items-center justify-start gap-6 sm:col-span-1`} } flex flex-col items-center justify-start gap-6 sm:col-span-1`}
> >
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor={`input-${idx}`} className="text-left text-sm font-medium">
htmlFor={`input-${idx}`}
className="text-left text-sm font-medium"
>
Input <small className="opacity-40">(default: blank)</small> Input <small className="opacity-40">(default: blank)</small>
</Label> </Label>
<TextareaAutosize <TextareaAutosize
@ -52,10 +46,7 @@ function Examples({ readonly, examples, setExample, addExample, removeExample, e
} flex flex-col items-center justify-start gap-6 sm:col-span-1`} } flex flex-col items-center justify-start gap-6 sm:col-span-1`}
> >
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor={`output-${idx}`} className="text-left text-sm font-medium">
htmlFor={`output-${idx}`}
className="text-left text-sm font-medium"
>
Output <small className="opacity-40">(default: blank)</small> Output <small className="opacity-40">(default: blank)</small>
</Label> </Label>
<TextareaAutosize <TextareaAutosize

View file

@ -5,7 +5,8 @@ const types = {
temp: 'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.', temp: 'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
topp: 'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.', topp: 'Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.',
topk: "Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).", topk: "Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).",
maxoutputtokens: " Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses." maxoutputtokens:
' Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses.'
}; };
function OptionHover({ type, side }) { function OptionHover({ type, side }) {

View file

@ -17,7 +17,18 @@ const optionText =
import store from '~/store'; import store from '~/store';
function Settings(props) { function Settings(props) {
const { readonly, model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens, setOption, edit = false } = props; const {
readonly,
model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxOutputTokens,
setOption,
edit = false
} = props;
const maxHeight = edit ? 'max-h-[233px]' : 'max-h-[350px]'; const maxHeight = edit ? 'max-h-[233px]' : 'max-h-[350px]';
const endpointsConfig = useRecoilValue(store.endpointsConfig); const endpointsConfig = useRecoilValue(store.endpointsConfig);
@ -49,10 +60,7 @@ function Settings(props) {
/> />
</div> </div>
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="modelLabel" className="text-left text-sm font-medium">
htmlFor="modelLabel"
className="text-left text-sm font-medium"
>
Custom Name <small className="opacity-40">(default: blank)</small> Custom Name <small className="opacity-40">(default: blank)</small>
</Label> </Label>
<Input <Input
@ -68,10 +76,7 @@ function Settings(props) {
/> />
</div> </div>
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
htmlFor="promptPrefix"
className="text-left text-sm font-medium"
>
Prompt Prefix <small className="opacity-40">(default: blank)</small> Prompt Prefix <small className="opacity-40">(default: blank)</small>
</Label> </Label>
<TextareaAutosize <TextareaAutosize
@ -91,10 +96,7 @@ function Settings(props) {
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="temp-int" className="text-left text-sm font-medium">
htmlFor="temp-int"
className="text-left text-sm font-medium"
>
Temperature <small className="opacity-40">(default: 0.2)</small> Temperature <small className="opacity-40">(default: 0.2)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -126,18 +128,12 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="temp" side="left" />
type="temp"
side="left"
/>
</HoverCard> </HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="top-p-int" className="text-left text-sm font-medium">
htmlFor="top-p-int"
className="text-left text-sm font-medium"
>
Top P <small className="opacity-40">(default: 0.95)</small> Top P <small className="opacity-40">(default: 0.95)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -169,19 +165,13 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="topp" side="left" />
type="topp"
side="left"
/>
</HoverCard> </HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="top-k-int" className="text-left text-sm font-medium">
htmlFor="top-k-int"
className="text-left text-sm font-medium"
>
Top K <small className="opacity-40">(default: 40)</small> Top K <small className="opacity-40">(default: 40)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -213,19 +203,13 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="topk" side="left" />
type="topk"
side="left"
/>
</HoverCard> </HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
htmlFor="max-tokens-int"
className="text-left text-sm font-medium"
>
Max Output Tokens <small className="opacity-40">(default: 1024)</small> Max Output Tokens <small className="opacity-40">(default: 1024)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -257,10 +241,7 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="maxoutputtokens" side="left" />
type="maxoutputtokens"
side="left"
/>
</HoverCard> </HoverCard>
</div> </div>
</div> </div>

View file

@ -17,7 +17,17 @@ const optionText =
import store from '~/store'; import store from '~/store';
function Settings(props) { function Settings(props) {
const { readonly, model, chatGptLabel, promptPrefix, temperature, topP, freqP, presP, setOption } = props; const {
readonly,
model,
chatGptLabel,
promptPrefix,
temperature,
topP,
freqP,
presP,
setOption
} = props;
const endpointsConfig = useRecoilValue(store.endpointsConfig); const endpointsConfig = useRecoilValue(store.endpointsConfig);
@ -49,10 +59,7 @@ function Settings(props) {
/> />
</div> </div>
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
htmlFor="chatGptLabel"
className="text-left text-sm font-medium"
>
Custom Name <small className="opacity-40">(default: blank)</small> Custom Name <small className="opacity-40">(default: blank)</small>
</Label> </Label>
<Input <Input
@ -68,10 +75,7 @@ function Settings(props) {
/> />
</div> </div>
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
htmlFor="promptPrefix"
className="text-left text-sm font-medium"
>
Prompt Prefix <small className="opacity-40">(default: blank)</small> Prompt Prefix <small className="opacity-40">(default: blank)</small>
</Label> </Label>
<TextareaAutosize <TextareaAutosize
@ -91,10 +95,7 @@ function Settings(props) {
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="temp-int" className="text-left text-sm font-medium">
htmlFor="temp-int"
className="text-left text-sm font-medium"
>
Temperature <small className="opacity-40">(default: 1)</small> Temperature <small className="opacity-40">(default: 1)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -126,18 +127,12 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="temp" side="left" />
type="temp"
side="left"
/>
</HoverCard> </HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="top-p-int" className="text-left text-sm font-medium">
htmlFor="top-p-int"
className="text-left text-sm font-medium"
>
Top P <small className="opacity-40">(default: 1)</small> Top P <small className="opacity-40">(default: 1)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -169,19 +164,13 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="topp" side="left" />
type="topp"
side="left"
/>
</HoverCard> </HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
htmlFor="freq-penalty-int"
className="text-left text-sm font-medium"
>
Frequency Penalty <small className="opacity-40">(default: 0)</small> Frequency Penalty <small className="opacity-40">(default: 0)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -213,19 +202,13 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="freq" side="left" />
type="freq"
side="left"
/>
</HoverCard> </HoverCard>
<HoverCard openDelay={300}> <HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2"> <HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between"> <div className="flex justify-between">
<Label <Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
htmlFor="pres-penalty-int"
className="text-left text-sm font-medium"
>
Presence Penalty <small className="opacity-40">(default: 0)</small> Presence Penalty <small className="opacity-40">(default: 0)</small>
</Label> </Label>
<InputNumber <InputNumber
@ -257,10 +240,7 @@ function Settings(props) {
className="flex h-4 w-full" className="flex h-4 w-full"
/> />
</HoverCardTrigger> </HoverCardTrigger>
<OptionHover <OptionHover type="pres" side="left" />
type="pres"
side="left"
/>
</HoverCard> </HoverCard>
</div> </div>
</div> </div>

View file

@ -33,18 +33,12 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }) => {
}, [open]); }, [open]);
return ( return (
<Dialog <Dialog open={open} onOpenChange={onOpenChange}>
open={open}
onOpenChange={onOpenChange}
>
<DialogTemplate <DialogTemplate
title="Save As Preset" title="Save As Preset"
main={ main={
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
htmlFor="chatGptLabel"
className="text-left text-sm font-medium"
>
Preset Name Preset Name
</Label> </Label>
<Input <Input

View file

@ -1,16 +1,16 @@
import React from 'react'; import React from 'react';
import { Settings2 } from 'lucide-react'; import { Settings2 } from 'lucide-react';
export default function AdjustButton({ onClick }) { export default function AdjustButton({ onClick }) {
const clickHandler = e => { const clickHandler = (e) => {
e.preventDefault(); e.preventDefault();
onClick(); onClick();
}; };
return ( return (
<button <button
onClick={clickHandler} onClick={clickHandler}
className="group absolute bottom-11 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500 lg:bottom-0 lg:-right-11" className="group absolute bottom-11 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500 lg:-right-11 lg:bottom-0"
> >
<div className="m-1 mr-0 rounded-md p-2 pt-[10px] pb-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent"> <div className="m-1 mr-0 rounded-md p-2 pb-[10px] pt-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
<Settings2 size="1em" /> <Settings2 size="1em" />
</div> </div>
</button> </button>

View file

@ -31,7 +31,7 @@ function BingAIOptions({ show }) {
setSaveAsDialogShow(true); setSaveAsDialogShow(true);
}; };
const setOption = param => newValue => { const setOption = param => (newValue) => {
let update = {}; let update = {};
update[param] = newValue; update[param] = newValue;
setConversation(prevState => ({ setConversation(prevState => ({
@ -44,7 +44,10 @@ function BingAIOptions({ show }) {
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white'; 'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
const defaultClasses = const defaultClasses =
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs'; 'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
const defaultSelected = cn(defaultClasses, 'font-medium data-[state=active]:text-white text-xs text-white'); const defaultSelected = cn(
defaultClasses,
'font-medium data-[state=active]:text-white text-xs text-white'
);
const selectedClass = val => val + '-tab ' + defaultSelected; const selectedClass = val => val + '-tab ' + defaultSelected;
return ( return (

View file

@ -17,7 +17,7 @@ function ChatGPTOptions() {
const models = endpointsConfig?.['chatGPTBrowser']?.['availableModels'] || []; const models = endpointsConfig?.['chatGPTBrowser']?.['availableModels'] || [];
const setOption = param => newValue => { const setOption = param => (newValue) => {
let update = {}; let update = {};
update[param] = newValue; update[param] = newValue;
setConversation(prevState => ({ setConversation(prevState => ({

View file

@ -2,17 +2,17 @@ import React from 'react';
export default function Footer() { export default function Footer() {
return ( return (
<div className="hidden px-3 pt-2 pb-1 text-center text-xs text-black/50 dark:text-white/50 md:block md:px-4 md:pt-3 md:pb-4"> <div className="hidden px-3 pb-1 pt-2 text-center text-xs text-black/50 dark:text-white/50 md:block md:px-4 md:pb-4 md:pt-3">
<a <a
href="https://github.com/danny-avila/chatgpt-clone" href="https://github.com/danny-avila/chatgpt-clone"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="underline" className="underline"
> >
{import.meta.env.VITE_APP_TITLE || "ChatGPT Clone"} {import.meta.env.VITE_APP_TITLE || 'ChatGPT Clone'}
</a> </a>
. Serves and searches all conversations reliably. All AI convos under one house. Pay per call and not . Serves and searches all conversations reliably. All AI convos under one house. Pay per call
per month (cents compared to dollars). and not per month (cents compared to dollars).
</div> </div>
); );
} }

View file

@ -40,7 +40,7 @@ function GoogleOptions() {
setSaveAsDialogShow(true); setSaveAsDialogShow(true);
}; };
const setOption = param => newValue => { const setOption = param => (newValue) => {
let update = {}; let update = {};
update[param] = newValue; update[param] = newValue;
setConversation(prevState => ({ setConversation(prevState => ({

View file

@ -12,8 +12,8 @@ const alternateName = {
azureOpenAI: 'Azure OpenAI', azureOpenAI: 'Azure OpenAI',
bingAI: 'Bing', bingAI: 'Bing',
chatGPTBrowser: 'ChatGPT', chatGPTBrowser: 'ChatGPT',
google: 'PaLM', google: 'PaLM'
} };
export default function ModelItem({ endpoint, value, onSelect }) { export default function ModelItem({ endpoint, value, onSelect }) {
const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false); const [setTokenDialogOpen, setSetTokenDialogOpen] = useState(false);
@ -42,7 +42,7 @@ export default function ModelItem({ endpoint, value, onSelect }) {
{isUserProvided ? ( {isUserProvided ? (
<button <button
className="invisible m-0 mr-1 flex-initial rounded-md p-0 text-xs font-medium text-gray-400 hover:text-gray-700 group-hover:visible dark:font-normal dark:text-gray-400 dark:hover:text-gray-200" className="invisible m-0 mr-1 flex-initial rounded-md p-0 text-xs font-medium text-gray-400 hover:text-gray-700 group-hover:visible dark:font-normal dark:text-gray-400 dark:hover:text-gray-200"
onClick={e => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
setSetTokenDialogOpen(true); setSetTokenDialogOpen(true);
}} }}

View file

@ -5,12 +5,7 @@ export default function EndpointItems({ endpoints, onSelect }) {
return ( return (
<> <>
{endpoints.map(endpoint => ( {endpoints.map(endpoint => (
<EndpointItem <EndpointItem key={endpoint} value={endpoint} onSelect={onSelect} endpoint={endpoint} />
key={endpoint}
value={endpoint}
onSelect={onSelect}
endpoint={endpoint}
/>
))} ))}
</> </>
); );

View file

@ -2,16 +2,23 @@ import { useState } from 'react';
import { FileUp } from 'lucide-react'; import { FileUp } from 'lucide-react';
import { cn } from '~/utils/'; import { cn } from '~/utils/';
const FileUpload = ({ onFileSelected, successText = null, invalidText = null, validator = null, text = null, id = '1' }) => { const FileUpload = ({
onFileSelected,
successText = null,
invalidText = null,
validator = null,
text = null,
id = '1'
}) => {
const [statusColor, setStatusColor] = useState('text-gray-600'); const [statusColor, setStatusColor] = useState('text-gray-600');
const [status, setStatus] = useState(null); const [status, setStatus] = useState(null);
const handleFileChange = event => { const handleFileChange = (event) => {
const file = event.target.files[0]; const file = event.target.files[0];
if (!file) return; if (!file) return;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = e => { reader.onload = (e) => {
const jsonData = JSON.parse(e.target.result); const jsonData = JSON.parse(e.target.result);
if (validator && !validator(jsonData)) { if (validator && !validator(jsonData)) {
setStatus('invalid'); setStatus('invalid');
@ -38,7 +45,9 @@ const FileUpload = ({ onFileSelected, successText = null, invalidText = null, va
)} )}
> >
<FileUp className="mr-1 flex w-[22px] items-center stroke-1" /> <FileUp className="mr-1 flex w-[22px] items-center stroke-1" />
<span className="flex text-xs ">{!status ? text || 'Import' : (status === 'success' ? successText : invalidText)}</span> <span className="flex text-xs ">
{!status ? text || 'Import' : status === 'success' ? successText : invalidText}
</span>
<input <input
id={`file-upload-${id}`} id={`file-upload-${id}`}
value="" value=""

View file

@ -4,7 +4,13 @@ import EditIcon from '../../svg/EditIcon.jsx';
import TrashIcon from '../../svg/TrashIcon.jsx'; import TrashIcon from '../../svg/TrashIcon.jsx';
import getIcon from '~/utils/getIcon'; import getIcon from '~/utils/getIcon';
export default function PresetItem({ preset = {}, value, onSelect, onChangePreset, onDeletePreset }) { export default function PresetItem({
preset = {},
value,
onSelect,
onChangePreset,
onDeletePreset
}) {
const { endpoint } = preset; const { endpoint } = preset;
const icon = getIcon({ const icon = getIcon({
@ -53,7 +59,7 @@ export default function PresetItem({ preset = {}, value, onSelect, onChangePrese
<div className="flex w-4 flex-1" /> <div className="flex w-4 flex-1" />
<button <button
className="invisible m-0 mr-1 rounded-md p-2 text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 " className="invisible m-0 mr-1 rounded-md p-2 text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
onClick={e => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
onChangePreset(preset); onChangePreset(preset);
}} }}
@ -62,7 +68,7 @@ export default function PresetItem({ preset = {}, value, onSelect, onChangePrese
</button> </button>
<button <button
className="invisible m-0 rounded-md text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 " className="invisible m-0 rounded-md text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
onClick={e => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
onDeletePreset(preset); onDeletePreset(preset);
}} }}

View file

@ -39,21 +39,21 @@ export default function NewConversationMenu() {
const deletePresetsMutation = useDeletePresetMutation(); const deletePresetsMutation = useDeletePresetMutation();
const createPresetMutation = useCreatePresetMutation(); const createPresetMutation = useCreatePresetMutation();
const importPreset = jsonData => { const importPreset = (jsonData) => {
createPresetMutation.mutate( createPresetMutation.mutate(
{ ...jsonData }, { ...jsonData },
{ {
onSuccess: data => { onSuccess: (data) => {
setPresets(data); setPresets(data);
}, },
onError: error => { onError: (error) => {
console.error('Error uploading the preset:', error); console.error('Error uploading the preset:', error);
} }
} }
); );
}; };
const onFileSelected = jsonData => { const onFileSelected = (jsonData) => {
const jsonPreset = { ...cleanupPreset({ preset: jsonData, endpointsConfig }), presetId: null }; const jsonPreset = { ...cleanupPreset({ preset: jsonData, endpointsConfig }), presetId: null };
importPreset(jsonPreset); importPreset(jsonPreset);
}; };
@ -80,7 +80,7 @@ export default function NewConversationMenu() {
}, [conversation]); }, [conversation]);
// set the current model // set the current model
const onSelectEndpoint = newEndpoint => { const onSelectEndpoint = (newEndpoint) => {
setMenuOpen(false); setMenuOpen(false);
if (!newEndpoint) return; if (!newEndpoint) return;
@ -90,7 +90,7 @@ export default function NewConversationMenu() {
}; };
// set the current model // set the current model
const onSelectPreset = newPreset => { const onSelectPreset = (newPreset) => {
setMenuOpen(false); setMenuOpen(false);
if (!newPreset) return; if (!newPreset) return;
else { else {
@ -98,7 +98,7 @@ export default function NewConversationMenu() {
} }
}; };
const onChangePreset = preset => { const onChangePreset = (preset) => {
setPresetModelVisible(true); setPresetModelVisible(true);
setPreset(preset); setPreset(preset);
}; };
@ -107,7 +107,7 @@ export default function NewConversationMenu() {
deletePresetsMutation.mutate({ arg: {} }); deletePresetsMutation.mutate({ arg: {} });
}; };
const onDeletePreset = preset => { const onDeletePreset = (preset) => {
deletePresetsMutation.mutate({ arg: preset }); deletePresetsMutation.mutate({ arg: preset });
}; };
@ -121,10 +121,7 @@ export default function NewConversationMenu() {
return ( return (
<Dialog> <Dialog>
<DropdownMenu <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
open={menuOpen}
onOpenChange={setMenuOpen}
>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="outline" variant="outline"
@ -148,22 +145,18 @@ export default function NewConversationMenu() {
className="overflow-y-auto" className="overflow-y-auto"
> >
{availableEndpoints.length ? ( {availableEndpoints.length ? (
<EndpointItems <EndpointItems endpoints={availableEndpoints} onSelect={onSelectEndpoint} />
endpoints={availableEndpoints}
onSelect={onSelectEndpoint}
/>
) : ( ) : (
<DropdownMenuLabel className="dark:text-gray-300">No endpoint available.</DropdownMenuLabel> <DropdownMenuLabel className="dark:text-gray-300">
No endpoint available.
</DropdownMenuLabel>
)} )}
</DropdownMenuRadioGroup> </DropdownMenuRadioGroup>
<div className="mt-6 w-full" /> <div className="mt-6 w-full" />
<DropdownMenuLabel className="flex items-center dark:text-gray-300"> <DropdownMenuLabel className="flex items-center dark:text-gray-300">
<span <span className="cursor-pointer" onClick={() => setShowPresets(prev => !prev)}>
className="cursor-pointer"
onClick={() => setShowPresets(prev => !prev)}
>
{showPresets ? 'Hide ' : 'Show '} Presets {showPresets ? 'Hide ' : 'Show '} Presets
</span> </span>
<div className="flex-1" /> <div className="flex-1" />

View file

@ -16,8 +16,15 @@ function OpenAIOptions() {
const [conversation, setConversation] = useRecoilState(store.conversation) || {}; const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const { endpoint, conversationId } = conversation; const { endpoint, conversationId } = conversation;
const { model, chatGptLabel, promptPrefix, temperature, top_p, presence_penalty, frequency_penalty } = const {
conversation; model,
chatGptLabel,
promptPrefix,
temperature,
top_p,
presence_penalty,
frequency_penalty
} = conversation;
const endpointsConfig = useRecoilValue(store.endpointsConfig); const endpointsConfig = useRecoilValue(store.endpointsConfig);
@ -36,7 +43,7 @@ function OpenAIOptions() {
setSaveAsDialogShow(true); setSaveAsDialogShow(true);
}; };
const setOption = param => newValue => { const setOption = param => (newValue) => {
let update = {}; let update = {};
update[param] = newValue; update[param] = newValue;
setConversation(prevState => ({ setConversation(prevState => ({

View file

@ -9,10 +9,7 @@ function InputWithLabel({ value, onChange, label, id }) {
return ( return (
<> <>
<Label <Label htmlFor={id} className="text-left text-sm font-medium">
htmlFor={id}
className="text-left text-sm font-medium"
>
{label} {label}
<br /> <br />
</Label> </Label>

View file

@ -49,8 +49,8 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
const helpText = { const helpText = {
bingAI: ( bingAI: (
<small className="break-all text-gray-600"> <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 The Bing Access Token is the "_U" cookie from bing.com. Use dev tools or an extension while
into the site to view it. logged into the site to view it.
</small> </small>
), ),
chatGPTBrowser: ( chatGPTBrowser: (
@ -96,8 +96,8 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
> >
Create a Service Account Create a Service Account
</a> </a>
. Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role. Lastly, create . Make sure to click 'Create and Continue' to give at least the 'Vertex AI User' role.
a JSON key to import here. Lastly, create a JSON key to import here.
</small> </small>
) )
}; };
@ -122,10 +122,7 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
} }
return ( return (
<Dialog <Dialog open={open} onOpenChange={onOpenChange}>
open={open}
onOpenChange={onOpenChange}
>
<DialogTemplate <DialogTemplate
title={`Set Token of ${endpoint}`} title={`Set Token of ${endpoint}`}
main={ main={
@ -137,7 +134,7 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
text="Import Service Account JSON Key" text="Import Service Account JSON Key"
successText="Successfully Imported Service Account JSON Key" successText="Successfully Imported Service Account JSON Key"
invalidText="Invalid Service Account JSON Key, Did you import the correct file?" invalidText="Invalid Service Account JSON Key, Did you import the correct file?"
validator={credentials => { validator={(credentials) => {
if (!credentials) { if (!credentials) {
return false; return false;
} }
@ -168,7 +165,7 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
return true; return true;
}} }}
onFileSelected={data => { onFileSelected={(data) => {
setToken(JSON.stringify(data)); setToken(JSON.stringify(data));
}} }}
/> />
@ -244,7 +241,9 @@ const SetTokenDialog = ({ open, onOpenChange, endpoint }) => {
/> />
</> </>
)} )}
<small className="text-red-600">Your token will be sent to the server, but not saved.</small> <small className="text-red-600">
Your token will be sent to the server, but not saved.
</small>
{helpText?.[endpoint]} {helpText?.[endpoint]}
</div> </div>
} }

View file

@ -17,7 +17,7 @@ export default function SubmitButton({
const isTokenProvided = endpointsConfig?.[endpoint]?.userProvide ? !!getToken() : true; const isTokenProvided = endpointsConfig?.[endpoint]?.userProvide ? !!getToken() : true;
const clickHandler = e => { const clickHandler = (e) => {
e.preventDefault(); e.preventDefault();
submitMessage(); submitMessage();
}; };
@ -101,12 +101,7 @@ export default function SubmitButton({
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="22" y1="2" x2="11" y2="13" />
x1="22"
y1="2"
x2="11"
y2="13"
/>
<polygon points="22 2 15 22 11 13 2 9 22 2" /> <polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg> </svg>
</div> </div>

View file

@ -34,7 +34,7 @@ export default function TextChat({ isSearchView = false }) {
// const bingStylesRef = useRef(null); // const bingStylesRef = useRef(null);
const [showBingToneSetting, setShowBingToneSetting] = useState(false); const [showBingToneSetting, setShowBingToneSetting] = useState(false);
const isNotAppendable = (latestMessage?.unfinished & !isSubmitting) || latestMessage?.error; const isNotAppendable = latestMessage?.unfinished & !isSubmitting || latestMessage?.error;
// auto focus to input, when enter a conversation. // auto focus to input, when enter a conversation.
useEffect(() => { useEffect(() => {
@ -64,12 +64,12 @@ export default function TextChat({ isSearchView = false }) {
setText(''); setText('');
}; };
const handleStopGenerating = e => { const handleStopGenerating = (e) => {
e.preventDefault(); e.preventDefault();
stopGenerating(); stopGenerating();
}; };
const handleKeyDown = e => { const handleKeyDown = (e) => {
if (e.key === 'Enter' && isSubmitting) { if (e.key === 'Enter' && isSubmitting) {
return; return;
} }
@ -83,7 +83,7 @@ export default function TextChat({ isSearchView = false }) {
} }
}; };
const handleKeyUp = e => { const handleKeyUp = (e) => {
if (e.keyCode === 8 && e.target.value.trim() === '') { if (e.keyCode === 8 && e.target.value.trim() === '') {
setText(e.target.value); setText(e.target.value);
} }
@ -105,7 +105,7 @@ export default function TextChat({ isSearchView = false }) {
isComposing.current = false; isComposing.current = false;
}; };
const changeHandler = e => { const changeHandler = (e) => {
const { value } = e.target; const { value } = e.target;
setText(value); setText(value);

View file

@ -27,7 +27,7 @@ export default function MessageHandler() {
text: data, text: data,
parentMessageId: message?.overrideParentMessageId, parentMessageId: message?.overrideParentMessageId,
messageId: message?.overrideParentMessageId + '_', messageId: message?.overrideParentMessageId + '_',
submitting: true, submitting: true
// unfinished: true // unfinished: true
} }
]); ]);
@ -40,7 +40,7 @@ export default function MessageHandler() {
text: data, text: data,
parentMessageId: message?.messageId, parentMessageId: message?.messageId,
messageId: message?.messageId + '_', messageId: message?.messageId + '_',
submitting: true, submitting: true
// unfinished: true // unfinished: true
} }
]); ]);
@ -153,7 +153,7 @@ export default function MessageHandler() {
return; return;
}; };
const abortConversation = conversationId => { const abortConversation = (conversationId) => {
console.log(submission); console.log(submission);
const { endpoint } = submission?.conversation || {}; const { endpoint } = submission?.conversation || {};
@ -161,18 +161,18 @@ export default function MessageHandler() {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` Authorization: `Bearer ${token}`
}, },
body: JSON.stringify({ body: JSON.stringify({
abortKey: conversationId abortKey: conversationId
}) })
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then((data) => {
console.log('aborted', data); console.log('aborted', data);
cancelHandler(data, submission); cancelHandler(data, submission);
}) })
.catch(error => { .catch((error) => {
console.error('Error aborting request'); console.error('Error aborting request');
console.error(error); console.error(error);
// errorHandler({ text: 'Error aborting request' }, { ...submission, message }); // errorHandler({ text: 'Error aborting request' }, { ...submission, message });
@ -190,10 +190,10 @@ export default function MessageHandler() {
const events = new SSE(server, { const events = new SSE(server, {
payload: JSON.stringify(payload), payload: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`} headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }
}); });
events.onmessage = e => { events.onmessage = (e) => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
if (data.final) { if (data.final) {
@ -219,7 +219,8 @@ export default function MessageHandler() {
events.onopen = () => console.log('connection is opened'); events.onopen = () => console.log('connection is opened');
events.oncancel = () => abortConversation(message?.conversationId || submission?.conversationId); events.oncancel = () =>
abortConversation(message?.conversationId || submission?.conversationId);
events.onerror = function (e) { events.onerror = function (e) {
console.log('error in opening conn.'); console.log('error in opening conn.');

View file

@ -7,15 +7,9 @@ const CodeBlock = ({ lang, codeChildren }) => {
return ( return (
<div className="rounded-md bg-black"> <div className="rounded-md bg-black">
<CodeBar <CodeBar lang={lang} codeRef={codeRef} />
lang={lang}
codeRef={codeRef}
/>
<div className="overflow-y-auto p-4"> <div className="overflow-y-auto p-4">
<code <code ref={codeRef} className={`hljs !whitespace-pre language-${lang}`}>
ref={codeRef}
className={`hljs !whitespace-pre language-${lang}`}
>
{codeChildren} {codeChildren}
</code> </code>
</div> </div>

View file

@ -4,7 +4,7 @@ import rehypeKatex from 'rehype-katex';
import rehypeHighlight from 'rehype-highlight'; import rehypeHighlight from 'rehype-highlight';
import remarkMath from 'remark-math'; import remarkMath from 'remark-math';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw' import rehypeRaw from 'rehype-raw';
import CodeBlock from './CodeBlock'; import CodeBlock from './CodeBlock';
import { langSubset } from '~/utils/languages.mjs'; import { langSubset } from '~/utils/languages.mjs';
@ -19,7 +19,7 @@ const Content = React.memo(({ content }) => {
subset: langSubset subset: langSubset
} }
], ],
[rehypeRaw], [rehypeRaw]
]; ];
return ( return (
@ -29,7 +29,7 @@ const Content = React.memo(({ content }) => {
linkTarget="_new" linkTarget="_new"
components={{ components={{
code, code,
p, p
// em, // em,
}} }}
> >
@ -46,17 +46,12 @@ const code = React.memo((props) => {
if (inline) { if (inline) {
return <code className={className}>{children}</code>; return <code className={className}>{children}</code>;
} else { } else {
return ( return <CodeBlock lang={lang || 'text'} codeChildren={children} />;
<CodeBlock
lang={lang || 'text'}
codeChildren={children}
/>
);
} }
}); });
const p = React.memo((props) => { const p = React.memo((props) => {
return <p className="whitespace-pre-wrap mb-2">{props?.children}</p>; return <p className="mb-2 whitespace-pre-wrap">{props?.children}</p>;
}); });
// const blinker = ({ node }) => { // const blinker = ({ node }) => {

View file

@ -3,7 +3,11 @@ import React from 'react';
export default function SubRow({ children, classes = '', subclasses = '', onClick }) { export default function SubRow({ children, classes = '', subclasses = '', onClick }) {
return ( return (
<div className={`flex justify-between ${classes}`} onClick={onClick}> <div className={`flex justify-between ${classes}`} onClick={onClick}>
<div className={`flex items-center justify-center gap-1 self-center pt-2 text-xs ${subclasses}`}>{children}</div> <div
className={`flex items-center justify-center gap-1 self-center pt-2 text-xs ${subclasses}`}
>
{children}
</div>
</div> </div>
); );
} }

View file

@ -32,10 +32,14 @@ export default function HoverButtons({
// for now, once branching is supported, regerate will be enabled // for now, once branching is supported, regerate will be enabled
const regenerateEnabled = const regenerateEnabled =
// !message?.error && // !message?.error &&
!message?.isCreatedByUser && !message?.searchResult && !isEditting && !isSubmitting && branchingSupported; !message?.isCreatedByUser &&
!message?.searchResult &&
!isEditting &&
!isSubmitting &&
branchingSupported;
return ( return (
<div className="visible mt-2 flex justify-center gap-3 self-end text-gray-400 md:gap-4 lg:absolute lg:top-0 lg:right-0 lg:mt-0 lg:translate-x-full lg:gap-1 lg:self-center lg:pl-2"> <div className="visible mt-2 flex justify-center gap-3 self-end text-gray-400 md:gap-4 lg:absolute lg:right-0 lg:top-0 lg:mt-0 lg:translate-x-full lg:gap-1 lg:self-center lg:pl-2">
{editEnabled ? ( {editEnabled ? (
<button <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 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"

View file

@ -41,7 +41,9 @@ export default function Message({
const { ask, regenerate } = useMessageHandler(); const { ask, regenerate } = useMessageHandler();
const { switchToConversation } = store.useConversation(); const { switchToConversation } = store.useConversation();
const blinker = submitting && isSubmitting; const blinker = submitting && isSubmitting;
const getConversationQuery = useGetConversationByIdQuery(message.conversationId, { enabled: false }); const getConversationQuery = useGetConversationByIdQuery(message.conversationId, {
enabled: false
});
// debugging // debugging
// useEffect(() => { // useEffect(() => {
@ -71,9 +73,9 @@ export default function Message({
} }
}; };
const getError = text => { const getError = (text) => {
const match = text.match(/\{[^{}]*\}/); const match = text.match(/\{[^{}]*\}/);
var json = match ? match[0] : '' var json = match ? match[0] : '';
if (isJson(json)) { if (isJson(json)) {
json = JSON.parse(json); json = JSON.parse(json);
if (json.code === 'invalid_api_key') { if (json.code === 'invalid_api_key') {
@ -124,7 +126,7 @@ export default function Message({
if (!isSubmitting && !message?.isCreatedByUser) regenerate(message); if (!isSubmitting && !message?.isCreatedByUser) regenerate(message);
}; };
const copyToClipboard = setIsCopied => { const copyToClipboard = (setIsCopied) => {
setIsCopied(true); setIsCopied(true);
copy(message?.text); copy(message?.text);
@ -135,17 +137,14 @@ export default function Message({
const clickSearchResult = async () => { const clickSearchResult = async () => {
if (!searchResult) return; if (!searchResult) return;
getConversationQuery.refetch(message.conversationId).then(response => { getConversationQuery.refetch(message.conversationId).then((response) => {
switchToConversation(response.data); switchToConversation(response.data);
}); });
}; };
return ( return (
<> <>
<div <div {...props} onWheel={handleWheel}>
{...props}
onWheel={handleWheel}
>
<div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl"> <div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
<div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm"> <div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm">
{typeof icon === 'string' && icon.match(/[^\\x00-\\x7F]+/) ? ( {typeof icon === 'string' && icon.match(/[^\\x00-\\x7F]+/) ? (
@ -197,10 +196,7 @@ export default function Message({
> >
Save & Submit Save & Submit
</button> </button>
<button <button className="btn btn-neutral relative" onClick={() => enterEdit(true)}>
className="btn btn-neutral relative"
onClick={() => enterEdit(true)}
>
Cancel Cancel
</button> </button>
</div> </div>

View file

@ -6,7 +6,7 @@ import { Button } from '../ui/Button.tsx';
import store from '~/store'; import store from '~/store';
const clipPromptPrefix = str => { const clipPromptPrefix = (str) => {
if (typeof str !== 'string') { if (typeof str !== 'string') {
return null; return null;
} else if (str.length > 10) { } else if (str.length > 10) {
@ -61,7 +61,9 @@ const MessageHeader = ({ isSearchView = false }) => {
)} )}
onClick={() => (endpoint === 'chatGPTBrowser' ? null : setSaveAsDialogShow(true))} onClick={() => (endpoint === 'chatGPTBrowser' ? null : setSaveAsDialogShow(true))}
> >
<div className="d-block flex w-full items-center justify-center p-3">{getConversationTitle()}</div> <div className="d-block flex w-full items-center justify-center p-3">
{getConversationTitle()}
</div>
</div> </div>
<EndpointOptionsDialog <EndpointOptionsDialog

View file

@ -17,7 +17,7 @@ export default function MultiMessage({
const [siblingIdx, setSiblingIdx] = useRecoilState(store.messagesSiblingIdxFamily(messageId)); const [siblingIdx, setSiblingIdx] = useRecoilState(store.messagesSiblingIdxFamily(messageId));
const setSiblingIdxRev = value => { const setSiblingIdxRev = (value) => {
setSiblingIdx(messagesTree?.length - value - 1); setSiblingIdx(messagesTree?.length - value - 1);
}; };

View file

@ -1,11 +1,10 @@
import React from 'react'; import React from 'react';
export default function ScrollToBottom({ scrollHandler}) { export default function ScrollToBottom({ scrollHandler }) {
return ( return (
<button <button
onClick={scrollHandler} onClick={scrollHandler}
className="absolute right-6 bottom-[124px] z-10 cursor-pointer rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 md:bottom-[120px]" className="absolute bottom-[124px] right-6 z-10 cursor-pointer rounded-full border border-gray-200 bg-gray-50 text-gray-600 dark:border-white/10 dark:bg-white/10 dark:text-gray-200 md:bottom-[120px]"
> >
<svg <svg
stroke="currentColor" stroke="currentColor"
@ -19,12 +18,7 @@ export default function ScrollToBottom({ scrollHandler}) {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="12" y1="5" x2="12" y2="19" />
x1="12"
y1="5"
x2="12"
y2="19"
/>
<polyline points="19 12 12 19 5 12" /> <polyline points="19 12 12 19 5 12" />
</svg> </svg>
</button> </button>

View file

@ -1,26 +1,58 @@
import React from 'react'; import React from 'react';
export default function SiblingSwitch({ export default function SiblingSwitch({ siblingIdx, siblingCount, setSiblingIdx }) {
siblingIdx,
siblingCount,
setSiblingIdx
}) {
const previous = () => { const previous = () => {
setSiblingIdx(siblingIdx - 1); setSiblingIdx(siblingIdx - 1);
} };
const next = () => { const next = () => {
setSiblingIdx(siblingIdx + 1); setSiblingIdx(siblingIdx + 1);
} };
return siblingCount > 1 ? ( return siblingCount > 1 ? (
<> <>
<button className="dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400" onClick={previous} disabled={siblingIdx==0}> <button
<svg stroke="currentColor" fill="none" strokeWidth="1.5" viewBox="0 0 24 24" strokeLinecap="round" strokeLinejoin="round" className="h-3 w-3" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="15 18 9 12 15 6"></polyline></svg> className="disabled:text-gray-300 dark:text-white dark:disabled:text-gray-400"
onClick={previous}
disabled={siblingIdx == 0}
>
<svg
stroke="currentColor"
fill="none"
strokeWidth="1.5"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="h-3 w-3"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button> </button>
<span className="flex-grow flex-shrink-0">{siblingIdx + 1}/{siblingCount}</span> <span className="flex-shrink-0 flex-grow">
<button className="dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400" onClick={next} disabled={siblingIdx==siblingCount-1}> {siblingIdx + 1}/{siblingCount}
<svg stroke="currentColor" fill="none" strokeWidth="1.5" viewBox="0 0 24 24" strokeLinecap="round" strokeLinejoin="round" className="h-3 w-3" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="9 18 15 12 9 6"></polyline></svg> </span>
<button
className="disabled:text-gray-300 dark:text-white dark:disabled:text-gray-400"
onClick={next}
disabled={siblingIdx == siblingCount - 1}
>
<svg
stroke="currentColor"
fill="none"
strokeWidth="1.5"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="h-3 w-3"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button> </button>
</> </>
):null; ) : null;
} }

View file

@ -76,7 +76,7 @@ export default function Messages({ isSearchView = false }) {
timeoutId = setTimeout(handleScroll, 100); timeoutId = setTimeout(handleScroll, 100);
}; };
const scrollHandler = e => { const scrollHandler = (e) => {
e.preventDefault(); e.preventDefault();
scrollToBottom(); scrollToBottom();
}; };
@ -87,10 +87,7 @@ export default function Messages({ isSearchView = false }) {
ref={scrollableRef} ref={scrollableRef}
onScroll={debouncedHandleScroll} onScroll={debouncedHandleScroll}
> >
<div <div className="dark:gpt-dark-gray mb-32 h-auto md:mb-48" ref={screenshotTargetRef}>
className="dark:gpt-dark-gray mb-32 h-auto md:mb-48"
ref={screenshotTargetRef}
>
<div className="dark:gpt-dark-gray flex h-auto flex-col items-center text-sm"> <div className="dark:gpt-dark-gray flex h-auto flex-col items-center text-sm">
<MessageHeader isSearchView={isSearchView} /> <MessageHeader isSearchView={isSearchView} />
{_messagesTree === null ? ( {_messagesTree === null ? (

View file

@ -25,9 +25,7 @@ export default function ClearConvos() {
return ( return (
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<button <button className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700">
className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
>
<TrashIcon /> <TrashIcon />
Clear conversations Clear conversations
</button> </button>

View file

@ -52,7 +52,7 @@ export default function ExportModel({ open, onOpenChange }) {
setRecursive(true); setRecursive(true);
}, [open]); }, [open]);
const _setType = newType => { const _setType = (newType) => {
const exportBranchesSupport = newType === 'json' || newType === 'csv' || newType === 'webpage'; const exportBranchesSupport = newType === 'json' || newType === 'csv' || newType === 'webpage';
const exportOptionsSupport = newType !== 'csv' && newType !== 'screenshot'; const exportOptionsSupport = newType !== 'csv' && newType !== 'screenshot';
@ -66,7 +66,13 @@ export default function ExportModel({ open, onOpenChange }) {
// return an object or an array based on branches and recursive option // return an object or an array based on branches and recursive option
// messageId is used to get siblindIdx from recoil snapshot // messageId is used to get siblindIdx from recoil snapshot
const buildMessageTree = async ({ messageId, message, messages, branches = false, recursive = false }) => { const buildMessageTree = async ({
messageId,
message,
messages,
branches = false,
recursive = false
}) => {
let children = []; let children = [];
if (messages?.length) if (messages?.length)
if (branches) if (branches)
@ -137,21 +143,39 @@ export default function ExportModel({ open, onOpenChange }) {
extension: 'csv', extension: 'csv',
exportType: exportFromJSON.types.csv, exportType: exportFromJSON.types.csv,
beforeTableEncode: entries => [ beforeTableEncode: entries => [
{ fieldName: 'sender', fieldValues: entries.find(e => e.fieldName == 'sender').fieldValues }, {
fieldName: 'sender',
fieldValues: entries.find(e => e.fieldName == 'sender').fieldValues
},
{ fieldName: 'text', fieldValues: entries.find(e => e.fieldName == 'text').fieldValues }, { fieldName: 'text', fieldValues: entries.find(e => e.fieldName == 'text').fieldValues },
{ {
fieldName: 'isCreatedByUser', fieldName: 'isCreatedByUser',
fieldValues: entries.find(e => e.fieldName == 'isCreatedByUser').fieldValues fieldValues: entries.find(e => e.fieldName == 'isCreatedByUser').fieldValues
}, },
{ fieldName: 'error', fieldValues: entries.find(e => e.fieldName == 'error').fieldValues }, {
{ fieldName: 'unfinished', fieldValues: entries.find(e => e.fieldName == 'unfinished').fieldValues }, fieldName: 'error',
{ fieldName: 'cancelled', fieldValues: entries.find(e => e.fieldName == 'cancelled').fieldValues }, fieldValues: entries.find(e => e.fieldName == 'error').fieldValues
{ fieldName: 'messageId', fieldValues: entries.find(e => e.fieldName == 'messageId').fieldValues }, },
{
fieldName: 'unfinished',
fieldValues: entries.find(e => e.fieldName == 'unfinished').fieldValues
},
{
fieldName: 'cancelled',
fieldValues: entries.find(e => e.fieldName == 'cancelled').fieldValues
},
{
fieldName: 'messageId',
fieldValues: entries.find(e => e.fieldName == 'messageId').fieldValues
},
{ {
fieldName: 'parentMessageId', fieldName: 'parentMessageId',
fieldValues: entries.find(e => e.fieldName == 'parentMessageId').fieldValues fieldValues: entries.find(e => e.fieldName == 'parentMessageId').fieldValues
}, },
{ fieldName: 'createdAt', fieldValues: entries.find(e => e.fieldName == 'createdAt').fieldValues } {
fieldName: 'createdAt',
fieldValues: entries.find(e => e.fieldName == 'createdAt').fieldValues
}
] ]
}); });
}; };
@ -284,10 +308,7 @@ export default function ExportModel({ open, onOpenChange }) {
'rounded-md border border-gray-200 focus:border-slate-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] 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-500 dark:bg-gray-700 focus:dark:bg-gray-600 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'; 'rounded-md border border-gray-200 focus:border-slate-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] 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-500 dark:bg-gray-700 focus:dark:bg-gray-600 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';
return ( return (
<Dialog <Dialog open={open} onOpenChange={onOpenChange}>
open={open}
onOpenChange={onOpenChange}
>
<DialogTemplate <DialogTemplate
title="Export conversation" title="Export conversation"
className="max-w-full sm:max-w-2xl" className="max-w-full sm:max-w-2xl"
@ -295,10 +316,7 @@ export default function ExportModel({ open, onOpenChange }) {
<div className="flex w-full flex-col items-center gap-6"> <div className="flex w-full flex-col items-center gap-6">
<div className="grid w-full gap-6 sm:grid-cols-2"> <div className="grid w-full gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-start justify-start gap-2"> <div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label <Label htmlFor="filename" className="text-left text-sm font-medium">
htmlFor="filename"
className="text-left text-sm font-medium"
>
Filename Filename
</Label> </Label>
<Input <Input
@ -313,10 +331,7 @@ export default function ExportModel({ open, onOpenChange }) {
/> />
</div> </div>
<div className="col-span-1 flex flex-col items-start justify-start gap-2"> <div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label <Label htmlFor="type" className="text-left text-sm font-medium">
htmlFor="type"
className="text-left text-sm font-medium"
>
Type Type
</Label> </Label>
<Dropdown <Dropdown
@ -335,10 +350,7 @@ export default function ExportModel({ open, onOpenChange }) {
<div className="grid w-full gap-6 sm:grid-cols-2"> <div className="grid w-full gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-start justify-start gap-2"> <div className="col-span-1 flex flex-col items-start justify-start gap-2">
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="includeOptions" className="text-left text-sm font-medium">
htmlFor="includeOptions"
className="text-left text-sm font-medium"
>
Include endpoint options Include endpoint options
</Label> </Label>
<div className="flex h-[40px] w-full items-center space-x-3"> <div className="flex h-[40px] w-full items-center space-x-3">
@ -359,10 +371,7 @@ export default function ExportModel({ open, onOpenChange }) {
</div> </div>
</div> </div>
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="exportBranches" className="text-left text-sm font-medium">
htmlFor="exportBranches"
className="text-left text-sm font-medium"
>
Export all message branches Export all message branches
</Label> </Label>
<div className="flex h-[40px] w-full items-center space-x-3"> <div className="flex h-[40px] w-full items-center space-x-3">
@ -383,10 +392,7 @@ export default function ExportModel({ open, onOpenChange }) {
</div> </div>
{type === 'json' ? ( {type === 'json' ? (
<div className="grid w-full items-center gap-2"> <div className="grid w-full items-center gap-2">
<Label <Label htmlFor="recursive" className="text-left text-sm font-medium">
htmlFor="recursive"
className="text-left text-sm font-medium"
>
Recursive or sequential? Recursive or sequential?
</Label> </Label>
<div className="flex h-[40px] w-full items-center space-x-3"> <div className="flex h-[40px] w-full items-center space-x-3">

View file

@ -25,7 +25,7 @@ export default function ExportConversation() {
<> <>
<button <button
className={cn( className={cn(
'flex py-3 px-3 items-center gap-3 transition-colors duration-200 text-white cursor-pointer text-sm hover:bg-gray-700 w-full', 'flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700',
exportable ? 'cursor-pointer text-white' : 'cursor-not-allowed text-gray-400' exportable ? 'cursor-pointer text-white' : 'cursor-not-allowed text-gray-400'
)} )}
onClick={clickHandler} onClick={clickHandler}
@ -34,10 +34,7 @@ export default function ExportConversation() {
Export conversation Export conversation
</button> </button>
<ExportModel <ExportModel open={open} onOpenChange={setOpen} />
open={open}
onOpenChange={setOpen}
/>
</> </>
); );
} }

View file

@ -6,13 +6,13 @@ export default function Logout() {
const { user, logout } = useAuthContext(); const { user, logout } = useAuthContext();
const handleLogout = () => { const handleLogout = () => {
logout() logout();
window.location.reload(); window.location.reload();
}; };
return ( return (
<button <button
className="flex py-3 px-3 items-center gap-3 transition-colors duration-200 text-white cursor-pointer text-sm hover:bg-gray-700 w-full" className="flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-700"
onClick={handleLogout} onClick={handleLogout}
> >
<LogOutIcon /> <LogOutIcon />

View file

@ -9,7 +9,7 @@ export default function MobileNav({ setNavVisible }) {
const { title = 'New Chat' } = conversation || {}; const { title = 'New Chat' } = conversation || {};
return ( return (
<div className="fixed top-0 left-0 right-0 z-10 flex items-center border-b border-white/20 bg-gray-800 pl-1 pt-1 text-gray-200 sm:pl-3 md:hidden"> <div className="fixed left-0 right-0 top-0 z-10 flex items-center border-b border-white/20 bg-gray-800 pl-1 pt-1 text-gray-200 sm:pl-3 md:hidden">
<button <button
type="button" type="button"
className="-ml-0.5 -mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white dark:hover:text-white" className="-ml-0.5 -mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white dark:hover:text-white"
@ -28,32 +28,13 @@ export default function MobileNav({ setNavVisible }) {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="3" y1="12" x2="21" y2="12" />
x1="3" <line x1="3" y1="6" x2="21" y2="6" />
y1="12" <line x1="3" y1="18" x2="21" y2="18" />
x2="21"
y2="12"
/>
<line
x1="3"
y1="6"
x2="21"
y2="6"
/>
<line
x1="3"
y1="18"
x2="21"
y2="18"
/>
</svg> </svg>
</button> </button>
<h1 className="flex-1 text-center text-base font-normal">{title || 'New Chat'}</h1> <h1 className="flex-1 text-center text-base font-normal">{title || 'New Chat'}</h1>
<button <button type="button" className="px-3" onClick={() => newConversation()}>
type="button"
className="px-3"
onClick={() => newConversation()}
>
<svg <svg
stroke="currentColor" stroke="currentColor"
fill="none" fill="none"
@ -66,18 +47,8 @@ export default function MobileNav({ setNavVisible }) {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="12" y1="5" x2="12" y2="19" />
x1="12" <line x1="5" y1="12" x2="19" y2="12" />
y1="5"
x2="12"
y2="19"
/>
<line
x1="5"
y1="12"
x2="19"
y2="12"
/>
</svg> </svg>
</button> </button>
</div> </div>

View file

@ -12,10 +12,7 @@ import DotsIcon from '../svg/DotsIcon';
export default function NavLinks({ clearSearch, isSearchEnabled }) { export default function NavLinks({ clearSearch, isSearchEnabled }) {
const { user, logout } = useAuthContext(); const { user, logout } = useAuthContext();
return ( return (
<Menu <Menu as="div" className="group relative">
as="div"
className="group relative"
>
{({ open }) => ( {({ open }) => (
<> <>
<Menu.Button <Menu.Button
@ -28,7 +25,9 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
<div className="relative flex"> <div className="relative flex">
<img <img
className="rounded-sm" className="rounded-sm"
src={user?.avatar || `https://avatars.dicebear.com/api/initials/${user?.name}.svg`} src={
user?.avatar || `https://avatars.dicebear.com/api/initials/${user?.name}.svg`
}
alt="" alt=""
/> />
</div> </div>
@ -54,17 +53,11 @@ export default function NavLinks({ clearSearch, isSearchEnabled }) {
</Menu.Item> </Menu.Item>
<Menu.Item>{({}) => <ExportConversation />}</Menu.Item> <Menu.Item>{({}) => <ExportConversation />}</Menu.Item>
<div <div className="my-1.5 h-px bg-white/20" role="none"></div>
className="my-1.5 h-px bg-white/20"
role="none"
></div>
<Menu.Item>{({}) => <DarkMode />}</Menu.Item> <Menu.Item>{({}) => <DarkMode />}</Menu.Item>
<Menu.Item>{({}) => <ClearConvos />}</Menu.Item> <Menu.Item>{({}) => <ClearConvos />}</Menu.Item>
<div <div className="my-1.5 h-px bg-white/20" role="none"></div>
className="my-1.5 h-px bg-white/20"
role="none"
></div>
<Menu.Item> <Menu.Item>
<Logout /> <Logout />
</Menu.Item> </Menu.Item>

View file

@ -13,7 +13,7 @@ export default function NewChat() {
return ( return (
<a <a
onClick={clickHandler} onClick={clickHandler}
className="mb-2 flex flex-shrink-0 cursor-pointer items-center gap-3 rounded-md border border-white/20 py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10" className="mb-2 flex flex-shrink-0 cursor-pointer items-center gap-3 rounded-md border border-white/20 px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"
> >
<svg <svg
stroke="currentColor" stroke="currentColor"
@ -27,18 +27,8 @@ export default function NewChat() {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="12" y1="5" x2="12" y2="19" />
x1="12" <line x1="5" y1="12" x2="19" y2="12" />
y1="5"
x2="12"
y2="19"
/>
<line
x1="5"
y1="12"
x2="19"
y2="12"
/>
</svg> </svg>
New chat New chat
</a> </a>

View file

@ -3,10 +3,9 @@ import { useRecoilState } from 'recoil';
import store from '~/store'; import store from '~/store';
export default function SearchBar({ clearSearch }) { export default function SearchBar({ clearSearch }) {
const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery); const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery);
const handleKeyUp = e => { const handleKeyUp = (e) => {
const { value } = e.target; const { value } = e.target;
if (e.keyCode === 8 && value === '') { if (e.keyCode === 8 && value === '') {
setSearchQuery(''); setSearchQuery('');
@ -15,7 +14,7 @@ export default function SearchBar({ clearSearch }) {
}; };
return ( return (
<div className="flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"> <div className="flex cursor-pointer items-center gap-3 rounded-md px-3 py-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10">
{<Search className="h-4 w-4" />} {<Search className="h-4 w-4" />}
<input <input
type="text" type="text"

View file

@ -42,10 +42,8 @@ export default function Nav({ navVisible, setNavVisible }) {
const debouncedSearchTerm = useDebounce(searchQuery, 750); const debouncedSearchTerm = useDebounce(searchQuery, 750);
const searchQueryFn = useSearchQuery(debouncedSearchTerm, pageNumber, { const searchQueryFn = useSearchQuery(debouncedSearchTerm, pageNumber, {
enabled: !!debouncedSearchTerm && enabled:
debouncedSearchTerm.length > 0 && !!debouncedSearchTerm && debouncedSearchTerm.length > 0 && isSearchEnabled && isSearching
isSearchEnabled &&
isSearching,
}); });
const onSearchSuccess = (data, expectedPage) => { const onSearchSuccess = (data, expectedPage) => {
@ -64,11 +62,10 @@ export default function Nav({ navVisible, setNavVisible }) {
//we use isInitialLoading here instead of isLoading because query is disabled by default //we use isInitialLoading here instead of isLoading because query is disabled by default
if (searchQueryFn.isInitialLoading) { if (searchQueryFn.isInitialLoading) {
setIsFetching(true); setIsFetching(true);
} } else if (searchQueryFn.data) {
else if (searchQueryFn.data) {
onSearchSuccess(searchQueryFn.data); onSearchSuccess(searchQueryFn.data);
} }
}, [searchQueryFn.data, searchQueryFn.isInitialLoading]) }, [searchQueryFn.data, searchQueryFn.isInitialLoading]);
const clearSearch = () => { const clearSearch = () => {
setPageNumber(1); setPageNumber(1);
@ -98,7 +95,9 @@ export default function Nav({ navVisible, setNavVisible }) {
setPageNumber(pages); setPageNumber(pages);
} else { } else {
if (!isSearching) { if (!isSearching) {
conversations = conversations.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); conversations = conversations.sort(
(a, b) => new Date(b.createdAt) - new Date(a.createdAt)
);
} }
setConversations(conversations); setConversations(conversations);
setPages(pages); setPages(pages);
@ -119,7 +118,6 @@ export default function Nav({ navVisible, setNavVisible }) {
} }
}; };
const toggleNavVisible = () => { const toggleNavVisible = () => {
setNavVisible(prev => !prev); setNavVisible(prev => !prev);
}; };
@ -142,8 +140,8 @@ export default function Nav({ navVisible, setNavVisible }) {
} }
> >
<div className="flex h-full min-h-0 flex-col "> <div className="flex h-full min-h-0 flex-col ">
<div className="scrollbar-trigger flex h-full w-full flex-1 items-start border-white/20 relative"> <div className="scrollbar-trigger relative flex h-full w-full flex-1 items-start border-white/20">
<nav className="flex h-full flex-1 flex-col space-y-1 p-2 relative"> <nav className="relative flex h-full flex-1 flex-col space-y-1 p-2">
<NewChat /> <NewChat />
<div <div
className={`flex-1 flex-col overflow-y-auto ${ className={`flex-1 flex-col overflow-y-auto ${
@ -171,10 +169,7 @@ export default function Nav({ navVisible, setNavVisible }) {
/> />
</div> </div>
</div> </div>
<NavLinks <NavLinks clearSearch={clearSearch} isSearchEnabled={isSearchEnabled} />
clearSearch={clearSearch}
isSearchEnabled={isSearchEnabled}
/>
</nav> </nav>
</div> </div>
</div> </div>
@ -196,25 +191,12 @@ export default function Nav({ navVisible, setNavVisible }) {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="3" y1="6" x2="15" y2="18" />
x1="3" <line x1="3" y1="18" x2="15" y2="6" />
y1="6"
x2="15"
y2="18"
/>
<line
x1="3"
y1="18"
x2="15"
y2="6"
/>
</svg> </svg>
</button> </button>
</div> </div>
<div <div className={'nav-mask' + (navVisible ? ' active' : '')} onClick={toggleNavVisible}></div>
className={'nav-mask' + (navVisible ? ' active' : '')}
onClick={toggleNavVisible}
></div>
</> </>
); );
} }

View file

@ -2,12 +2,7 @@ import React from 'react';
export default function BingChatIcon() { export default function BingChatIcon() {
return ( return (
<svg <svg xmlns="http://www.w3.org/2000/svg" width="41" height="41" fill="none">
xmlns="http://www.w3.org/2000/svg"
width="41"
height="41"
fill="none"
>
<path <path
fill="#174AE4" fill="#174AE4"
d="M8 0a8 8 0 1 1-3.613 15.14l-.121-.065-3.645.91a.5.5 0 0 1-.62-.441v-.082l.014-.083.91-3.644-.063-.12a7.95 7.95 0 0 1-.83-2.887l-.025-.382L0 8a8 8 0 0 1 8-8Zm.5 9h-3l-.09.008a.5.5 0 0 0 0 .984L5.5 10h3l.09-.008a.5.5 0 0 0 0-.984L8.5 9Zm2-3h-5l-.09.008a.5.5 0 0 0 0 .984L5.5 7h5l.09-.008a.5.5 0 0 0 0-.984L10.5 6Z" d="M8 0a8 8 0 1 1-3.613 15.14l-.121-.065-3.645.91a.5.5 0 0 1-.62-.441v-.082l.014-.083.91-3.644-.063-.12a7.95 7.95 0 0 1-.83-2.887l-.025-.382L0 8a8 8 0 0 1 8-8Zm.5 9h-3l-.09.008a.5.5 0 0 0 0 .984L5.5 10h3l.09-.008a.5.5 0 0 0 0-.984L8.5 9Zm2-3h-5l-.09.008a.5.5 0 0 0 0 .984L5.5 7h5l.09-.008a.5.5 0 0 0 0-.984L10.5 6Z"

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
export default function BingIcon({ size=25 }) { export default function BingIcon({ size = 25 }) {
return ( return (
<svg <svg
width={size} width={size}
@ -42,39 +42,15 @@ export default function BingIcon({ size=25 }) {
y2="38.1597" y2="38.1597"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#37BDFF"/> <stop stopColor="#37BDFF" />
<stop <stop offset="0.1832" stopColor="#33BFFD" />
offset="0.1832" <stop offset="0.3576" stopColor="#28C5F5" />
stopColor="#33BFFD" <stop offset="0.528" stopColor="#15D0E9" />
/> <stop offset="0.5468" stopColor="#12D1E7" />
<stop <stop offset="0.5903" stopColor="#1CD2E5" />
offset="0.3576" <stop offset="0.7679" stopColor="#42D8DC" />
stopColor="#28C5F5" <stop offset="0.9107" stopColor="#59DBD6" />
/> <stop offset="1" stopColor="#62DCD4" />
<stop
offset="0.528"
stopColor="#15D0E9"
/>
<stop
offset="0.5468"
stopColor="#12D1E7"
/>
<stop
offset="0.5903"
stopColor="#1CD2E5"
/>
<stop
offset="0.7679"
stopColor="#42D8DC"
/>
<stop
offset="0.9107"
stopColor="#59DBD6"
/>
<stop
offset="1"
stopColor="#62DCD4"
/>
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint1_linear_36_2239" id="paint1_linear_36_2239"
@ -84,39 +60,15 @@ export default function BingIcon({ size=25 }) {
y2="45.3798" y2="45.3798"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#39D2FF"/> <stop stopColor="#39D2FF" />
<stop <stop offset="0.1501" stopColor="#38CEFE" />
offset="0.1501" <stop offset="0.2931" stopColor="#35C3FA" />
stopColor="#38CEFE" <stop offset="0.4327" stopColor="#2FB0F3" />
/> <stop offset="0.5468" stopColor="#299AEB" />
<stop <stop offset="0.5827" stopColor="#2692EC" />
offset="0.2931" <stop offset="0.7635" stopColor="#1A6CF1" />
stopColor="#35C3FA" <stop offset="0.909" stopColor="#1355F4" />
/> <stop offset="1" stopColor="#104CF5" />
<stop
offset="0.4327"
stopColor="#2FB0F3"
/>
<stop
offset="0.5468"
stopColor="#299AEB"
/>
<stop
offset="0.5827"
stopColor="#2692EC"
/>
<stop
offset="0.7635"
stopColor="#1A6CF1"
/>
<stop
offset="0.909"
stopColor="#1355F4"
/>
<stop
offset="1"
stopColor="#104CF5"
/>
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint2_linear_36_2239" id="paint2_linear_36_2239"
@ -126,23 +78,11 @@ export default function BingIcon({ size=25 }) {
y2="1.52914" y2="1.52914"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="#1B48EF"/> <stop stopColor="#1B48EF" />
<stop <stop offset="0.1221" stopColor="#1C51F0" />
offset="0.1221" <stop offset="0.3212" stopColor="#1E69F5" />
stopColor="#1C51F0" <stop offset="0.5676" stopColor="#2190FB" />
/> <stop offset="1" stopColor="#26B8F4" />
<stop
offset="0.3212"
stopColor="#1E69F5"
/>
<stop
offset="0.5676"
stopColor="#2190FB"
/>
<stop
offset="1"
stopColor="#26B8F4"
/>
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint3_linear_36_2239" id="paint3_linear_36_2239"
@ -152,48 +92,18 @@ export default function BingIcon({ size=25 }) {
y2="32.6475" y2="32.6475"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="white"/> <stop stopColor="white" />
<stop <stop offset="0.3726" stopColor="#FDFDFD" />
offset="0.3726" <stop offset="0.5069" stopColor="#F6F6F6" />
stopColor="#FDFDFD" <stop offset="0.6026" stopColor="#EBEBEB" />
/> <stop offset="0.68" stopColor="#DADADA" />
<stop <stop offset="0.7463" stopColor="#C4C4C4" />
offset="0.5069" <stop offset="0.805" stopColor="#A8A8A8" />
stopColor="#F6F6F6" <stop offset="0.8581" stopColor="#888888" />
/> <stop offset="0.9069" stopColor="#626262" />
<stop <stop offset="0.9523" stopColor="#373737" />
offset="0.6026" <stop offset="0.9926" stopColor="#090909" />
stopColor="#EBEBEB" <stop offset="1" />
/>
<stop
offset="0.68"
stopColor="#DADADA"
/>
<stop
offset="0.7463"
stopColor="#C4C4C4"
/>
<stop
offset="0.805"
stopColor="#A8A8A8"
/>
<stop
offset="0.8581"
stopColor="#888888"
/>
<stop
offset="0.9069"
stopColor="#626262"
/>
<stop
offset="0.9523"
stopColor="#373737"
/>
<stop
offset="0.9926"
stopColor="#090909"
/>
<stop offset="1"/>
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint4_linear_36_2239" id="paint4_linear_36_2239"
@ -203,56 +113,21 @@ export default function BingIcon({ size=25 }) {
y2="47.9822" y2="47.9822"
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
> >
<stop stopColor="white"/> <stop stopColor="white" />
<stop <stop offset="0.3726" stopColor="#FDFDFD" />
offset="0.3726" <stop offset="0.5069" stopColor="#F6F6F6" />
stopColor="#FDFDFD" <stop offset="0.6026" stopColor="#EBEBEB" />
/> <stop offset="0.68" stopColor="#DADADA" />
<stop <stop offset="0.7463" stopColor="#C4C4C4" />
offset="0.5069" <stop offset="0.805" stopColor="#A8A8A8" />
stopColor="#F6F6F6" <stop offset="0.8581" stopColor="#888888" />
/> <stop offset="0.9069" stopColor="#626262" />
<stop <stop offset="0.9523" stopColor="#373737" />
offset="0.6026" <stop offset="0.9926" stopColor="#090909" />
stopColor="#EBEBEB" <stop offset="1" />
/>
<stop
offset="0.68"
stopColor="#DADADA"
/>
<stop
offset="0.7463"
stopColor="#C4C4C4"
/>
<stop
offset="0.805"
stopColor="#A8A8A8"
/>
<stop
offset="0.8581"
stopColor="#888888"
/>
<stop
offset="0.9069"
stopColor="#626262"
/>
<stop
offset="0.9523"
stopColor="#373737"
/>
<stop
offset="0.9926"
stopColor="#090909"
/>
<stop offset="1"/>
</linearGradient> </linearGradient>
<clipPath id="clip0_36_2239"> <clipPath id="clip0_36_2239">
<rect <rect width="37" height="56" fill="white" transform="translate(10)"></rect>
width="37"
height="56"
fill="white"
transform="translate(10)"
></rect>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>

View file

@ -15,18 +15,8 @@ export default function CautionIcon() {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /> <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line <line x1="12" y1="9" x2="12" y2="13" />
x1="12" <line x1="12" y1="17" x2="12.01" y2="17" />
y1="9"
x2="12"
y2="13"
/>
<line
x1="12"
y1="17"
x2="12.01"
y2="17"
/>
</svg> </svg>
); );
} }

View file

@ -15,14 +15,7 @@ export default function Clipboard() {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path> <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
<rect <rect x="8" y="2" width="8" height="4" rx="1" ry="1" />
x="8"
y="2"
width="8"
height="4"
rx="1"
ry="1"
/>
</svg> </svg>
); );
} }

View file

@ -14,18 +14,8 @@ export default function CrossIcon() {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="18" y1="6" x2="6" y2="18" />
x1="18" <line x1="6" y1="6" x2="18" y2="18" />
y1="6"
x2="6"
y2="18"
/>
<line
x1="6"
y1="6"
x2="18"
y2="18"
/>
</svg> </svg>
); );
} }

View file

@ -14,21 +14,9 @@ export default function DotsIcon() {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<circle <circle cx="12" cy="12" r="1" />
cx="12" <circle cx="19" cy="12" r="1" />
cy="12" <circle cx="5" cy="12" r="1" />
r="1"
/>
<circle
cx="19"
cy="12"
r="1"
/>
<circle
cx="5"
cy="12"
r="1"
/>
</svg> </svg>
); );
} }

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
export default function GPTIcon({ button = false, menu = false, size=25 }) { export default function GPTIcon({ button = false, menu = false, size = 25 }) {
let unit = '41'; let unit = '41';
let height = size; let height = size;
let width = size; let width = size;

View file

@ -14,59 +14,15 @@ export default function LightModeIcon() {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<circle <circle cx="12" cy="12" r="5" />
cx="12" <line x1="12" y1="1" x2="12" y2="3" />
cy="12" <line x1="12" y1="21" x2="12" y2="23" />
r="5" <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
/> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line <line x1="1" y1="12" x2="3" y2="12" />
x1="12" <line x1="21" y1="12" x2="23" y2="12" />
y1="1" <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
x2="12" <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
y2="3"
/>
<line
x1="12"
y1="21"
x2="12"
y2="23"
/>
<line
x1="4.22"
y1="4.22"
x2="5.64"
y2="5.64"
/>
<line
x1="18.36"
y1="18.36"
x2="19.78"
y2="19.78"
/>
<line
x1="1"
y1="12"
x2="3"
y2="12"
/>
<line
x1="21"
y1="12"
x2="23"
y2="12"
/>
<line
x1="4.22"
y1="19.78"
x2="5.64"
y2="18.36"
/>
<line
x1="18.36"
y1="5.64"
x2="19.78"
y2="4.22"
/>
</svg> </svg>
); );
} }

View file

@ -16,12 +16,7 @@ export default function LogOutIcon() {
> >
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /> <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" /> <polyline points="16 17 21 12 16 7" />
<line <line x1="21" y1="12" x2="9" y2="12" />
x1="21"
y1="12"
x2="9"
y2="12"
/>
</svg> </svg>
); );
} }

View file

@ -14,54 +14,14 @@ export default function Spinner() {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<line <line x1="12" y1="2" x2="12" y2="6" />
x1="12" <line x1="12" y1="18" x2="12" y2="22" />
y1="2" <line x1="4.93" y1="4.93" x2="7.76" y2="7.76" />
x2="12" <line x1="16.24" y1="16.24" x2="19.07" y2="19.07" />
y2="6" <line x1="2" y1="12" x2="6" y2="12" />
/> <line x1="18" y1="12" x2="22" y2="12" />
<line <line x1="4.93" y1="19.07" x2="7.76" y2="16.24" />
x1="12" <line x1="16.24" y1="7.76" x2="19.07" y2="4.93" />
y1="18"
x2="12"
y2="22"
/>
<line
x1="4.93"
y1="4.93"
x2="7.76"
y2="7.76"
/>
<line
x1="16.24"
y1="16.24"
x2="19.07"
y2="19.07"
/>
<line
x1="2"
y1="12"
x2="6"
y2="12"
/>
<line
x1="18"
y1="12"
x2="22"
y2="12"
/>
<line
x1="4.93"
y1="19.07"
x2="7.76"
y2="16.24"
/>
<line
x1="16.24"
y1="7.76"
x2="19.07"
y2="4.93"
/>
</svg> </svg>
); );
} }

View file

@ -14,14 +14,7 @@ export default function StopGeneratingIcon() {
width="1em" width="1em"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<rect <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
></rect>
</svg> </svg>
); );
} }

Some files were not shown because too many files have changed in this diff Show more