mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
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:
parent
8d75b25104
commit
7fdc862042
157 changed files with 4836 additions and 2403 deletions
113
.eslintrc.js
Normal file
113
.eslintrc.js
Normal 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
5
.husky/pre-commit
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged
|
||||||
|
|
19
.prettierrc.js
Normal file
19
.prettierrc.js
Normal 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,
|
||||||
|
};
|
|
@ -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'
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -7,8 +7,8 @@ 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');
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = getCitations;
|
module.exports = getCitations;
|
||||||
|
|
|
@ -2,11 +2,11 @@ function mergeSort(arr, compareFn) {
|
||||||
if (arr.length <= 1) {
|
if (arr.length <= 1) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mid = Math.floor(arr.length / 2);
|
const mid = Math.floor(arr.length / 2);
|
||||||
const leftArr = arr.slice(0, mid);
|
const leftArr = arr.slice(0, mid);
|
||||||
const rightArr = arr.slice(mid);
|
const rightArr = arr.slice(mid);
|
||||||
|
|
||||||
return merge(mergeSort(leftArr, compareFn), mergeSort(rightArr, compareFn), compareFn);
|
return merge(mergeSort(leftArr, compareFn), mergeSort(rightArr, compareFn), compareFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ function merge(leftArr, rightArr, compareFn) {
|
||||||
const result = [];
|
const result = [];
|
||||||
let leftIndex = 0;
|
let leftIndex = 0;
|
||||||
let rightIndex = 0;
|
let rightIndex = 0;
|
||||||
|
|
||||||
while (leftIndex < leftArr.length && rightIndex < rightArr.length) {
|
while (leftIndex < leftArr.length && rightIndex < rightArr.length) {
|
||||||
if (compareFn(leftArr[leftIndex], rightArr[rightIndex]) < 0) {
|
if (compareFn(leftArr[leftIndex], rightArr[rightIndex]) < 0) {
|
||||||
result.push(leftArr[leftIndex++]);
|
result.push(leftArr[leftIndex++]);
|
||||||
|
@ -22,8 +22,8 @@ function merge(leftArr, rightArr, compareFn) {
|
||||||
result.push(rightArr[rightIndex++]);
|
result.push(rightArr[rightIndex++]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.concat(leftArr.slice(leftIndex)).concat(rightArr.slice(rightIndex));
|
return result.concat(leftArr.slice(leftIndex)).concat(rightArr.slice(rightIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = mergeSort;
|
module.exports = mergeSort;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ const Message = require('./schema/messageSchema');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Message,
|
Message,
|
||||||
|
|
||||||
async saveMessage({
|
async saveMessage({
|
||||||
messageId,
|
messageId,
|
||||||
newMessageId,
|
newMessageId,
|
||||||
|
@ -32,7 +32,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{ upsert: true, new: true }
|
{ upsert: true, new: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messageId,
|
messageId,
|
||||||
conversationId,
|
conversationId,
|
||||||
|
@ -41,13 +41,12 @@ 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.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteMessagesSince({ messageId, conversationId }) {
|
async deleteMessagesSince({ messageId, conversationId }) {
|
||||||
try {
|
try {
|
||||||
const message = await Message.findOne({ messageId }).exec();
|
const message = await Message.findOne({ messageId }).exec();
|
||||||
|
@ -57,27 +56,24 @@ 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.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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.');
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
const promptSchema = mongoose.Schema({
|
const promptSchema = mongoose.Schema(
|
||||||
title: {
|
{
|
||||||
type: String,
|
title: {
|
||||||
required: true
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
},
|
},
|
||||||
prompt: {
|
{ timestamps: true }
|
||||||
type: String,
|
);
|
||||||
required: true
|
|
||||||
},
|
|
||||||
category: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
}, { 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' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
@ -198,8 +195,8 @@ module.exports = function mongoMeili(schema, options) {
|
||||||
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
|
||||||
console.log('Syncing convos...');
|
console.log('Syncing convos...');
|
||||||
mongoose.model('Conversation').syncWithMeili();
|
mongoose.model('Conversation').syncWithMeili();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
|
||||||
console.log('Syncing messages...');
|
console.log('Syncing messages...');
|
||||||
mongoose.model('Message').syncWithMeili();
|
mongoose.model('Message').syncWithMeili();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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}`);
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -36,13 +36,14 @@ 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
|
||||||
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }
|
? { userProvide: process.env.BINGAI_TOKEN == 'user_provided' }
|
||||||
: false;
|
: false;
|
||||||
|
@ -56,4 +57,4 @@ router.get('/', async function (req, res) {
|
||||||
res.send(JSON.stringify({ azureOpenAI, openAI, google, bingAI, chatGPTBrowser }));
|
res.send(JSON.stringify({ azureOpenAI, openAI, google, bingAI, chatGPTBrowser }));
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { router, getOpenAIModels, getChatGPTBrowserModels };
|
module.exports = { router, getOpenAIModels, getChatGPTBrowserModels };
|
||||||
|
|
|
@ -19,5 +19,5 @@ module.exports = {
|
||||||
auth,
|
auth,
|
||||||
oauth,
|
oauth,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
endpoints,
|
endpoints
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,4 +61,4 @@ router.get(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
@ -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)
|
||||||
const { _formatted, ...rest } = message;
|
).hits.map((message) => {
|
||||||
return { ...rest, searchResult: true, text: _formatted.text };
|
const { _formatted, ...rest } = message;
|
||||||
}
|
return { ...rest, searchResult: true, text: _formatted.text };
|
||||||
);
|
});
|
||||||
res.send(messages);
|
res.send(messages);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const passport = require('passport');
|
const passport = require('passport');
|
||||||
const FacebookStrategy = require('passport-facebook').Strategy;
|
const FacebookStrategy = require('passport-facebook').Strategy;
|
||||||
const User = require('../models/User');
|
const User = require('../models/User');
|
||||||
|
|
||||||
const serverUrl =
|
const serverUrl =
|
||||||
process.env.NODE_ENV === 'production' ? process.env.SERVER_URL_PROD : process.env.SERVER_URL_DEV;
|
process.env.NODE_ENV === 'production' ? process.env.SERVER_URL_PROD : process.env.SERVER_URL_DEV;
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
|
|
@ -65,4 +65,3 @@ function log({ title, parameters }) {
|
||||||
DebugControl.log.parameters(parameters);
|
DebugControl.log.parameters(parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,4 +21,4 @@ const registerSchema = Joi.object().keys({
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loginSchema,
|
loginSchema,
|
||||||
registerSchema
|
registerSchema
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,14 +12,16 @@ 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 = [
|
||||||
/api[-_]?key/i,
|
// Array of regular expressions for redacting patterns
|
||||||
|
/api[-_]?key/i,
|
||||||
/password/i,
|
/password/i,
|
||||||
/token/i,
|
/token/i,
|
||||||
/secret/i,
|
/secret/i,
|
||||||
|
@ -30,7 +33,7 @@ const redactPatterns = [ // Array of regular expressions for redacting patterns
|
||||||
/authorization[-_]?acr[-_]?values/i,
|
/authorization[-_]?acr[-_]?values/i,
|
||||||
/authorization[-_]?response[-_]?mode/i,
|
/authorization[-_]?response[-_]?mode/i,
|
||||||
/authorization[-_]?nonce/i
|
/authorization[-_]?nonce/i
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Example of redacting sensitive data from object class instances
|
// Example of redacting sensitive data from object class instances
|
||||||
|
@ -49,21 +52,19 @@ const redactPatterns = [ // Array of regular expressions for redacting patterns
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const levels = {
|
const levels = {
|
||||||
TRACE: 10,
|
TRACE: 10,
|
||||||
DEBUG: 20,
|
DEBUG: 20,
|
||||||
INFO: 30,
|
INFO: 30,
|
||||||
WARN: 40,
|
WARN: 40,
|
||||||
ERROR: 50,
|
ERROR: 50,
|
||||||
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 = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -51,4 +51,4 @@ sendEmail(
|
||||||
);
|
);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = sendEmail;
|
module.exports = sendEmail;
|
||||||
|
|
|
@ -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",
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
@ -139,28 +134,47 @@ function Login() {
|
||||||
</button>
|
</button>
|
||||||
</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>
|
||||||
|
|
||||||
|
|
|
@ -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,29 +69,29 @@ 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{errors.name && (
|
{errors.name && (
|
||||||
<span role="alert" className="mt-1 text-sm text-red-600">
|
<span role="alert" className="mt-1 text-sm text-red-600">
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
|
@ -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>
|
||||||
|
@ -268,29 +263,48 @@ function Registration() {
|
||||||
</button>
|
</button>
|
||||||
</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
|
||||||
|
|
|
@ -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
|
||||||
|
@ -59,33 +62,33 @@ function RequestPasswordReset() {
|
||||||
>
|
>
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
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>
|
||||||
|
@ -112,4 +115,4 @@ function RequestPasswordReset() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RequestPasswordReset;
|
export default RequestPasswordReset;
|
||||||
|
|
|
@ -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
|
||||||
|
@ -70,107 +69,113 @@ function ResetPassword() {
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
>
|
>
|
||||||
<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"
|
||||||
<input
|
id="token"
|
||||||
type="password"
|
value={params.get('token')}
|
||||||
id="password"
|
{...register('token', { required: 'Unable to process: No valid reset token' })}
|
||||||
autoComplete="current-password"
|
/>
|
||||||
aria-label="Password"
|
<input
|
||||||
{...register("password", {
|
type="hidden"
|
||||||
required: "Password is required",
|
id="userId"
|
||||||
minLength: {
|
value={params.get('userId')}
|
||||||
value: 8,
|
{...register('userId', { required: 'Unable to process: No valid user id' })}
|
||||||
message: "Password must be at least 8 characters",
|
/>
|
||||||
},
|
<input
|
||||||
maxLength: {
|
type="password"
|
||||||
value: 40,
|
id="password"
|
||||||
message: "Password must be less than 40 characters",
|
autoComplete="current-password"
|
||||||
},
|
aria-label="Password"
|
||||||
})}
|
{...register('password', {
|
||||||
aria-invalid={!!errors.password}
|
required: 'Password is required',
|
||||||
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"
|
minLength: {
|
||||||
placeholder=" "
|
value: 8,
|
||||||
></input>
|
message: 'Password must be at least 8 characters'
|
||||||
<label
|
},
|
||||||
htmlFor="password"
|
maxLength: {
|
||||||
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"
|
value: 40,
|
||||||
>
|
message: 'Password must be less than 40 characters'
|
||||||
Password
|
}
|
||||||
</label>
|
})}
|
||||||
</div>
|
aria-invalid={!!errors.password}
|
||||||
|
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=" "
|
||||||
|
></input>
|
||||||
|
<label
|
||||||
|
htmlFor="password"
|
||||||
|
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
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{errors.password && (
|
{errors.password && (
|
||||||
<span role="alert" className="mt-1 text-sm text-red-600">
|
<span role="alert" className="mt-1 text-sm text-red-600">
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
{errors.password.message}
|
{errors.password.message}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<div className="mb-2">
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="confirm_password"
|
|
||||||
aria-label="Confirm Password"
|
|
||||||
// uncomment to prevent pasting in confirm field
|
|
||||||
onPaste={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}}
|
|
||||||
{...register("confirm_password", {
|
|
||||||
validate: (value) =>
|
|
||||||
value === password || "Passwords do not match",
|
|
||||||
})}
|
|
||||||
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"
|
|
||||||
placeholder=" "
|
|
||||||
></input>
|
|
||||||
<label
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
Confirm Password
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{errors.confirm_password && (
|
<div className="mb-2">
|
||||||
<span role="alert" className="mt-1 text-sm text-red-600">
|
<div className="relative">
|
||||||
{/* @ts-ignore */}
|
<input
|
||||||
{errors.confirm_password.message}
|
type="password"
|
||||||
</span>
|
id="confirm_password"
|
||||||
)}
|
aria-label="Confirm Password"
|
||||||
{errors.token && (
|
// uncomment to prevent pasting in confirm field
|
||||||
<span role="alert" className="mt-1 text-sm text-red-600">
|
onPaste={(e) => {
|
||||||
{/* @ts-ignore */}
|
e.preventDefault();
|
||||||
{errors.token.message}
|
return false;
|
||||||
</span>
|
}}
|
||||||
)}
|
{...register('confirm_password', {
|
||||||
{errors.userId && (
|
validate: value => value === password || 'Passwords do not match'
|
||||||
<span role="alert" className="mt-1 text-sm text-red-600">
|
})}
|
||||||
{/* @ts-ignore */}
|
aria-invalid={!!errors.confirm_password}
|
||||||
{errors.userId.message}
|
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"
|
||||||
</span>
|
placeholder=" "
|
||||||
)}
|
></input>
|
||||||
</div>
|
<label
|
||||||
<div className="mt-6">
|
htmlFor="confirm_password"
|
||||||
<button
|
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"
|
||||||
disabled={
|
>
|
||||||
!!errors.password ||
|
Confirm Password
|
||||||
!!errors.confirm_password
|
</label>
|
||||||
}
|
</div>
|
||||||
type="submit"
|
{errors.confirm_password && (
|
||||||
aria-label="Submit registration"
|
<span role="alert" className="mt-1 text-sm text-red-600">
|
||||||
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"
|
{/* @ts-ignore */}
|
||||||
>
|
{errors.confirm_password.message}
|
||||||
Continue
|
</span>
|
||||||
</button>
|
)}
|
||||||
</div>
|
{errors.token && (
|
||||||
</form>
|
<span role="alert" className="mt-1 text-sm text-red-600">
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{errors.token.message}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{errors.userId && (
|
||||||
|
<span role="alert" className="mt-1 text-sm text-red-600">
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
{errors.userId.message}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mt-6">
|
||||||
|
<button
|
||||||
|
disabled={!!errors.password || !!errors.confirm_password}
|
||||||
|
type="submit"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ResetPassword;
|
export default ResetPassword;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export { default as Login } from './Login';
|
export { default as Login } from './Login';
|
||||||
export { default as Registration } from './Registration';
|
export { default as Registration } from './Registration';
|
||||||
export { default as RequestPasswordReset } from './RequestPasswordReset';
|
export { default as RequestPasswordReset } from './RequestPasswordReset';
|
||||||
export { default as ResetPassword } from './ResetPassword';
|
export { default as ResetPassword } from './ResetPassword';
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -61,7 +61,7 @@ export default function Conversation({ conversation, retainView }) {
|
||||||
updateConvoMutation.mutate({ conversationId, title: titleInput });
|
updateConvoMutation.mutate({ conversationId, title: titleInput });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (updateConvoMutation.isSuccess) {
|
if (updateConvoMutation.isSuccess) {
|
||||||
refreshConversations();
|
refreshConversations();
|
||||||
if (conversationId == currentConversation?.conversationId) {
|
if (conversationId == currentConversation?.conversationId) {
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,26 +14,22 @@ 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();
|
||||||
retainView();
|
retainView();
|
||||||
}
|
}
|
||||||
}, [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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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={
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -21,33 +21,32 @@ function Settings(props) {
|
||||||
const debouncedContext = useDebounce(context, 250);
|
const debouncedContext = useDebounce(context, 250);
|
||||||
const updateTokenCountMutation = useUpdateTokenCountMutation();
|
const updateTokenCountMutation = useUpdateTokenCountMutation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!debouncedContext || debouncedContext.trim() === '') {
|
if (!debouncedContext || debouncedContext.trim() === '') {
|
||||||
setTokenCount(0);
|
setTokenCount(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTextChange = context => {
|
const handleTextChange = (context) => {
|
||||||
updateTokenCountMutation.mutate({ text: context }, {
|
updateTokenCountMutation.mutate(
|
||||||
onSuccess: data => {
|
{ text: context },
|
||||||
setTokenCount(data.count);
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
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">
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 => ({
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => ({
|
||||||
|
|
|
@ -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);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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');
|
||||||
|
@ -23,7 +30,7 @@ const FileUpload = ({ onFileSelected, successText = null, invalidText = null, va
|
||||||
setStatus('success');
|
setStatus('success');
|
||||||
setStatusColor('text-green-500 dark:text-green-500');
|
setStatusColor('text-green-500 dark:text-green-500');
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileSelected(jsonData);
|
onFileSelected(jsonData);
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
|
@ -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=""
|
||||||
|
|
|
@ -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);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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 => ({
|
||||||
|
|
|
@ -8,9 +8,9 @@ export default function RowButton({ onClick, children, text, className }) {
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<span className="hidden md:block">{text}</span>
|
<span className="hidden md:block">{text}</span>
|
||||||
{/* <RegenerateIcon />
|
{/* <RegenerateIcon />
|
||||||
<span className="hidden md:block">Regenerate response</span> */}
|
<span className="hidden md:block">Regenerate response</span> */}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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.');
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,18 +42,18 @@ export default function MultiMessage({
|
||||||
<>
|
<>
|
||||||
{messagesTree
|
{messagesTree
|
||||||
? messagesTree.map(message => (
|
? messagesTree.map(message => (
|
||||||
<Message
|
<Message
|
||||||
key={message.messageId}
|
key={message.messageId}
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
message={message}
|
message={message}
|
||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
currentEditId={currentEditId}
|
currentEditId={currentEditId}
|
||||||
setCurrentEditId={null}
|
setCurrentEditId={null}
|
||||||
siblingIdx={1}
|
siblingIdx={1}
|
||||||
siblingCount={1}
|
siblingCount={1}
|
||||||
setSiblingIdx={null}
|
setSiblingIdx={null}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ? (
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -21,8 +21,8 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
const [pageNumber, setPageNumber] = useState(1);
|
||||||
// total pages
|
// total pages
|
||||||
const [pages, setPages] = useState(1);
|
const [pages, setPages] = useState(1);
|
||||||
|
|
||||||
// data provider
|
// data provider
|
||||||
const getConversationsQuery = useGetConversationsQuery(pageNumber, { enabled: isAuthenticated });
|
const getConversationsQuery = useGetConversationsQuery(pageNumber, { enabled: isAuthenticated });
|
||||||
|
|
||||||
// search
|
// search
|
||||||
|
@ -41,11 +41,9 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
const [isFetching, setIsFetching] = useState(false);
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
|
||||||
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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,11 @@ 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"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue