feat: claude integration (#552)

* feat: bare bones implementation of claude client (WIP)

* feat: client implementation of Claude (WIP)

* fix: add claude to store

* feat: bare bones implementation of claude client (WIP)

* switch eventsource

* Try new method of calling claude with anthropic sdk

* (WIP) Finish initial claude client implementation and api

* debugging update

* fix(ClaudeClient.js): fix prompt prefixes for HUMAN_PROMPT and AI_PROMPT
fix(ClaudeClient.js): refactor buildMessages logic for correct handling of messages
refactor(ClaudeClient.js): refactor buildPrompt method to buildMessages for use in BaseClient sendMessage method
refactor(ClaudeClient.js): refactor getCompletion method to sendCompletion for use in BaseClient sendMessage method
refactor(ClaudeClient.js): omit getMessageMapMethod method for future refactoring
refactor(ClaudeClient.js): remove unused sendMessage method to prefer BaseClient message
fix(askClaude.js): error in getIds method was causing a frontend crash, userMessage was not defined
fix(askClaude.js): import abortMessage function from utils module
feat(askClaude.js): add /abort route to handle message abort requests
feat(askClaude.js): create abortControllers map to store abort controllers
feat(askClaude.js): implement abortAsk function to handle message abort logic
feat(askClaude.js): add onStart callback to handle message start logic
feat(HoverButtons.jsx): add 'claude' as a supported endpoint for branching

* fix(ClaudeClient.js): update defaultPrefix and promptPrefix messages

includes 'Remember your instructions' as Claude is trained to recognize labels preceding colons as participants of a conversation

* Change name from claude to anthropic

* add settings to handleSubmit and models to endpoints

* Implement Claude settings

* use svg for anthropic icon

* Implement abort

* Implement reverse proxy

* remove png icons

* replace web browser plugin

* remove default prefix

* fix styling of claude icon

* fix console error from svg properties

* remove single quote requirement from eslintrc

* fix(AnthropicClient.js): fix labels for HUMAN_PROMPT and AI_PROMPT
feat(AnthropicClient.js): add support for custom userLabel and modelLabel options
feat(AnthropicClient.js): add user_id metadata to requestOptions in getCompletion method
feat(anthropic, AnthropicClient.js): add debug logging

* refactor(AnthropicClient.js): change promptSuffix variable declaration from let to const

* fix(EndpointOptionsDialog.jsx): remove unnecessary code that changes endpointName from 'anthropic' to 'Claude'
fix(utils/index.jsx): fix alternateName value for 'anthropic' from 'Claude' to 'Anthropic'

* fix(AnthropicIcon): fix sizing/rendering/name of anthropic icon

* fix(AnthropicClient.js): change maxContextTokens default value to 99999
fix(AnthropicClient.js): change maxResponseTokens default value to 1500
fix(AnthropicClient.js): remove unnecessary code for setting maxContextTokens and maxResponseTokens based on modelOptions
fix(AnthropicClient.js): change max_tokens_to_sample default value to 1500
fix(anthropic.js): pass endpointOption.token to AnthropicClient constructor

* Update .env.example

* fix(AnthropicClient.js): remove exceeding message when it puts us over the token limit
fix(AnthropicClient.js): handle case when the first message exceeds the token limit
fix(AnthropicClient.js): throw error when prompt is too long
fix(AnthropicClient.js): adjust max tokens calculation to use maxOutputTokens
fix(anthropic.js): remove console.log statement in ask route

* feat(server/index): increase incoming json payload allowed size

---------

Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
This commit is contained in:
Dan Orlando 2023-07-13 18:35:15 -07:00 committed by GitHub
parent 981d009508
commit 9e931229e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1455 additions and 30 deletions

View file

@ -107,6 +107,16 @@ CHATGPT_MODELS=text-davinci-002-render-sha,gpt-4
# By default it will use the node-chatgpt-api recommended proxy, (it's a third party server)
# CHATGPT_REVERSE_PROXY=<YOUR REVERSE PROXY>
##########################
# Anthropic Endpoint:
##########################
# Access key from https://console.anthropic.com/
# Leave it blank to disable this feature.
# Set to "user_provided" to allow the user to provide their API key from the UI.
# Note that access to claude-1 may potentially become unavailable with the release of claude-2.
ANTHROPIC_API_KEY="user_provided"
ANTHROPIC_MODELS=claude-1,claude-instant-1,claude-2
#############################
# Plugins:
#############################

View file

@ -52,7 +52,6 @@ module.exports = {
'no-restricted-syntax': 'off',
'react/prop-types': ['off'],
'react/display-name': ['off'],
'quotes': ['error', 'single'],
},
overrides: [
{

View file

@ -36,7 +36,7 @@ https://github.com/danny-avila/LibreChat/assets/110412045/c1eb0c0f-41f6-4335-b98
# Features
- Response streaming identical to ChatGPT through server-sent events
- UI from original ChatGPT, including Dark mode
- AI model selection (through 5 endpoints: OpenAI API, BingAI, ChatGPT Browser, PaLM2, Plugins)
- AI model selection (through 6 endpoints: OpenAI API, BingAI, ChatGPT Browser, PaLM2, Claude, Plugins)
- Create, Save, & Share custom presets - [More info on prompt presets here](https://github.com/danny-avila/LibreChat/releases/tag/v0.3.0)
- Edit and Resubmit messages with conversation branching
- Search all messages/conversations - [More info here](https://github.com/danny-avila/LibreChat/releases/tag/v0.1.0)

View file

@ -0,0 +1,319 @@
const Keyv = require('keyv');
// const { Agent, ProxyAgent } = require('undici');
const BaseClient = require('./BaseClient');
const {
encoding_for_model: encodingForModel,
get_encoding: getEncoding
} = require('@dqbd/tiktoken');
const Anthropic = require('@anthropic-ai/sdk');
const HUMAN_PROMPT = '\n\nHuman:';
const AI_PROMPT = '\n\nAssistant:';
const tokenizersCache = {};
class AnthropicClient extends BaseClient {
constructor(apiKey, options = {}, cacheOptions = {}) {
super(apiKey, options, cacheOptions)
cacheOptions.namespace = cacheOptions.namespace || 'anthropic';
this.conversationsCache = new Keyv(cacheOptions);
this.apiKey = apiKey || process.env.ANTHROPIC_API_KEY;
this.sender = 'Anthropic';
this.userLabel = HUMAN_PROMPT;
this.assistantLabel = AI_PROMPT;
this.setOptions(options);
}
setOptions(options) {
if (this.options && !this.options.replaceOptions) {
// nested options aren't spread properly, so we need to do this manually
this.options.modelOptions = {
...this.options.modelOptions,
...options.modelOptions
};
delete options.modelOptions;
// now we can merge options
this.options = {
...this.options,
...options,
};
} else {
this.options = options;
}
const modelOptions = this.options.modelOptions || {};
this.modelOptions = {
...modelOptions,
// set some good defaults (check for undefined in some cases because they may be 0)
model: modelOptions.model || 'claude-1',
temperature: typeof modelOptions.temperature === 'undefined' ? 0.7 : modelOptions.temperature, // 0 - 1, 0.7 is recommended
topP: typeof modelOptions.topP === 'undefined' ? 0.7 : modelOptions.topP, // 0 - 1, default: 0.7
topK: typeof modelOptions.topK === 'undefined' ? 40 : modelOptions.topK, // 1-40, default: 40
stop: modelOptions.stop // no stop method for now
};
this.maxContextTokens = this.options.maxContextTokens || 99999;
this.maxResponseTokens = this.modelOptions.maxOutputTokens || 1500;
this.maxPromptTokens =
this.options.maxPromptTokens || this.maxContextTokens - this.maxResponseTokens;
if (this.maxPromptTokens + this.maxResponseTokens > this.maxContextTokens) {
throw new Error(
`maxPromptTokens + maxOutputTokens (${this.maxPromptTokens} + ${this.maxResponseTokens} = ${
this.maxPromptTokens + this.maxResponseTokens
}) must be less than or equal to maxContextTokens (${this.maxContextTokens})`
);
}
this.startToken = '||>';
this.endToken = '';
this.gptEncoder = this.constructor.getTokenizer('cl100k_base');
if (!this.modelOptions.stop) {
const stopTokens = [this.startToken];
if (this.endToken && this.endToken !== this.startToken) {
stopTokens.push(this.endToken);
}
stopTokens.push(`${this.userLabel}`);
stopTokens.push('<|diff_marker|>');
this.modelOptions.stop = stopTokens;
}
return this;
}
getClient() {
if(this.options.reverseProxyUrl) {
return new Anthropic({
apiKey: this.apiKey,
baseURL: this.options.reverseProxyUrl
});
}
else {
return new Anthropic({
apiKey: this.apiKey,
});
}
};
async buildMessages(messages, parentMessageId) {
const orderedMessages = this.constructor.getMessagesForConversation(messages, parentMessageId);
if (this.options.debug) {
console.debug('AnthropicClient: orderedMessages', orderedMessages, parentMessageId);
}
const formattedMessages = orderedMessages.map((message) => ({
author: message.isCreatedByUser ? this.userLabel : this.assistantLabel,
content: message?.content ?? message.text
}));
let identityPrefix = '';
if (this.options.userLabel) {
identityPrefix = `\nHuman's name: ${this.options.userLabel}`;
}
if (this.options.modelLabel) {
identityPrefix = `${identityPrefix}\nYou are ${this.options.modelLabel}`;
}
let promptPrefix = (this.options.promptPrefix || '').trim();
if (promptPrefix) {
// If the prompt prefix doesn't end with the end token, add it.
if (!promptPrefix.endsWith(`${this.endToken}`)) {
promptPrefix = `${promptPrefix.trim()}${this.endToken}\n\n`;
}
promptPrefix = `\nContext:\n${promptPrefix}`;
}
if (identityPrefix) {
promptPrefix = `${identityPrefix}${promptPrefix}`;
}
const promptSuffix = `${promptPrefix}${this.assistantLabel}\n`; // Prompt AI to respond.
let currentTokenCount = this.getTokenCount(promptSuffix);
let promptBody = '';
const maxTokenCount = this.maxPromptTokens;
const context = [];
// Iterate backwards through the messages, adding them to the prompt until we reach the max token count.
// Do this within a recursive async function so that it doesn't block the event loop for too long.
// Also, remove the next message when the message that puts us over the token limit is created by the user.
// Otherwise, remove only the exceeding message. This is due to Anthropic's strict payload rule to start with "Human:".
const nextMessage = {
remove: false,
tokenCount: 0,
messageString: '',
};
const buildPromptBody = async () => {
if (currentTokenCount < maxTokenCount && formattedMessages.length > 0) {
const message = formattedMessages.pop();
const isCreatedByUser = message.author === this.userLabel;
const messageString = `${message.author}\n${message.content}${this.endToken}\n`;
let newPromptBody = `${messageString}${promptBody}`;
context.unshift(message);
const tokenCountForMessage = this.getTokenCount(messageString);
const newTokenCount = currentTokenCount + tokenCountForMessage;
if (!isCreatedByUser) {
nextMessage.messageString = messageString;
nextMessage.tokenCount = tokenCountForMessage;
}
if (newTokenCount > maxTokenCount) {
if (!promptBody) {
// This is the first message, so we can't add it. Just throw an error.
throw new Error(`Prompt is too long. Max token count is ${maxTokenCount}, but prompt is ${newTokenCount} tokens long.`);
}
// Otherwise, ths message would put us over the token limit, so don't add it.
// if created by user, remove next message, otherwise remove only this message
if (isCreatedByUser) {
nextMessage.remove = true;
}
return false;
}
promptBody = newPromptBody;
currentTokenCount = newTokenCount;
// wait for next tick to avoid blocking the event loop
await new Promise(resolve => setImmediate(resolve));
return buildPromptBody();
}
return true;
};
await buildPromptBody();
if (nextMessage.remove) {
promptBody = promptBody.replace(nextMessage.messageString, '');
currentTokenCount -= nextMessage.tokenCount;
context.shift();
}
const prompt = `${promptBody}${promptSuffix}`;
// Add 2 tokens for metadata after all messages have been counted.
currentTokenCount += 2;
// Use up to `this.maxContextTokens` tokens (prompt + response), but try to leave `this.maxTokens` tokens for the response.
this.modelOptions.maxOutputTokens = Math.min(this.maxContextTokens - currentTokenCount, this.maxResponseTokens);
return { prompt, context };
}
getCompletion() {
console.log('AnthropicClient doesn\'t use getCompletion (all handled in sendCompletion)');
}
// TODO: implement abortController usage
async sendCompletion(payload, { onProgress, abortController }) {
if (!abortController) {
abortController = new AbortController();
}
const { signal } = abortController;
const modelOptions = { ...this.modelOptions };
if (typeof onProgress === 'function') {
modelOptions.stream = true;
}
const { debug } = this.options;
if (debug) {
console.debug();
console.debug(modelOptions);
console.debug();
}
const client = this.getClient();
const metadata = {
user_id: this.user,
};
let text = '';
const requestOptions = {
prompt: payload,
model: this.modelOptions.model,
stream: this.modelOptions.stream || true,
max_tokens_to_sample: this.modelOptions.maxOutputTokens || 1500,
metadata,
...modelOptions
};
if (this.options.debug) {
console.log('AnthropicClient: requestOptions');
console.dir(requestOptions, { depth: null });
}
const response = await client.completions.create(requestOptions);
signal.addEventListener('abort', () => {
if (this.options.debug) {
console.log('AnthropicClient: message aborted!');
}
response.controller.abort();
});
for await (const completion of response) {
if (this.options.debug) {
// Uncomment to debug message stream
// console.debug(completion);
}
text += completion.completion;
onProgress(completion.completion);
}
signal.removeEventListener('abort', () => {
if (this.options.debug) {
console.log('AnthropicClient: message aborted!');
}
response.controller.abort();
});
return text.trim();
}
// I commented this out because I will need to refactor this for the BaseClient/all clients
// getMessageMapMethod() {
// return ((message) => ({
// author: message.isCreatedByUser ? this.userLabel : this.assistantLabel,
// content: message?.content ?? message.text
// })).bind(this);
// }
getSaveOptions() {
return {
...this.modelOptions
};
}
getBuildMessagesOptions() {
if (this.options.debug) {
console.log('AnthropicClient doesn\'t use getBuildMessagesOptions');
}
}
static getTokenizer(encoding, isModelName = false, extendSpecialTokens = {}) {
if (tokenizersCache[encoding]) {
return tokenizersCache[encoding];
}
let tokenizer;
if (isModelName) {
tokenizer = encodingForModel(encoding, extendSpecialTokens);
} else {
tokenizer = getEncoding(encoding, extendSpecialTokens);
}
tokenizersCache[encoding] = tokenizer;
return tokenizer;
}
getTokenCount(text) {
return this.gptEncoder.encode(text, 'all').length;
}
}
module.exports = AnthropicClient;

View file

@ -370,7 +370,6 @@ class BaseClient {
}
async sendMessage(message, opts = {}) {
console.log('BaseClient: sendMessage', message, opts);
const {
user,
conversationId,
@ -379,6 +378,7 @@ class BaseClient {
userMessage,
} = await this.handleStartMethods(message, opts);
this.user = user;
// It's not necessary to push to currentMessages
// depending on subclass implementation of handling messages
this.currentMessages.push(userMessage);

View file

@ -3,6 +3,7 @@ const OpenAIClient = require('./OpenAIClient');
const PluginsClient = require('./PluginsClient');
const GoogleClient = require('./GoogleClient');
const TextStream = require('./TextStream');
const AnthropicClient = require('./AnthropicClient');
const toolUtils = require('./tools/util');
module.exports = {
@ -11,5 +12,6 @@ module.exports = {
PluginsClient,
GoogleClient,
TextStream,
AnthropicClient,
...toolUtils
};

View file

@ -34,7 +34,7 @@
"name": "Browser",
"pluginKey": "browser",
"description": "Scrape and summarize webpage data",
"icon": "/assets/web-browser.png",
"icon": "/assets/web-browser.svg",
"authConfig": [
{
"authField": "OPENAI_API_KEY",

View file

@ -1,5 +1,5 @@
const conversationPreset = {
// endpoint: [azureOpenAI, openAI, bingAI, chatGPTBrowser]
// endpoint: [azureOpenAI, openAI, bingAI, anthropic, chatGPTBrowser]
endpoint: {
type: String,
default: null,

View file

@ -20,7 +20,9 @@
},
"homepage": "https://github.com/danny-avila/LibreChat#readme",
"dependencies": {
"@anthropic-ai/sdk": "^0.5.0",
"@dqbd/tiktoken": "^1.0.2",
"@fortaine/fetch-event-source": "^3.0.6",
"@keyv/mongo": "^2.1.8",
"@waylaidwanderer/chatgpt-api": "^1.37.0",
"axios": "^1.3.4",

View file

@ -24,8 +24,8 @@ config.validate(); // Validate the config
const app = express();
app.use(errorController);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.json({ limit: '3mb' }));
app.use(express.urlencoded({ extended: true, limit: '3mb' }));
app.use(express.static(path.join(projectPath, 'dist')));
app.use(express.static(path.join(projectPath, 'public')));

View file

@ -0,0 +1,186 @@
const express = require('express');
const router = express.Router();
const crypto = require('crypto');
const { titleConvo, AnthropicClient } = require('../../../app');
const requireJwtAuth = require('../../../middleware/requireJwtAuth');
const { abortMessage } = require('../../../utils');
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
const { handleError, sendMessage, createOnProgress } = require('./handlers');
const abortControllers = new Map();
router.post('/abort', requireJwtAuth, async (req, res) => {
return await abortMessage(req, res, abortControllers);
});
router.post('/', requireJwtAuth, async (req, res) => {
const { endpoint, text, parentMessageId, conversationId: oldConversationId } = req.body;
if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' });
if (endpoint !== 'anthropic') return handleError(res, { text: 'Illegal request' });
const endpointOption = {
promptPrefix: req.body?.promptPrefix ?? null,
modelLabel: req.body?.modelLabel ?? null,
token: req.body?.token ?? null,
modelOptions: {
model: req.body?.model ?? 'claude-1',
temperature: req.body?.temperature ?? 0.7,
maxOutputTokens: req.body?.maxOutputTokens ?? 1024,
topP: req.body?.topP ?? 0.7,
topK: req.body?.topK ?? 40
}
};
const conversationId = oldConversationId || crypto.randomUUID();
return await ask({
text,
endpointOption,
conversationId,
parentMessageId,
req,
res
});
});
const ask = async ({ text, endpointOption, parentMessageId = null, conversationId, req, res }) => {
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
let userMessage;
let userMessageId;
let responseMessageId;
let lastSavedTimestamp = 0;
const { overrideParentMessageId = null } = req.body;
try {
const getIds = (data) => {
userMessage = data.userMessage;
userMessageId = data.userMessage.messageId;
responseMessageId = data.responseMessageId;
if (!conversationId) {
conversationId = data.conversationId;
}
};
const { onProgress: progressCallback, getPartialText } = createOnProgress({
onProgress: ({ text: partialText }) => {
const currentTimestamp = Date.now();
if (currentTimestamp - lastSavedTimestamp > 500) {
lastSavedTimestamp = currentTimestamp;
saveMessage({
messageId: responseMessageId,
sender: 'Anthropic',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: partialText,
unfinished: true,
cancelled: false,
error: false
});
}
}
});
const abortController = new AbortController();
abortController.abortAsk = async function () {
this.abort();
const responseMessage = {
messageId: responseMessageId,
sender: 'Anthropic',
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
text: getPartialText(),
model: endpointOption.modelOptions.model,
unfinished: false,
cancelled: true,
error: false,
};
saveMessage(responseMessage);
return {
title: await getConvoTitle(req.user.id, conversationId),
final: true,
conversation: await getConvo(req.user.id, conversationId),
requestMessage: userMessage,
responseMessage: responseMessage
};
};
const onStart = (userMessage) => {
sendMessage(res, { message: userMessage, created: true });
abortControllers.set(userMessage.conversationId, { abortController, ...endpointOption });
}
const client = new AnthropicClient(endpointOption.token);
let response = await client.sendMessage(text, {
getIds,
debug: false,
user: req.user.id,
conversationId,
parentMessageId,
overrideParentMessageId,
...endpointOption,
onProgress: progressCallback.call(null, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId
}),
onStart,
abortController
});
if (overrideParentMessageId) {
response.parentMessageId = overrideParentMessageId;
}
await saveConvo(req.user.id, {
...endpointOption,
...endpointOption.modelOptions,
conversationId,
endpoint: 'anthropic'
});
await saveMessage(response);
sendMessage(res, {
title: await getConvoTitle(req.user.id, conversationId),
final: true,
conversation: await getConvo(req.user.id, conversationId),
requestMessage: userMessage,
responseMessage: response
});
res.end();
if (parentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ text, response });
await saveConvo(req.user.id, {
conversationId,
title
});
}
} catch (error) {
console.error(error);
const errorMessage = {
messageId: responseMessageId,
sender: 'Anthropic',
conversationId,
parentMessageId,
unfinished: false,
cancelled: false,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;

View file

@ -7,6 +7,7 @@ const google = require('./google');
const askBingAI = require('./askBingAI');
const gptPlugins = require('./gptPlugins');
const askChatGPTBrowser = require('./askChatGPTBrowser');
const anthropic = require('./anthropic');
// router.use('/azureOpenAI', askAzureOpenAI);
router.use(['/azureOpenAI', '/openAI'], openAI);
@ -14,5 +15,6 @@ router.use('/google', google);
router.use('/bingAI', askBingAI);
router.use('/chatGPTBrowser', askChatGPTBrowser);
router.use('/gptPlugins', gptPlugins);
router.use('/anthropic', anthropic);
module.exports = router;

View file

@ -16,6 +16,12 @@ const getChatGPTBrowserModels = () => {
return models;
};
const getAnthropicModels = () => {
let models = ['claude-1', 'claude-1-100k', 'claude-instant-1', 'claude-instant-1-100k', 'claude-2'];
if (process.env.ANTHROPIC_MODELS) models = String(process.env.ANTHROPIC_MODELS).split(',');
return models;
};
const getPluginModels = () => {
let models = ['gpt-4', 'gpt-4-0613', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo-0301'];
@ -55,7 +61,7 @@ router.get('/', async function (req, res) {
? { availableModels: getOpenAIModels(), userProvide: openAIApiKey === 'user_provided' }
: false;
const azureOpenAI = azureOpenAIApiKey
? { availableModels: getOpenAIModels({ azure: true}), userProvide: azureOpenAIApiKey === 'user_provided' }
? { availableModels: getOpenAIModels({ azure: true }), userProvide: azureOpenAIApiKey === 'user_provided' }
: false;
const gptPlugins = openAIApiKey || azureOpenAIApiKey
? { availableModels: getPluginModels(), availableTools, availableAgents: ['classic', 'functions'], userProvide: userProvidedOpenAI }
@ -69,8 +75,14 @@ router.get('/', async function (req, res) {
availableModels: getChatGPTBrowserModels()
}
: false;
const anthropic = process.env.ANTHROPIC_API_KEY
? {
userProvide: process.env.ANTHROPIC_API_KEY == 'user_provided',
availableModels: getAnthropicModels()
}
: false;
res.send(JSON.stringify({ azureOpenAI, openAI, google, bingAI, chatGPTBrowser, gptPlugins }));
res.send(JSON.stringify({ azureOpenAI, openAI, google, bingAI, chatGPTBrowser, gptPlugins, anthropic }));
});
module.exports = { router, getOpenAIModels, getChatGPTBrowserModels };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="135.46666mm"
height="135.46666mm"
viewBox="0 0 135.46666 135.46666"
version="1.1"
id="svg5"
xml:space="preserve"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
sodipodi:docname="web-browser.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.829812"
inkscape:cx="339.10587"
inkscape:cy="281.44968"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="1072"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs2"><linearGradient
id="linearGradient1021"
inkscape:collect="always"><stop
style="stop-color:#6914d1;stop-opacity:1;"
offset="0"
id="stop1017" /><stop
style="stop-color:#c82090;stop-opacity:1;"
offset="1"
id="stop1019" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1021"
id="linearGradient5957"
x1="75.754601"
y1="163.95738"
x2="146.97395"
y2="86.082359"
gradientUnits="userSpaceOnUse" /></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-38.978451,-51.992085)"><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
transform="translate(38.978451,51.992085)"
style="fill:#000000;fill-opacity:1"><rect
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:34.9999"
id="rect3228"
width="135.46666"
height="135.46666"
x="0"
y="0" /></g><g
id="g5792"
transform="matrix(0.10863854,0,0,0.10863854,51.849445,65.949185)"><path
style="display:inline;fill:url(#linearGradient5957);fill-opacity:1;stroke-width:34.9999"
id="path5949"
sodipodi:type="arc"
sodipodi:cx="106.16872"
sodipodi:cy="120.26846"
sodipodi:rx="53.232887"
sodipodi:ry="53.232887"
sodipodi:start="0.31945228"
sodipodi:end="0.31361418"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="M 156.70842,136.98607 A 53.232887,53.232887 0 0 1 89.524891,170.83252 53.232887,53.232887 0 0 1 55.580426,103.69845 53.232887,53.232887 0 0 1 122.66487,69.656042 53.232887,53.232887 0 0 1 156.80516,136.69073"
transform="matrix(9.2048366,0,0,9.2048366,-477.26567,-607.05147)" /><path
d="m 500,10 c -0.1,0 -0.3,0 -0.4,0 -0.1,0 -0.1,0 -0.2,0 -0.2,0 -0.4,0 -0.6,0 C 228.7,10.6 10,229.8 10,500 c 0,270.2 218.7,489.3 488.8,490 0.2,0 0.4,0 0.6,0 0.1,0 0.1,0 0.2,0 0.2,0 0.3,0 0.4,0 C 770.6,990 990,770.6 990,500 990,229.4 770.6,10 500,10 Z m 19.6,293.2 c 51.9,-1.4 102.5,-8.3 151.2,-20.1 14.7,57.8 23.8,124.3 25.2,197.3 H 519.6 Z m 0,-39.2 V 52.3 C 572.4,66.9 626,137.4 660.1,245.4 614.8,256.3 567.9,262.7 519.6,264 Z M 480.4,51.8 V 264 c -48.7,-1.4 -96,-7.8 -141.6,-19 C 373.2,136.4 427.2,65.7 480.4,51.8 Z m 0,251.4 V 480.4 H 302.8 c 1.4,-73.1 10.6,-139.7 25.2,-197.5 49.1,12 100,18.9 152.4,20.3 z M 263.3,480.4 H 49.7 c 4.3,-100.8 41.9,-193.1 102,-266.2 43.6,24 89.9,43.7 138.4,58.4 -15.8,62.5 -25.3,133 -26.8,207.8 z m 0,39.2 c 1.4,74.8 10.9,145.2 26.8,207.8 -48.5,14.7 -94.8,34.4 -138.4,58.5 C 91.6,712.7 54,620.4 49.7,519.6 Z m 39.5,0 h 177.6 v 177 C 428,698 377.1,705 328,717 313.3,659.2 304.2,592.6 302.8,519.6 Z M 480.4,735.7 V 948.1 C 427.2,934.2 373.1,863.4 338.7,754.6 c 45.7,-11 93,-17.5 141.7,-18.9 z m 39.2,212 v -212 c 48.3,1.4 95.2,7.8 140.5,18.7 C 626,862.5 572.5,933.1 519.6,947.7 Z m 0,-251.1 v -177 H 696 C 694.6,592.5 685.5,659 670.8,716.7 622.1,704.8 571.6,698 519.6,696.6 Z m 215.8,-177 H 950.3 C 946,620.4 908.5,712.7 848.4,785.8 804.4,761.6 757.7,741.8 708.8,727 724.5,664.5 734,594.2 735.4,519.6 Z m 0,-39.2 C 734,405.7 724.5,335.3 708.7,272.8 757.6,258 804.3,238.2 848.2,214 c 60.2,73.1 97.7,165.5 102.1,266.3 z M 821.2,184.1 C 782.2,204.8 741,222 698.1,235 675.2,161.3 643,101.1 605,61.6 688.4,81.7 762.9,124.8 821.2,184.1 Z M 393.5,62 c -37.8,39.4 -69.9,99.3 -92.7,172.7 -42.6,-13 -83.4,-30 -122,-50.6 C 236.7,125.2 310.6,82.2 393.5,62 Z M 178.6,815.7 c 38.7,-20.6 79.5,-37.6 122.1,-50.6 22.8,73.4 54.9,133.4 92.7,172.9 C 310.6,917.8 236.6,874.7 178.6,815.7 Z m 426.3,122.6 c 38.1,-39.5 70.3,-99.7 93.2,-173.6 43,13.1 84.2,30.2 123.2,51 C 763,875.1 688.5,918.3 604.9,938.3 Z"
id="path5790"
style="fill:#000000;fill-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -305,7 +305,6 @@ function Registration() {
)}
{startupConfig?.openidLoginEnabled && startupConfig?.socialLoginEnabled && (
<>
<div className="mt-4 flex gap-x-2">
<a
aria-label="Login with OpenID"

View file

@ -0,0 +1,27 @@
import React from 'react';
import { HoverCardPortal, HoverCardContent } from '~/components/ui/HoverCard.tsx';
const types = {
temp: 'Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. 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.',
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.'
};
function OptionHover({ type, side }) {
return (
<HoverCardPortal>
<HoverCardContent
side={side}
className="w-80 "
>
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{types[type]}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
);
}
export default OptionHover;

View file

@ -0,0 +1,251 @@
import React from 'react';
import { useRecoilValue } from 'recoil';
import TextareaAutosize from 'react-textarea-autosize';
import SelectDropDown from '../../ui/SelectDropDown';
import { Input } from '~/components/ui/Input.tsx';
import { Label } from '~/components/ui/Label.tsx';
import { Slider } from '~/components/ui/Slider.tsx';
import { InputNumber } from '~/components/ui/InputNumber.tsx';
import OptionHover from './OptionHover';
import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx';
import { cn } from '~/utils/';
const defaultTextProps =
'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';
const optionText =
'p-0 shadow-none text-right pr-1 h-8 border-transparent focus:ring-[#10a37f] focus:ring-offset-0 focus:ring-opacity-100 hover:bg-gray-800/10 dark:hover:bg-white/10 focus:bg-gray-800/10 dark:focus:bg-white/10 transition-colors';
import store from '~/store';
function Settings(props) {
const {
readonly,
model,
modelLabel,
promptPrefix,
temperature,
topP,
topK,
maxOutputTokens,
setOption
} = props;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
const setModel = setOption('model');
const setModelLabel = setOption('modelLabel');
const setPromptPrefix = setOption('promptPrefix');
const setTemperature = setOption('temperature');
const setTopP = setOption('topP');
const setTopK = setOption('topK');
const setMaxOutputTokens = setOption('maxOutputTokens');
const models = endpointsConfig?.['anthropic']?.['availableModels'] || [];
return (
<div className={`h-[490px] overflow-y-auto md:h-[350px]`}>
<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="grid w-full items-center gap-2">
<SelectDropDown
value={model}
setValue={setModel}
availableValues={models}
disabled={readonly}
className={cn(
defaultTextProps,
'z-50 flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'
)}
containerClassName="flex w-full resize-none"
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
Custom Name <small className="opacity-40">(default: blank)</small>
</Label>
<Input
id="modelLabel"
disabled={readonly}
value={modelLabel || ''}
onChange={(e) => setModelLabel(e.target.value || null)}
placeholder="Set a custom name for Claude"
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'
)}
/>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
Prompt Prefix <small className="opacity-40">(default: blank)</small>
</Label>
<TextareaAutosize
id="promptPrefix"
disabled={readonly}
value={promptPrefix || ''}
onChange={(e) => setPromptPrefix(e.target.value || null)}
placeholder="Set custom instructions or context. Ignored if empty."
className={cn(
defaultTextProps,
'flex max-h-[300px] min-h-[100px] w-full resize-none px-3 py-2 '
)}
/>
</div>
</div>
<div className="col-span-1 flex flex-col items-center justify-start gap-6">
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
Temperature <small className="opacity-40">(default: 0.7)</small>
</Label>
<InputNumber
id="temp-int"
disabled={readonly}
value={temperature}
onChange={(value) => setTemperature(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[temperature]}
onValueChange={(value) => setTemperature(value[0])}
doubleClickHandler={() => setTemperature(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="temp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
Top P <small className="opacity-40">(default: 0.95)</small>
</Label>
<InputNumber
id="top-p-int"
disabled={readonly}
value={topP}
onChange={(value) => setTopP(value)}
max={1}
min={0}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[topP]}
onValueChange={(value) => setTopP(value[0])}
doubleClickHandler={() => setTopP(1)}
max={1}
min={0}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topp" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="top-k-int" className="text-left text-sm font-medium">
Top K <small className="opacity-40">(default: 40)</small>
</Label>
<InputNumber
id="top-k-int"
disabled={readonly}
value={topK}
onChange={(value) => setTopK(value)}
max={40}
min={1}
step={0.01}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[topK]}
onValueChange={(value) => setTopK(value[0])}
doubleClickHandler={() => setTopK(0)}
max={40}
min={1}
step={0.01}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="topk" side="left" />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<div className="flex justify-between">
<Label htmlFor="max-tokens-int" className="text-left text-sm font-medium">
Max Output Tokens <small className="opacity-40">(default: 1024)</small>
</Label>
<InputNumber
id="max-tokens-int"
disabled={readonly}
value={maxOutputTokens}
onChange={(value) => setMaxOutputTokens(value)}
max={1024}
min={1}
step={1}
controls={false}
className={cn(
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200'
)
)}
/>
</div>
<Slider
disabled={readonly}
value={[maxOutputTokens]}
onValueChange={(value) => setMaxOutputTokens(value[0])}
doubleClickHandler={() => setMaxOutputTokens(0)}
max={1024}
min={1}
step={1}
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover type="maxoutputtokens" side="left" />
</HoverCard>
</div>
</div>
</div>
);
}
export default Settings;

View file

@ -10,17 +10,12 @@ const types = {
};
function OptionHover({ type, side }) {
// const options = {};
// if (type === 'pres') {
// options.sideOffset = 45;
// }
return (
<HoverCardPortal>
<HoverCardContent
side={side}
className="w-80 "
// {...options}
>
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-300">{types[type]}</p>

View file

@ -43,7 +43,7 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }) => {
id="chatGptLabel"
value={title || ''}
onChange={(e) => setTitle(e.target.value || '')}
placeholder="Set a custom name, in case you can find this preset"
placeholder="Set a custom name for this preset"
className={cn(
defaultTextProps,
'flex h-10 max-h-10 w-full resize-none px-3 py-2 focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'

View file

@ -2,12 +2,12 @@ import OpenAISettings from './OpenAI/Settings.jsx';
import BingAISettings from './BingAI/Settings.jsx';
import GoogleSettings from './Google/Settings.jsx';
import PluginsSettings from './Plugins/Settings.jsx';
import AnthropicSettings from './Anthropic/Settings.jsx';
// A preset dialog to show readonly preset values.
const Settings = ({ preset, ...props }) => {
const renderSettings = () => {
const { endpoint } = preset || {};
console.log('endpoint', endpoint);
if (endpoint === 'openAI' || endpoint === 'azureOpenAI') {
return (
@ -47,6 +47,20 @@ const Settings = ({ preset, ...props }) => {
{...props}
/>
);
} else if (endpoint === 'anthropic') {
return (
<AnthropicSettings
model={preset?.model}
modelLabel={preset?.modelLabel}
promptPrefix={preset?.promptPrefix}
temperature={preset?.temperature}
topP={preset?.topP}
topK={preset?.topK}
maxOutputTokens={preset?.maxOutputTokens}
edit={true}
{...props}
/>
);
} else if (endpoint === 'gptPlugins') {
return (
<PluginsSettings

View file

@ -0,0 +1,107 @@
import { useState } from 'react';
import { Settings2 } from 'lucide-react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { SelectDropDown, Button } from '~/components';
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
import Settings from '../../Endpoints/Anthropic/Settings.jsx';
import { cn } from '~/utils/';
import store from '~/store';
function AnthropicOptions() {
const [advancedMode, setAdvancedMode] = useState(false);
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
const { endpoint, conversationId } = conversation;
const { model, modelLabel, promptPrefix, temperature, topP, topK, maxOutputTokens } =
conversation;
const endpointsConfig = useRecoilValue(store.endpointsConfig);
if (endpoint !== 'anthropic') return null;
if (conversationId !== 'new') return null;
const models = endpointsConfig?.['anthropic']?.['availableModels'] || [];
const triggerAdvancedMode = () => setAdvancedMode((prev) => !prev);
const switchToSimpleMode = () => {
setAdvancedMode(false);
};
const saveAsPreset = () => {
setSaveAsDialogShow(true);
};
const setOption = (param) => (newValue) => {
let update = {};
update[param] = newValue;
setConversation((prevState) => ({
...prevState,
...update
}));
};
const cardStyle =
'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';
return (
<>
<div
className={
'openAIOptions-simple-container flex w-full flex-wrap items-center justify-center gap-2' +
(!advancedMode ? ' show' : '')
}
>
<SelectDropDown
value={model}
setValue={setOption('model')}
availableValues={models}
showAbove={true}
showLabel={false}
className={cn(
cardStyle,
'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600'
)}
/>
<Button
type="button"
className={cn(
cardStyle,
'min-w-4 z-50 flex h-[40px] flex-none items-center justify-center px-4 hover:bg-slate-50 focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-600'
)}
onClick={triggerAdvancedMode}
>
<Settings2 className="w-4 text-gray-600 dark:text-white" />
</Button>
</div>
<EndpointOptionsPopover
content={
<div className="px-4 py-4">
<Settings
model={model}
modelLabel={modelLabel}
promptPrefix={promptPrefix}
temperature={temperature}
topP={topP}
topK={topK}
maxOutputTokens={maxOutputTokens}
setOption={setOption}
/>
</div>
}
visible={advancedMode}
saveAsPreset={saveAsPreset}
switchToSimpleMode={switchToSimpleMode}
/>
<SaveAsPresetDialog
open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow}
preset={conversation}
/>
</>
);
}
export default AnthropicOptions;

View file

@ -6,6 +6,7 @@ import PluginsOptions from './PluginsOptions';
import ChatGPTOptions from './ChatGPTOptions';
import BingAIOptions from './BingAIOptions';
import GoogleOptions from './GoogleOptions';
import AnthropicOptions from './AnthropicOptions';
import NewConversationMenu from './NewConversationMenu';
import AdjustToneButton from './AdjustToneButton';
import Footer from './Footer';
@ -130,6 +131,7 @@ export default function TextChat({ isSearchView = false }) {
<ChatGPTOptions />
<GoogleOptions />
<BingAIOptions show={showBingToneSetting} />
<AnthropicOptions />
</span>
</div>
<div className="input-panel md:bg-vert-light-gradient dark:md:bg-vert-dark-gradient relative w-full border-t bg-white py-2 dark:border-white/20 dark:bg-gray-800 md:border-t-0 md:border-transparent md:bg-transparent md:dark:border-transparent md:dark:bg-transparent">

View file

@ -19,7 +19,7 @@ export default function HoverButtons({
const branchingSupported =
// azureOpenAI, openAI, chatGPTBrowser support branching, so edit enabled // 5/21/23: Bing is allowing editing and Message regenerating
!!['azureOpenAI', 'openAI', 'chatGPTBrowser', 'google', 'bingAI', 'gptPlugins'].find(
!!['azureOpenAI', 'openAI', 'chatGPTBrowser', 'google', 'bingAI', 'gptPlugins', 'anthropic'].find(
(e) => e === endpoint
);
// Sydney in bingAI supports branching, so edit enabled

View file

@ -47,6 +47,8 @@ const MessageHeader = ({ isSearchView = false }) => {
if (model) _title += `: ${model}`;
} else if (endpoint === 'gptPlugins') {
return plugins;
} else if (endpoint === 'anthropic') {
_title = 'Claude';
} else if (endpoint === null) {
null;
} else {

View file

@ -0,0 +1,37 @@
export default function AnthropicIcon({ size = 25 }) {
return (
<svg viewBox="0 0 24 16" overflow="visible" width={size} height={size}>
<g
style={{
transform: 'translateX(13px) rotateZ(0deg)',
transformorigin: '4.775px 7.73501px'
}}
// eslint-disable-next-line react/no-unknown-property
transformorigin="4.7750020027160645px 7.735011100769043px"
>
<path
shapeRendering="geometricPrecision"
fill="rgb(24,24,24)"
fillOpacity="1"
d=" M0,0 C0,0 6.1677093505859375,15.470022201538086 6.1677093505859375,15.470022201538086 C6.1677093505859375,15.470022201538086 9.550004005432129,15.470022201538086 9.550004005432129,15.470022201538086 C9.550004005432129,15.470022201538086 3.382294178009033,0 3.382294178009033,0 C3.382294178009033,0 0,0 0,0 C0,0 0,0 0,0z"
></path>
</g>
<g
style={{
transform: 'none',
transformorigin: '7.935px 7.73501px'
}}
opacity="1"
// eslint-disable-next-line react/no-unknown-property
transformorigin="7.93500280380249px 7.735011100769043px"
>
<path
shapeRendering="geometricPrecision"
fill="rgb(24,24,24)"
fillOpacity="1"
d=" M5.824605464935303,9.348296165466309 C5.824605464935303,9.348296165466309 7.93500280380249,3.911694288253784 7.93500280380249,3.911694288253784 C7.93500280380249,3.911694288253784 10.045400619506836,9.348296165466309 10.045400619506836,9.348296165466309 C10.045400619506836,9.348296165466309 5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309 C5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309 5.824605464935303,9.348296165466309z M6.166755199432373,0 C6.166755199432373,0 0,15.470022201538086 0,15.470022201538086 C0,15.470022201538086 3.4480772018432617,15.470022201538086 3.4480772018432617,15.470022201538086 C3.4480772018432617,15.470022201538086 4.709278583526611,12.22130012512207 4.709278583526611,12.22130012512207 C4.709278583526611,12.22130012512207 11.16093635559082,12.22130012512207 11.16093635559082,12.22130012512207 C11.16093635559082,12.22130012512207 12.421928405761719,15.470022201538086 12.421928405761719,15.470022201538086 C12.421928405761719,15.470022201538086 15.87000560760498,15.470022201538086 15.87000560760498,15.470022201538086 C15.87000560760498,15.470022201538086 9.703250885009766,0 9.703250885009766,0 C9.703250885009766,0 6.166755199432373,0 6.166755199432373,0 C6.166755199432373,0 6.166755199432373,0 6.166755199432373,0z"
></path>
</g>
</svg>
);
}

View file

@ -9,3 +9,4 @@ export { default as GoogleIcon } from './GoogleIcon';
export { default as OpenIDIcon } from './OpenIDIcon';
export { default as GithubIcon } from './GithubIcon';
export { default as DiscordIcon } from './DiscordIcon';
export { default as AnthropicIcon } from './AnthropicIcon';

View file

@ -8,7 +8,8 @@ const endpointsConfig = atom({
bingAI: null,
chatGPTBrowser: null,
gptPlugins: null,
google: null
google: null,
anthropic: null
}
});
@ -26,7 +27,7 @@ const endpointsFilter = selector({
const availableEndpoints = selector({
key: 'availableEndpoints',
get: ({ get }) => {
const endpoints = ['azureOpenAI', 'openAI', 'chatGPTBrowser', 'gptPlugins', 'bingAI', 'google'];
const endpoints = ['azureOpenAI', 'openAI', 'chatGPTBrowser', 'gptPlugins', 'bingAI', 'google', 'anthropic'];
const f = get(endpointsFilter);
return endpoints.filter((endpoint) => f[endpoint]);
}

View file

@ -29,6 +29,19 @@ const cleanupPreset = ({ preset: _preset, endpointsConfig = {} }) => {
topK: _preset?.topK ?? 40,
title: _preset?.title ?? 'New Preset'
};
} else if (endpoint === 'anthropic') {
preset = {
endpoint,
presetId: _preset?.presetId ?? null,
model: _preset?.model ?? endpointsConfig[endpoint]?.availableModels?.[0] ?? 'claude-1',
modelLabel: _preset?.modelLabel ?? null,
promptPrefix: _preset?.promptPrefix ?? null,
temperature: _preset?.temperature ?? 0.7,
maxOutputTokens: _preset?.maxOutputTokens ?? 1024,
topP: _preset?.topP ?? 0.7,
topK: _preset?.topK ?? 40,
title: _preset?.title ?? 'New Preset'
};
} else if (endpoint === 'bingAI') {
preset = {
endpoint,

View file

@ -57,6 +57,22 @@ const buildDefaultConversation = ({
clientId: null,
invocationId: 1
};
} else if (endpoint === 'anthropic') {
conversation = {
...conversation,
endpoint,
model:
lastConversationSetup?.model ??
lastSelectedModel[endpoint] ??
endpointsConfig[endpoint]?.availableModels?.[0] ??
'claude-1',
modelLabel: lastConversationSetup?.modelLabel ?? null,
promptPrefix: lastConversationSetup?.promptPrefix ?? null,
temperature: lastConversationSetup?.temperature ?? 0.7,
maxOutputTokens: lastConversationSetup?.maxOutputTokens ?? 1024,
topP: lastConversationSetup?.topP ?? 0.7,
topK: lastConversationSetup?.topK ?? 40
};
} else if (endpoint === 'chatGPTBrowser') {
conversation = {
...conversation,
@ -165,7 +181,8 @@ const getDefaultConversation = ({ conversation, endpointsConfig, preset }) => {
'bingAI',
'chatGPTBrowser',
'gptPlugins',
'google'
'google',
'anthropic'
].find((e) => endpointsConfig?.[e]);
if (endpoint) {
conversation = buildDefaultConversation({ conversation, endpoint, endpointsConfig });

View file

@ -1,5 +1,6 @@
import { Plugin, GPTIcon, BingIcon } from '~/components/svg';
import { Plugin, GPTIcon, BingIcon, AnthropicIcon } from '~/components/svg';
import { useAuthContext } from '~/hooks/AuthContext';
import { cn } from '~/utils'
const getIcon = (props) => {
const { size = 30, isCreatedByUser, button, model, message = true } = props;
@ -20,7 +21,9 @@ const getIcon = (props) => {
className="rounded-sm"
src={
user?.avatar ||
`https://api.dicebear.com/6.x/initials/svg?seed=${user?.name || 'User'}&fontFamily=Verdana&fontSize=36`
`https://api.dicebear.com/6.x/initials/svg?seed=${
user?.name || 'User'
}&fontFamily=Verdana&fontSize=36`
}
alt="avatar"
/>
@ -53,6 +56,11 @@ const getIcon = (props) => {
const { modelLabel } = props;
icon = <img src="/assets/google-palm.svg" alt="Palm Icon"/>;
name = modelLabel || 'PaLM2';
} else if (endpoint === 'anthropic') {
const { modelLabel } = props;
icon = <AnthropicIcon size={size * 0.7} />;
bg = '#d09a74';
name = modelLabel || 'Claude';
} else if (endpoint === 'bingAI') {
const { jailbreak } = props;
icon = <BingIcon size={size * 0.7} />;
@ -83,9 +91,10 @@ const getIcon = (props) => {
width: size,
height: size
}}
className={
'relative flex items-center justify-center rounded-sm text-white ' + props?.className
}
className={cn(
'relative flex items-center justify-center rounded-sm text-white ',
props?.className ?? '',
)}
>
{icon}
{error && (

View file

@ -76,6 +76,22 @@ const useMessageHandler = () => {
token: endpointsConfig[endpoint]?.userProvide ? getToken() : null
};
responseSender = endpointOption.jailbreak ? 'Sydney' : 'BingAI';
} else if (endpoint === 'anthropic') {
endpointOption = {
endpoint,
model:
currentConversation?.model ??
endpointsConfig[endpoint]?.availableModels?.[0] ??
'claude-1',
modelLabel: currentConversation?.modelLabel ?? null,
promptPrefix: currentConversation?.promptPrefix ?? null,
temperature: currentConversation?.temperature ?? 0.7,
maxOutputTokens: currentConversation?.maxOutputTokens ?? 1024,
topP: currentConversation?.topP ?? 0.7,
topK: currentConversation?.topK ?? 40,
token: endpointsConfig[endpoint]?.userProvide ? getToken() : null
};
responseSender = 'Anthropic';
} else if (endpoint === 'chatGPTBrowser') {
endpointOption = {
endpoint,

View file

@ -41,6 +41,7 @@ export const alternateName = {
bingAI: 'Bing',
chatGPTBrowser: 'ChatGPT',
gptPlugins: 'Plugins',
google: 'PaLM'
google: 'PaLM',
anthropic: 'Anthropic',
};

313
package-lock.json generated
View file

@ -47,7 +47,9 @@
"version": "0.5.3",
"license": "ISC",
"dependencies": {
"@anthropic-ai/sdk": "^0.5.0",
"@dqbd/tiktoken": "^1.0.2",
"@fortaine/fetch-event-source": "^3.0.6",
"@keyv/mongo": "^2.1.8",
"@waylaidwanderer/chatgpt-api": "^1.37.0",
"axios": "^1.3.4",
@ -91,6 +93,28 @@
"supertest": "^6.3.3"
}
},
"api/node_modules/@anthropic-ai/sdk": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.5.0.tgz",
"integrity": "sha512-7T/oK6CtheVkdRZmIO9FSdHtKyUU92F6JHRz7RY99gzcm6DM5rYTNORvwVHWTe0nr9lsZuso8GpoXZsUG7Zypg==",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"@types/qs": "^6.9.7",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"digest-fetch": "^1.3.0",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7",
"qs": "^6.10.3"
}
},
"api/node_modules/@types/node": {
"version": "18.16.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz",
"integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA=="
},
"api/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
@ -371,6 +395,15 @@
}
}
},
"api/node_modules/langchain/node_modules/@anthropic-ai/sdk": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.4.4.tgz",
"integrity": "sha512-Z/39nQi1sSUCeLII3lsAbL1u+0JF6cR2XmUEX9sLH0VtxmIjY6cjOUYjCkYh4oapTxOkhAFnVSAFJ6cxml2qXg==",
"dependencies": {
"@fortaine/fetch-event-source": "^3.0.6",
"cross-fetch": "^3.1.5"
}
},
"client": {
"name": "@librechat/frontend",
"version": "0.5.3",
@ -7560,6 +7593,28 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz",
"integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ=="
},
"node_modules/@types/node-fetch": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz",
"integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==",
"dependencies": {
"@types/node": "*",
"form-data": "^3.0.0"
}
},
"node_modules/@types/node-fetch/node_modules/form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@types/parse5": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
@ -7576,6 +7631,11 @@
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
"node_modules/@types/react": {
"version": "18.2.11",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.11.tgz",
@ -8320,6 +8380,19 @@
"node": ">= 14"
}
},
"node_modules/agentkeepalive": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz",
"integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==",
"dependencies": {
"debug": "^4.1.0",
"depd": "^2.0.0",
"humanize-ms": "^1.2.1"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@ -9213,6 +9286,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
@ -9754,6 +9832,14 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"engines": {
"node": "*"
}
},
"node_modules/cheerio": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
@ -10519,6 +10605,14 @@
"node": ">= 8"
}
},
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"engines": {
"node": "*"
}
},
"node_modules/crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@ -11022,6 +11116,15 @@
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/digest-fetch": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",
"integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==",
"dependencies": {
"base-64": "^0.1.0",
"md5": "^2.3.0"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -12898,6 +13001,11 @@
"node": ">= 6"
}
},
"node_modules/form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
@ -12906,6 +13014,18 @@
"node": ">=0.4.x"
}
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"dependencies": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
},
"engines": {
"node": ">= 12.20"
}
},
"node_modules/formidable": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
@ -13894,6 +14014,14 @@
"node": ">=10.17.0"
}
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"dependencies": {
"ms": "^2.0.0"
}
},
"node_modules/husky": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
@ -18228,6 +18356,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"node_modules/md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -18238,6 +18376,11 @@
"safe-buffer": "^5.1.2"
}
},
"node_modules/md5/node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"node_modules/mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
@ -19544,6 +19687,24 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
@ -25895,6 +26056,14 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"engines": {
"node": ">= 14"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
@ -30217,7 +30386,9 @@
"@librechat/backend": {
"version": "file:api",
"requires": {
"@anthropic-ai/sdk": "^0.5.0",
"@dqbd/tiktoken": "^1.0.2",
"@fortaine/fetch-event-source": "^3.0.6",
"@keyv/mongo": "^2.1.8",
"@waylaidwanderer/chatgpt-api": "^1.37.0",
"axios": "^1.3.4",
@ -30259,6 +30430,28 @@
"supertest": "^6.3.3"
},
"dependencies": {
"@anthropic-ai/sdk": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.5.0.tgz",
"integrity": "sha512-7T/oK6CtheVkdRZmIO9FSdHtKyUU92F6JHRz7RY99gzcm6DM5rYTNORvwVHWTe0nr9lsZuso8GpoXZsUG7Zypg==",
"requires": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"@types/qs": "^6.9.7",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"digest-fetch": "^1.3.0",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7",
"qs": "^6.10.3"
}
},
"@types/node": {
"version": "18.16.19",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz",
"integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA=="
},
"ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
@ -30303,6 +30496,17 @@
"yaml": "^2.2.1",
"zod": "^3.21.4",
"zod-to-json-schema": "^3.20.4"
},
"dependencies": {
"@anthropic-ai/sdk": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.4.4.tgz",
"integrity": "sha512-Z/39nQi1sSUCeLII3lsAbL1u+0JF6cR2XmUEX9sLH0VtxmIjY6cjOUYjCkYh4oapTxOkhAFnVSAFJ6cxml2qXg==",
"requires": {
"@fortaine/fetch-event-source": "^3.0.6",
"cross-fetch": "^3.1.5"
}
}
}
}
}
@ -31631,6 +31835,27 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz",
"integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ=="
},
"@types/node-fetch": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz",
"integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==",
"requires": {
"@types/node": "*",
"form-data": "^3.0.0"
},
"dependencies": {
"form-data": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
}
}
},
"@types/parse5": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
@ -31647,6 +31872,11 @@
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw=="
},
"@types/react": {
"version": "18.2.11",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.11.tgz",
@ -32239,6 +32469,16 @@
"debug": "^4.3.4"
}
},
"agentkeepalive": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz",
"integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==",
"requires": {
"debug": "^4.1.0",
"depd": "^2.0.0",
"humanize-ms": "^1.2.1"
}
},
"aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@ -32943,6 +33183,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
@ -33329,6 +33574,11 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="
},
"cheerio": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
@ -33917,6 +34167,11 @@
"which": "^2.0.1"
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="
},
"crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@ -34280,6 +34535,15 @@
}
}
},
"digest-fetch": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",
"integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==",
"requires": {
"base-64": "^0.1.0",
"md5": "^2.3.0"
}
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -35709,11 +35973,25 @@
"mime-types": "^2.1.12"
}
},
"form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
},
"format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="
},
"formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"requires": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
}
},
"formidable": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
@ -36444,6 +36722,14 @@
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
},
"humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"requires": {
"ms": "^2.0.0"
}
},
"husky": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
@ -39607,6 +39893,23 @@
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
"integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw=="
},
"md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"requires": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
},
"dependencies": {
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
}
}
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -40486,6 +40789,11 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
},
"node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
},
"node-fetch": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
@ -44889,6 +45197,11 @@
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
"integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="
},
"web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="
},
"webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",

View file

@ -12,7 +12,8 @@ export default function createPayload(submission: TSubmission) {
bingAI: '/api/ask/bingAI',
chatGPT: '/api/ask/chatGPT',
chatGPTBrowser: '/api/ask/chatGPTBrowser',
gptPlugins: '/api/ask/gptPlugins'
gptPlugins: '/api/ask/gptPlugins',
anthropic: '/api/ask/anthropic',
};
const server = endpointUrlMap[endpoint];

View file

@ -24,6 +24,7 @@ export enum EModelEndpoint {
chatGPTBrowser = 'chatGPTBrowser',
google = 'google',
gptPlugins = 'gptPlugins',
anthropic = 'anthropic',
}
export type TSubmission = {