mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
feat(OpenAI, PaLM): Add model support for new OpenAI models and codechat-bison (#516)
* feat(OpenAI, PaLM): add new models refactor(chatgpt-client.js): use object to map max tokens for each model refactor(askChatGPTBrowser.js, askGPTPlugins.js, askOpenAI.js): comment out unused function calls and error handling feat(askGoogle.js): add support for codechat-bison model refactor(endpoints.js): add gpt-4-0613 and gpt-3.5-turbo-16k to available models for OpenAI and GPT plugins refactor(EditPresetDialog.jsx): hide examples for codechat-bison model in google endpoint style(EndpointOptionsPopover.jsx): add cn utility function import and use it to set additionalButton className refactor(Google/Settings.jsx): conditionally render custom name and prompt prefix fields based on model type The code has been refactored to conditionally render the custom name and prompt prefix fields based on the type of model selected. If the model starts with 'codechat-', the fields will not be rendered. refactor(Settings.jsx): remove duplicated code and wrap a section in a conditional statement based on a variable style(Input): add z-index to Input component to fix overlapping issue feat(GoogleOptions): disable Examples button when model starts with 'codechat-' prefix * feat(.env.example, endpoints.js): add PLUGIN_MODELS environment variable and use it to get plugin models in endpoints.js
This commit is contained in:
parent
42583e7344
commit
36a524a630
12 changed files with 168 additions and 131 deletions
|
|
@ -30,7 +30,7 @@ OPENAI_API_KEY="user_provided"
|
||||||
# Identify the available models, separated by commas *without spaces*.
|
# Identify the available models, separated by commas *without spaces*.
|
||||||
# The first will be default.
|
# The first will be default.
|
||||||
# Leave it blank to use internal settings.
|
# Leave it blank to use internal settings.
|
||||||
OPENAI_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-0301,text-davinci-003,gpt-4,gpt-4-0314
|
OPENAI_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-16k,gpt-3.5-turbo-0301,text-davinci-003,gpt-4,gpt-4-0314,gpt-4-0613
|
||||||
|
|
||||||
# Reverse proxy settings for OpenAI:
|
# Reverse proxy settings for OpenAI:
|
||||||
# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy
|
# https://github.com/waylaidwanderer/node-chatgpt-api#using-a-reverse-proxy
|
||||||
|
|
@ -98,6 +98,11 @@ CHATGPT_MODELS=text-davinci-002-render-sha,gpt-4
|
||||||
# Plugins:
|
# Plugins:
|
||||||
#############################
|
#############################
|
||||||
|
|
||||||
|
# Identify the available models, separated by commas *without spaces*.
|
||||||
|
# The first will be default.
|
||||||
|
# Leave it blank to use internal settings.
|
||||||
|
PLUGIN_MODELS=gpt-3.5-turbo,gpt-3.5-turbo-16k,gpt-3.5-turbo-0301,gpt-4,gpt-4-0314,gpt-4-0613
|
||||||
|
|
||||||
# For securely storing credentials, you need a fixed key and IV. You can set them here for prod and dev environments
|
# For securely storing credentials, you need a fixed key and IV. You can set them here for prod and dev environments
|
||||||
# If you don't set them, the app will crash on startup.
|
# If you don't set them, the app will crash on startup.
|
||||||
# You need a 32-byte key (64 characters in hex) and 16-byte IV (32 characters in hex)
|
# You need a 32-byte key (64 characters in hex) and 16-byte IV (32 characters in hex)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,19 @@ const askClient = async ({
|
||||||
if (promptPrefix) {
|
if (promptPrefix) {
|
||||||
promptText = promptPrefix;
|
promptText = promptPrefix;
|
||||||
}
|
}
|
||||||
const maxContextTokens = model === 'gpt-4-32k' ? 32767 : model.startsWith('gpt-4') ? 8191 : 4095; // 1 less than maximum
|
|
||||||
|
const maxTokensMap = {
|
||||||
|
'gpt-4': 8191,
|
||||||
|
'gpt-4-0613': 8191,
|
||||||
|
'gpt-4-32k': 32767,
|
||||||
|
'gpt-4-32k-0613': 32767,
|
||||||
|
'gpt-3.5-turbo': 4095,
|
||||||
|
'gpt-3.5-turbo-0613': 4095,
|
||||||
|
'gpt-3.5-turbo-0301': 4095,
|
||||||
|
'gpt-3.5-turbo-16k': 15999,
|
||||||
|
};
|
||||||
|
|
||||||
|
const maxContextTokens = maxTokensMap[model] ?? 4095; // 1 less than maximum
|
||||||
const clientOptions = {
|
const clientOptions = {
|
||||||
reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null,
|
reverseProxyUrl: process.env.OPENAI_REVERSE_PROXY || null,
|
||||||
azure,
|
azure,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { getChatGPTBrowserModels } = require('../endpoints');
|
// const { getChatGPTBrowserModels } = require('../endpoints');
|
||||||
const { browserClient } = require('../../../app/');
|
const { browserClient } = require('../../../app/');
|
||||||
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
|
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
|
||||||
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
|
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
|
||||||
|
|
@ -38,9 +38,9 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||||
token: req.body?.token ?? null
|
token: req.body?.token ?? null
|
||||||
};
|
};
|
||||||
|
|
||||||
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', {
|
||||||
userMessage,
|
userMessage,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { titleConvo } = require('../../../app/');
|
const { titleConvo } = require('../../../app/');
|
||||||
const { getOpenAIModels } = require('../endpoints');
|
// const { getOpenAIModels } = require('../endpoints');
|
||||||
const ChatAgent = require('../../../app/langchain/ChatAgent');
|
const ChatAgent = require('../../../app/langchain/ChatAgent');
|
||||||
const { validateTools } = require('../../../app/langchain/tools');
|
const { validateTools } = require('../../../app/langchain/tools');
|
||||||
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
|
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
|
||||||
|
|
@ -63,10 +63,10 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||||
agentOptions
|
agentOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
const availableModels = getOpenAIModels();
|
// const availableModels = getOpenAIModels();
|
||||||
if (availableModels.find((model) => model === endpointOption.modelOptions.model) === undefined) {
|
// if (availableModels.find((model) => model === endpointOption.modelOptions.model) === undefined) {
|
||||||
return handleError(res, { text: `Illegal request: model` });
|
// return handleError(res, { text: `Illegal request: model` });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// console.log('ask log', {
|
// console.log('ask log', {
|
||||||
// text,
|
// text,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const availableModels = ['chat-bison', 'text-bison'];
|
const availableModels = ['chat-bison', 'text-bison', 'codechat-bison'];
|
||||||
if (availableModels.find((model) => model === endpointOption.modelOptions.model) === undefined) {
|
if (availableModels.find((model) => model === endpointOption.modelOptions.model) === undefined) {
|
||||||
return handleError(res, { text: `Illegal request: model` });
|
return handleError(res, { text: `Illegal request: model` });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const express = require('express');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const addToCache = require('./addToCache');
|
const addToCache = require('./addToCache');
|
||||||
const { getOpenAIModels } = require('../endpoints');
|
// const { getOpenAIModels } = require('../endpoints');
|
||||||
const { titleConvo, askClient } = require('../../../app/');
|
const { titleConvo, askClient } = require('../../../app/');
|
||||||
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
|
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
|
||||||
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
|
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
|
||||||
|
|
@ -63,9 +63,9 @@ router.post('/', requireJwtAuth, async (req, res) => {
|
||||||
frequency_penalty: req.body?.frequency_penalty ?? 0
|
frequency_penalty: req.body?.frequency_penalty ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const availableModels = getOpenAIModels();
|
// const availableModels = getOpenAIModels();
|
||||||
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', {
|
||||||
userMessage,
|
userMessage,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ const router = express.Router();
|
||||||
const { availableTools } = require('../../app/langchain/tools');
|
const { availableTools } = require('../../app/langchain/tools');
|
||||||
|
|
||||||
const getOpenAIModels = () => {
|
const getOpenAIModels = () => {
|
||||||
let models = ['gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301'];
|
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', 'text-davinci-003' ];
|
||||||
if (process.env.OPENAI_MODELS) models = String(process.env.OPENAI_MODELS).split(',');
|
if (process.env.OPENAI_MODELS) models = String(process.env.OPENAI_MODELS).split(',');
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
|
|
@ -16,6 +16,13 @@ const getChatGPTBrowserModels = () => {
|
||||||
return models;
|
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'];
|
||||||
|
if (process.env.PLUGIN_MODELS) models = String(process.env.PLUGIN_MODELS).split(',');
|
||||||
|
|
||||||
|
return models;
|
||||||
|
};
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
router.get('/', async function (req, res) {
|
router.get('/', async function (req, res) {
|
||||||
let key, palmUser;
|
let key, palmUser;
|
||||||
|
|
@ -38,7 +45,7 @@ router.get('/', async function (req, res) {
|
||||||
|
|
||||||
const google =
|
const google =
|
||||||
key || palmUser
|
key || palmUser
|
||||||
? { userProvide: palmUser, availableModels: ['chat-bison', 'text-bison'] }
|
? { userProvide: palmUser, availableModels: ['chat-bison', 'text-bison', 'codechat-bison'] }
|
||||||
: false;
|
: false;
|
||||||
const azureOpenAI = !!process.env.AZURE_OPENAI_API_KEY;
|
const azureOpenAI = !!process.env.AZURE_OPENAI_API_KEY;
|
||||||
const apiKey = process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_API_KEY;
|
const apiKey = process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_API_KEY;
|
||||||
|
|
@ -46,7 +53,7 @@ router.get('/', async function (req, res) {
|
||||||
? { availableModels: getOpenAIModels(), userProvide: apiKey === 'user_provided' }
|
? { availableModels: getOpenAIModels(), userProvide: apiKey === 'user_provided' }
|
||||||
: false;
|
: false;
|
||||||
const gptPlugins = apiKey
|
const gptPlugins = apiKey
|
||||||
? { availableModels: ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301'], availableTools }
|
? { availableModels: getPluginModels(), availableTools }
|
||||||
: 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' }
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ const EditPresetDialog = ({ open, onOpenChange, preset: _preset, title }) => {
|
||||||
<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">
|
||||||
{shouldShowSettings && <Settings preset={preset} setOption={setOption} />}
|
{shouldShowSettings && <Settings preset={preset} setOption={setOption} />}
|
||||||
{preset?.endpoint === 'google' && showExamples && (
|
{preset?.endpoint === 'google' && showExamples && !preset?.model?.startsWith('codechat-') && (
|
||||||
<Examples
|
<Examples
|
||||||
examples={preset.examples}
|
examples={preset.examples}
|
||||||
setExample={setExample}
|
setExample={setExample}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Button } from '../ui/Button.tsx';
|
||||||
import CrossIcon from '../svg/CrossIcon';
|
import CrossIcon from '../svg/CrossIcon';
|
||||||
// import SaveIcon from '../svg/SaveIcon';
|
// import SaveIcon from '../svg/SaveIcon';
|
||||||
import { Save } from 'lucide-react';
|
import { Save } from 'lucide-react';
|
||||||
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
function EndpointOptionsPopover({
|
function EndpointOptionsPopover({
|
||||||
content,
|
content,
|
||||||
|
|
@ -18,7 +19,7 @@ function EndpointOptionsPopover({
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
' endpointOptionsPopover-container absolute bottom-[-10px] flex w-full flex-col items-center md:px-4 z-50' +
|
' endpointOptionsPopover-container absolute bottom-[-10px] flex w-full flex-col items-center md:px-4 z-0' +
|
||||||
(visible ? ' show' : '')
|
(visible ? ' show' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -41,7 +42,7 @@ function EndpointOptionsPopover({
|
||||||
{additionalButton && (
|
{additionalButton && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="ml-1 h-auto justify-start bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0"
|
className={cn(additionalButton.buttonClass, "ml-1 h-auto justify-start bg-transparent px-2 py-1 text-xs font-medium font-normal text-black hover:bg-slate-200 hover:text-black focus:ring-0 focus:ring-offset-0 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:focus:outline-none dark:focus:ring-offset-0")}
|
||||||
onClick={additionalButton.handler}
|
onClick={additionalButton.handler}
|
||||||
>
|
>
|
||||||
{additionalButton.icon}
|
{additionalButton.icon}
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,10 @@ function Settings(props) {
|
||||||
|
|
||||||
const models = endpointsConfig?.['google']?.['availableModels'] || [];
|
const models = endpointsConfig?.['google']?.['availableModels'] || [];
|
||||||
|
|
||||||
|
const codeChat = model.startsWith('codechat-');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${maxHeight} overflow-y-auto`}>
|
<div className={`${maxHeight} min-h-[200px] 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">
|
||||||
|
|
@ -55,11 +57,13 @@ function Settings(props) {
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
className={cn(
|
className={cn(
|
||||||
defaultTextProps,
|
defaultTextProps,
|
||||||
'flex w-full resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'
|
'flex w-full z-50 resize-none focus:outline-none focus:ring-0 focus:ring-opacity-0 focus:ring-offset-0'
|
||||||
)}
|
)}
|
||||||
containerClassName="flex w-full resize-none"
|
containerClassName="flex w-full resize-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{!codeChat && (
|
||||||
|
<>
|
||||||
<div className="grid w-full items-center gap-2">
|
<div className="grid w-full items-center gap-2">
|
||||||
<Label htmlFor="modelLabel" className="text-left text-sm font-medium">
|
<Label 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>
|
||||||
|
|
@ -92,6 +96,8 @@ function Settings(props) {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</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">
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
|
|
@ -131,6 +137,8 @@ function Settings(props) {
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<OptionHover type="temp" side="left" />
|
<OptionHover type="temp" side="left" />
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
|
{!codeChat && (
|
||||||
|
<>
|
||||||
<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">
|
||||||
|
|
@ -207,6 +215,8 @@ function Settings(props) {
|
||||||
<OptionHover type="topk" side="left" />
|
<OptionHover 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">
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ function GoogleOptions() {
|
||||||
const cardStyle =
|
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';
|
'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 isCodeChat = model?.startsWith('codechat-');
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
@ -126,7 +127,7 @@ function GoogleOptions() {
|
||||||
<EndpointOptionsPopover
|
<EndpointOptionsPopover
|
||||||
content={
|
content={
|
||||||
<div className="px-4 py-4">
|
<div className="px-4 py-4">
|
||||||
{showExamples ? (
|
{showExamples && !isCodeChat ? (
|
||||||
<Examples
|
<Examples
|
||||||
examples={examples}
|
examples={examples}
|
||||||
setExample={setExample}
|
setExample={setExample}
|
||||||
|
|
@ -152,6 +153,7 @@ function GoogleOptions() {
|
||||||
switchToSimpleMode={switchToSimpleMode}
|
switchToSimpleMode={switchToSimpleMode}
|
||||||
additionalButton={{
|
additionalButton={{
|
||||||
label: (showExamples ? 'Hide' : 'Show') + ' Examples',
|
label: (showExamples ? 'Hide' : 'Show') + ' Examples',
|
||||||
|
buttonClass: isCodeChat ? 'disabled' : '',
|
||||||
handler: triggerExamples,
|
handler: triggerExamples,
|
||||||
icon: <MessagesSquared className="mr-1 w-[14px]" />
|
icon: <MessagesSquared className="mr-1 w-[14px]" />
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ export default function TextChat({ isSearchView = false }) {
|
||||||
onCompositionEnd={handleCompositionEnd}
|
onCompositionEnd={handleCompositionEnd}
|
||||||
placeholder={getPlaceholderText()}
|
placeholder={getPlaceholderText()}
|
||||||
disabled={disabled || isNotAppendable}
|
disabled={disabled || isNotAppendable}
|
||||||
className="m-0 flex h-auto max-h-52 flex-1 resize-none overflow-auto border-0 bg-transparent p-0 pl-2 pr-12 leading-6 placeholder:text-sm placeholder:text-gray-600 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder:text-gray-500 md:pl-2"
|
className="m-0 z-[100] flex h-auto max-h-52 flex-1 resize-none overflow-auto border-0 bg-transparent p-0 pl-2 pr-12 leading-6 placeholder:text-sm placeholder:text-gray-600 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder:text-gray-500 md:pl-2"
|
||||||
/>
|
/>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
submitMessage={submitMessage}
|
submitMessage={submitMessage}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue