mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
Merge branch 'master' of https://github.com/danny-avila/chatgpt-clone into search
This commit is contained in:
commit
48b901fdfe
38 changed files with 894 additions and 581 deletions
46
api/models/Preset.js
Normal file
46
api/models/Preset.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
const Preset = require('./schema/presetSchema');
|
||||||
|
|
||||||
|
const getPreset = async (user, presetId) => {
|
||||||
|
try {
|
||||||
|
return await Preset.findOne({ user, presetId }).exec();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return { message: 'Error getting single preset' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Preset,
|
||||||
|
getPreset,
|
||||||
|
getPresets: async (user, filter) => {
|
||||||
|
try {
|
||||||
|
return await Preset.find({ ...filter, user }).exec();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return { message: 'Error retriving presets' };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
savePreset: async (user, { presetId, newPresetId, ...preset }) => {
|
||||||
|
try {
|
||||||
|
const update = { presetId, ...preset };
|
||||||
|
if (newPresetId) {
|
||||||
|
update.presetId = newPresetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Preset.findOneAndUpdate(
|
||||||
|
{ presetId, user },
|
||||||
|
{ $set: update },
|
||||||
|
{ new: true, upsert: true }
|
||||||
|
).exec();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return { message: 'Error saving preset' };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deletePresets: async (user, filter) => {
|
||||||
|
let toRemove = await Preset.find({ ...filter, user }).select('presetId');
|
||||||
|
const ids = toRemove.map(instance => instance.presetId);
|
||||||
|
let deleteCount = await Preset.deleteMany({ ...filter, user }).exec();
|
||||||
|
return deleteCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
const { getMessages, saveMessage, saveBingMessage, deleteMessagesSince, deleteMessages } = require('./Message');
|
const {
|
||||||
|
getMessages,
|
||||||
|
saveMessage,
|
||||||
|
saveBingMessage,
|
||||||
|
deleteMessagesSince,
|
||||||
|
deleteMessages
|
||||||
|
} = require('./Message');
|
||||||
const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt');
|
const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt');
|
||||||
const { getConvoTitle, getConvo, saveConvo, updateConvo } = require('./Conversation');
|
const { getConvoTitle, getConvo, saveConvo, updateConvo } = require('./Conversation');
|
||||||
|
const { getPreset, getPresets, savePreset, deletePresets } = require('./Preset');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getMessages,
|
getMessages,
|
||||||
|
|
@ -8,12 +15,19 @@ module.exports = {
|
||||||
saveBingMessage,
|
saveBingMessage,
|
||||||
deleteMessagesSince,
|
deleteMessagesSince,
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
|
|
||||||
getConvoTitle,
|
getConvoTitle,
|
||||||
getConvo,
|
getConvo,
|
||||||
saveConvo,
|
saveConvo,
|
||||||
updateConvo,
|
updateConvo,
|
||||||
|
|
||||||
getCustomGpts,
|
getCustomGpts,
|
||||||
updateCustomGpt,
|
updateCustomGpt,
|
||||||
updateByLabel,
|
updateByLabel,
|
||||||
deleteCustomGpts
|
deleteCustomGpts,
|
||||||
|
|
||||||
|
getPreset,
|
||||||
|
getPresets,
|
||||||
|
savePreset,
|
||||||
|
deletePresets
|
||||||
};
|
};
|
||||||
|
|
|
||||||
70
api/models/schema/conversationPreset.js
Normal file
70
api/models/schema/conversationPreset.js
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
module.exports = {
|
||||||
|
// endpoint: [azureOpenAI, openAI, bingAI, chatGPTBrowser]
|
||||||
|
endpoint: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// for azureOpenAI, openAI, chatGPTBrowser only
|
||||||
|
model: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
// for azureOpenAI, openAI only
|
||||||
|
chatGptLabel: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
promptPrefix: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
top_p: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
presence_penalty: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
frequency_penalty: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
// for bingai only
|
||||||
|
jailbreak: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
jailbreakConversationId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
conversationSignature: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
clientId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
invocationId: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
toneStyle: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const mongoMeili = require('../plugins/mongoMeili');
|
const mongoMeili = require('../plugins/mongoMeili');
|
||||||
|
const conversationPreset = require('./conversationPreset');
|
||||||
const convoSchema = mongoose.Schema(
|
const convoSchema = mongoose.Schema(
|
||||||
{
|
{
|
||||||
conversationId: {
|
conversationId: {
|
||||||
|
|
@ -19,74 +20,7 @@ const convoSchema = mongoose.Schema(
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
|
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
|
||||||
// endpoint: [azureOpenAI, openAI, bingAI, chatGPTBrowser]
|
...conversationPreset
|
||||||
endpoint: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
// for azureOpenAI, openAI, chatGPTBrowser only
|
|
||||||
model: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
// for azureOpenAI, openAI only
|
|
||||||
chatGptLabel: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
promptPrefix: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
temperature: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
top_p: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
presence_penalty: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
frequency_penalty: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
// for bingai only
|
|
||||||
jailbreak: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
jailbreakConversationId: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
conversationSignature: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
clientId: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
invocationId: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
toneStyle: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|
|
||||||
27
api/models/schema/presetSchema.js
Normal file
27
api/models/schema/presetSchema.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const conversationPreset = require('./conversationPreset');
|
||||||
|
const presetSchema = mongoose.Schema(
|
||||||
|
{
|
||||||
|
presetId: {
|
||||||
|
type: String,
|
||||||
|
unique: true,
|
||||||
|
required: true,
|
||||||
|
index: true
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: 'New Chat',
|
||||||
|
meiliIndex: true
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
...conversationPreset
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const Preset = mongoose.models.Preset || mongoose.model('Preset', presetSchema);
|
||||||
|
|
||||||
|
module.exports = Preset;
|
||||||
|
|
@ -57,6 +57,7 @@ const projectPath = path.join(__dirname, '..', '..', 'client');
|
||||||
app.use('/api/messages', routes.authenticatedOr401, routes.messages);
|
app.use('/api/messages', routes.authenticatedOr401, routes.messages);
|
||||||
app.use('/api/convos', routes.authenticatedOr401, routes.convos);
|
app.use('/api/convos', routes.authenticatedOr401, routes.convos);
|
||||||
app.use('/api/customGpts', routes.authenticatedOr401, routes.customGpts);
|
app.use('/api/customGpts', routes.authenticatedOr401, routes.customGpts);
|
||||||
|
app.use('/api/presets', routes.authenticatedOr401, routes.presets);
|
||||||
app.use('/api/prompts', routes.authenticatedOr401, routes.prompts);
|
app.use('/api/prompts', routes.authenticatedOr401, routes.prompts);
|
||||||
app.use('/auth', routes.auth);
|
app.use('/auth', routes.auth);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,21 @@
|
||||||
const ask = require('./ask');
|
const ask = require('./ask');
|
||||||
const messages = require('./messages');
|
const messages = require('./messages');
|
||||||
const convos = require('./convos');
|
const convos = require('./convos');
|
||||||
|
const presets = require('./presets');
|
||||||
const customGpts = require('./customGpts');
|
const customGpts = require('./customGpts');
|
||||||
const prompts = require('./prompts');
|
const prompts = require('./prompts');
|
||||||
const search = require('./search');
|
const search = require('./search');
|
||||||
const { router: auth, authenticatedOr401, authenticatedOrRedirect } = require('./auth');
|
const { router: auth, authenticatedOr401, authenticatedOrRedirect } = require('./auth');
|
||||||
|
|
||||||
module.exports = { search, ask, messages, convos, customGpts, prompts, auth, authenticatedOr401, authenticatedOrRedirect };
|
module.exports = {
|
||||||
|
search,
|
||||||
|
ask,
|
||||||
|
messages,
|
||||||
|
convos,
|
||||||
|
presets,
|
||||||
|
customGpts,
|
||||||
|
prompts,
|
||||||
|
auth,
|
||||||
|
authenticatedOr401,
|
||||||
|
authenticatedOrRedirect
|
||||||
|
};
|
||||||
|
|
|
||||||
48
api/server/routes/presets.js
Normal file
48
api/server/routes/presets.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { getPreset, getPresets, savePreset, deletePresets } = require('../../models');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
const presets = (await getPresets(req?.session?.user?.username)).map(preset => {
|
||||||
|
return preset.toObject();
|
||||||
|
});
|
||||||
|
res.status(200).send(presets);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
const update = req.body || {};
|
||||||
|
|
||||||
|
update.presetId = update?.presetId || crypto.randomUUID();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await savePreset(req?.session?.user?.username, update);
|
||||||
|
|
||||||
|
const presets = (await getPresets(req?.session?.user?.username)).map(preset => {
|
||||||
|
return preset.toObject();
|
||||||
|
});
|
||||||
|
res.status(201).send(presets);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/delete', async (req, res) => {
|
||||||
|
const { arg } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deletePresets(req?.session?.user?.username, arg);
|
||||||
|
|
||||||
|
const presets = (await getPresets(req?.session?.user?.username)).map(preset => {
|
||||||
|
return preset.toObject();
|
||||||
|
});
|
||||||
|
res.status(201).send(presets);
|
||||||
|
// res.status(201).send(dbResponse);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -39,6 +39,7 @@ const App = () => {
|
||||||
const [user, setUser] = useRecoilState(store.user);
|
const [user, setUser] = useRecoilState(store.user);
|
||||||
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
|
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
|
||||||
const setEndpointsConfig = useSetRecoilState(store.endpointsConfig);
|
const setEndpointsConfig = useSetRecoilState(store.endpointsConfig);
|
||||||
|
const setPresets = useSetRecoilState(store.presets);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// fetch if seatch enabled
|
// fetch if seatch enabled
|
||||||
|
|
@ -70,6 +71,21 @@ const App = () => {
|
||||||
console.log('Not login!');
|
console.log('Not login!');
|
||||||
window.location.href = '/auth/login';
|
window.location.href = '/auth/login';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// fetch presets
|
||||||
|
axios
|
||||||
|
.get('/api/presets', {
|
||||||
|
timeout: 1000,
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
setPresets(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
console.log('Not login!');
|
||||||
|
window.location.href = '/auth/login';
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (user)
|
if (user)
|
||||||
|
|
|
||||||
134
client/src/components/Endpoints/EditPresetDialog.jsx
Normal file
134
client/src/components/Endpoints/EditPresetDialog.jsx
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useSetRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
import axios from 'axios';
|
||||||
|
import DialogTemplate from '../ui/DialogTemplate';
|
||||||
|
import { Dialog } from '../ui/Dialog.tsx';
|
||||||
|
import { Input } from '../ui/Input.tsx';
|
||||||
|
import { Label } from '../ui/Label.tsx';
|
||||||
|
import Dropdown from '../ui/Dropdown';
|
||||||
|
import { cn } from '~/utils/';
|
||||||
|
import OpenAISettings from './OpenAI/Settings';
|
||||||
|
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
const EditPresetDialog = ({ open, onOpenChange, preset: _preset }) => {
|
||||||
|
// const [title, setTitle] = useState('My Preset');
|
||||||
|
const [preset, setPreset] = useState({});
|
||||||
|
const setPresets = useSetRecoilState(store.presets);
|
||||||
|
|
||||||
|
const availableEndpoints = useRecoilValue(store.availableEndpoints);
|
||||||
|
|
||||||
|
const setOption = param => newValue => {
|
||||||
|
let update = {};
|
||||||
|
update[param] = newValue;
|
||||||
|
setPreset(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
...update
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSettings = () => {
|
||||||
|
const { endpoint } = preset || {};
|
||||||
|
|
||||||
|
if (endpoint === 'openAI')
|
||||||
|
return (
|
||||||
|
<OpenAISettings
|
||||||
|
model={preset?.model}
|
||||||
|
setModel={setOption('model')}
|
||||||
|
chatGptLabel={preset?.chatGptLabel}
|
||||||
|
setChatGptLabel={setOption('chatGptLabel')}
|
||||||
|
promptPrefix={preset?.promptPrefix}
|
||||||
|
setPromptPrefix={setOption('promptPrefix')}
|
||||||
|
temperature={preset?.temperature}
|
||||||
|
setTemperature={setOption('temperature')}
|
||||||
|
topP={preset?.top_p}
|
||||||
|
setTopP={setOption('top_p')}
|
||||||
|
freqP={preset?.presence_penalty}
|
||||||
|
setFreqP={setOption('presence_penalty')}
|
||||||
|
presP={preset?.frequency_penalty}
|
||||||
|
setPresP={setOption('frequency_penalty')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
else return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 submitPreset = () => {
|
||||||
|
axios({
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/presets',
|
||||||
|
data: preset,
|
||||||
|
withCredentials: true
|
||||||
|
}).then(res => {
|
||||||
|
setPresets(res?.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPreset(_preset);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<DialogTemplate
|
||||||
|
title="Edit Preset"
|
||||||
|
className="max-w-full sm:max-w-4xl"
|
||||||
|
main=<div className="flex w-full flex-col items-center gap-2">
|
||||||
|
<div className="grid w-full gap-6 sm:grid-cols-2">
|
||||||
|
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="chatGptLabel"
|
||||||
|
className="text-left text-sm font-medium"
|
||||||
|
>
|
||||||
|
Preset Name
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="chatGptLabel"
|
||||||
|
value={preset?.title || ''}
|
||||||
|
onChange={e => setOption('title')(e.target.value || '')}
|
||||||
|
placeholder="Set a custom name, in case you can find 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'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="endpoint"
|
||||||
|
className="text-left text-sm font-medium"
|
||||||
|
>
|
||||||
|
Endpoint
|
||||||
|
</Label>
|
||||||
|
<Dropdown
|
||||||
|
id="endpoint"
|
||||||
|
value={preset?.endpoint || ''}
|
||||||
|
onChange={setOption('endpoint')}
|
||||||
|
options={availableEndpoints}
|
||||||
|
className={cn(
|
||||||
|
defaultTextProps,
|
||||||
|
'flex h-10 max-h-10 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>
|
||||||
|
<div className="my-4 w-full border-t border-gray-300 dark:border-gray-500" />
|
||||||
|
<div className="w-full p-0">{renderSettings()}</div>
|
||||||
|
</div>
|
||||||
|
selection={{
|
||||||
|
selectHandler: submitPreset,
|
||||||
|
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
|
||||||
|
selectText: 'Save'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditPresetDialog;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button } from './Button.tsx';
|
import { Button } from '../ui/Button.tsx';
|
||||||
import SwitchIcon from '../svg/SwitchIcon';
|
import SwitchIcon from '../svg/SwitchIcon';
|
||||||
// import SaveIcon from '../svg/SaveIcon';
|
// import SaveIcon from '../svg/SaveIcon';
|
||||||
import { Save } from 'lucide-react';
|
import { Save } from 'lucide-react';
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import ModelDropDown from './ModelDropDown';
|
import ModelDropDown from '../../ui/ModelDropDown';
|
||||||
import { Input } from '~/components/ui/Input.tsx';
|
import { Input } from '~/components/ui/Input.tsx';
|
||||||
import { Label } from '~/components/ui/Label.tsx';
|
import { Label } from '~/components/ui/Label.tsx';
|
||||||
import { Slider } from '~/components/ui/Slider.tsx';
|
import { Slider } from '~/components/ui/Slider.tsx';
|
||||||
|
|
@ -8,7 +8,7 @@ import OptionHover from './OptionHover';
|
||||||
import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx';
|
import { HoverCard, HoverCardTrigger } from '~/components/ui/HoverCard.tsx';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
const defaultTextProps =
|
const defaultTextProps =
|
||||||
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] 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-400 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
|
'rounded-md border border-gray-200 focus:border-slate-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-500 dark:bg-gray-700 focus:dark:bg-gray-600 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-400 dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0';
|
||||||
|
|
||||||
const optionText =
|
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';
|
'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';
|
||||||
|
|
@ -40,6 +40,11 @@ function Settings(props) {
|
||||||
model={model}
|
model={model}
|
||||||
setModel={setModel}
|
setModel={setModel}
|
||||||
endpoint="openAI"
|
endpoint="openAI"
|
||||||
|
className={cn(
|
||||||
|
defaultTextProps,
|
||||||
|
'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"
|
||||||
/>
|
/>
|
||||||
{/* <Label
|
{/* <Label
|
||||||
htmlFor="model"
|
htmlFor="model"
|
||||||
75
client/src/components/Endpoints/SaveAsPresetDialog.jsx
Normal file
75
client/src/components/Endpoints/SaveAsPresetDialog.jsx
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import axios from 'axios';
|
||||||
|
import DialogTemplate from '../ui/DialogTemplate';
|
||||||
|
import { Dialog } from '../ui/Dialog.tsx';
|
||||||
|
import { Input } from '../ui/Input.tsx';
|
||||||
|
import { Label } from '../ui/Label.tsx';
|
||||||
|
import { cn } from '~/utils/';
|
||||||
|
import buildPresetByConversation from '~/utils/buildPresetByConversation';
|
||||||
|
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
const SaveAsPresetDialog = ({ open, onOpenChange, conversation }) => {
|
||||||
|
const [title, setTitle] = useState('My Preset');
|
||||||
|
const setPresets = useSetRecoilState(store.presets);
|
||||||
|
|
||||||
|
const defaultTextProps =
|
||||||
|
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] 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-400 dark:bg-gray-700 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 submitPreset = () => {
|
||||||
|
const preset = buildPresetByConversation({
|
||||||
|
title,
|
||||||
|
conversation
|
||||||
|
});
|
||||||
|
|
||||||
|
axios({
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/presets',
|
||||||
|
data: preset,
|
||||||
|
withCredentials: true
|
||||||
|
}).then(res => {
|
||||||
|
setPresets(res?.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle('My Preset');
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<DialogTemplate
|
||||||
|
title="Save As Preset"
|
||||||
|
main=<div className="grid w-full items-center gap-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="chatGptLabel"
|
||||||
|
className="text-left text-sm font-medium"
|
||||||
|
>
|
||||||
|
Preset Name
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="chatGptLabel"
|
||||||
|
value={title || ''}
|
||||||
|
onChange={e => setTitle(e.target.value || '')}
|
||||||
|
placeholder="Set a custom name, in case you can find 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'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
selection={{
|
||||||
|
selectHandler: submitPreset,
|
||||||
|
selectClasses: 'bg-green-600 hover:bg-green-700 dark:hover:bg-green-800 text-white',
|
||||||
|
selectText: 'Save'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SaveAsPresetDialog;
|
||||||
|
|
@ -19,7 +19,7 @@ function BingAIOptions() {
|
||||||
const { toneStyle } = conversation;
|
const { toneStyle } = conversation;
|
||||||
|
|
||||||
const cardStyle =
|
const cardStyle =
|
||||||
'shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
'transition-colors shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 hover:border-black/10 focus:border-black/10 dark:border-black/10 dark:hover:border-black/10 dark:focus:border-black/10 border dark:bg-gray-700 text-black dark:text-white';
|
||||||
const defaultClasses =
|
const defaultClasses =
|
||||||
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
|
'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs';
|
||||||
const defaultSelected = cn(defaultClasses, 'font-medium data-[state=active]:text-white text-xs text-white');
|
const defaultSelected = cn(defaultClasses, 'font-medium data-[state=active]:text-white text-xs text-white');
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ModelItem from './ModelItem';
|
|
||||||
|
|
||||||
export default function MenuItems({ models, onSelect }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{models.map(modelItem => (
|
|
||||||
<ModelItem
|
|
||||||
key={modelItem._id}
|
|
||||||
value={modelItem.value}
|
|
||||||
onSelect={onSelect}
|
|
||||||
model={modelItem}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
import React, { useState, useRef } from 'react';
|
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
|
||||||
import manualSWR from '~/utils/fetchers';
|
|
||||||
import { Button } from '../../ui/Button.tsx';
|
|
||||||
import { Input } from '../../ui/Input.tsx';
|
|
||||||
import { Label } from '../../ui/Label.tsx';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle
|
|
||||||
} from '../../ui/Dialog.tsx';
|
|
||||||
|
|
||||||
import store from '~/store';
|
|
||||||
|
|
||||||
export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
|
||||||
const { newConversation } = store.useConversation();
|
|
||||||
|
|
||||||
const [chatGptLabel, setChatGptLabel] = useState('');
|
|
||||||
const [promptPrefix, setPromptPrefix] = useState('');
|
|
||||||
const [saveText, setSaveText] = useState('Save');
|
|
||||||
const [required, setRequired] = useState(false);
|
|
||||||
const inputRef = useRef(null);
|
|
||||||
const updateCustomGpt = manualSWR(`/api/customGpts/`, 'post');
|
|
||||||
|
|
||||||
const selectHandler = e => {
|
|
||||||
if (chatGptLabel.length === 0) {
|
|
||||||
e.preventDefault();
|
|
||||||
setRequired(true);
|
|
||||||
inputRef.current.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSaveState(chatGptLabel.toLowerCase());
|
|
||||||
|
|
||||||
// Set new conversation
|
|
||||||
newConversation({
|
|
||||||
model: 'chatgptCustom',
|
|
||||||
chatGptLabel,
|
|
||||||
promptPrefix
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveHandler = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
setModelSave(true);
|
|
||||||
const value = chatGptLabel.toLowerCase();
|
|
||||||
|
|
||||||
if (chatGptLabel.length === 0) {
|
|
||||||
setRequired(true);
|
|
||||||
inputRef.current.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCustomGpt.trigger({ value, chatGptLabel, promptPrefix });
|
|
||||||
|
|
||||||
mutate();
|
|
||||||
setSaveText(prev => prev + 'd!');
|
|
||||||
setTimeout(() => {
|
|
||||||
setSaveText('Save');
|
|
||||||
}, 2500);
|
|
||||||
|
|
||||||
// dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
|
||||||
newConversation({
|
|
||||||
model: 'chatgptCustom',
|
|
||||||
chatGptLabel,
|
|
||||||
promptPrefix
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Commented by wtlyu
|
|
||||||
// if (
|
|
||||||
// chatGptLabel !== 'chatgptCustom' &&
|
|
||||||
// modelMap[chatGptLabel.toLowerCase()] &&
|
|
||||||
// !initial[chatGptLabel.toLowerCase()] &&
|
|
||||||
// saveText === 'Save'
|
|
||||||
// ) {
|
|
||||||
// setSaveText('Update');
|
|
||||||
// } else if (!modelMap[chatGptLabel.toLowerCase()] && saveText === 'Update') {
|
|
||||||
// setSaveText('Save');
|
|
||||||
// }
|
|
||||||
|
|
||||||
const requiredProp = required ? { required: true } : {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DialogContent className="shadow-2xl dark:bg-gray-800">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="text-gray-800 dark:text-white">Customize ChatGPT</DialogTitle>
|
|
||||||
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
|
||||||
Note: important instructions are often better placed in your message rather than the prefix.{' '}
|
|
||||||
<a
|
|
||||||
href="https://platform.openai.com/docs/guides/chat/instructing-chat-models"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<u>More info here</u>
|
|
||||||
</a>
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="grid gap-4 py-4">
|
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
|
||||||
<Label
|
|
||||||
htmlFor="chatGptLabel"
|
|
||||||
className="text-right"
|
|
||||||
>
|
|
||||||
Custom Name
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="chatGptLabel"
|
|
||||||
value={chatGptLabel}
|
|
||||||
ref={inputRef}
|
|
||||||
onChange={e => setChatGptLabel(e.target.value)}
|
|
||||||
placeholder="Set a custom name for ChatGPT"
|
|
||||||
className=" col-span-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 invalid:border-red-400 invalid:text-red-600 invalid:placeholder-red-600 invalid:placeholder-opacity-70 invalid:ring-opacity-10 focus:ring-0 focus:invalid:border-red-400 focus:invalid:ring-red-300 dark:border-none dark:bg-gray-700
|
|
||||||
dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:invalid:border-red-600 dark:invalid:text-red-300 dark:invalid:placeholder-opacity-80 dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0 dark:focus:invalid:ring-red-600 dark:focus:invalid:ring-opacity-50"
|
|
||||||
{...requiredProp}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
|
||||||
<Label
|
|
||||||
htmlFor="promptPrefix"
|
|
||||||
className="text-right"
|
|
||||||
>
|
|
||||||
Prompt Prefix
|
|
||||||
</Label>
|
|
||||||
<TextareaAutosize
|
|
||||||
id="promptPrefix"
|
|
||||||
value={promptPrefix}
|
|
||||||
onChange={e => setPromptPrefix(e.target.value)}
|
|
||||||
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
|
|
||||||
className="col-span-3 flex h-20 max-h-52 w-full resize-none rounded-md border border-gray-300 bg-transparent py-2 px-3 text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] 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-none dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
<DialogClose className="dark:hover:gray-400 border-gray-700">Cancel</DialogClose>
|
|
||||||
<Button
|
|
||||||
style={{ backgroundColor: 'rgb(16, 163, 127)' }}
|
|
||||||
onClick={saveHandler}
|
|
||||||
className="inline-flex h-10 items-center justify-center rounded-md border-none py-2 px-4 text-sm font-semibold text-white transition-colors dark:text-gray-200"
|
|
||||||
>
|
|
||||||
{saveText}
|
|
||||||
</Button>
|
|
||||||
<DialogClose
|
|
||||||
onClick={selectHandler}
|
|
||||||
className="inline-flex h-10 items-center justify-center rounded-md border-none bg-gray-900 py-2 px-4 text-sm font-semibold text-white transition-colors hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900"
|
|
||||||
>
|
|
||||||
Select
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
import React, { useState, useRef } from 'react';
|
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
|
||||||
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
|
|
||||||
import { Circle } from 'lucide-react';
|
|
||||||
import { DialogTrigger } from '../../ui/Dialog.tsx';
|
|
||||||
import RenameButton from '../../Conversations/RenameButton';
|
|
||||||
import TrashIcon from '../../svg/TrashIcon';
|
|
||||||
import manualSWR from '~/utils/fetchers';
|
|
||||||
import getIcon from '~/utils/getIcon';
|
|
||||||
|
|
||||||
import store from '~/store';
|
|
||||||
|
|
||||||
export default function ModelItem({ model: _model, value, onSelect }) {
|
|
||||||
const { name, model, _id: id, chatGptLabel = null, promptPrefix = null } = _model;
|
|
||||||
const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
|
|
||||||
const currentConversation = useRecoilValue(store.conversation) || {};
|
|
||||||
|
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
|
||||||
const [renaming, setRenaming] = useState(false);
|
|
||||||
const [currentName, setCurrentName] = useState(name);
|
|
||||||
const [modelInput, setModelInput] = useState(name);
|
|
||||||
const inputRef = useRef(null);
|
|
||||||
const rename = manualSWR(`/api/customGpts`, 'post', res => {});
|
|
||||||
const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post', res => {
|
|
||||||
const fetchedModels = res.data.map(modelItem => ({
|
|
||||||
...modelItem,
|
|
||||||
name: modelItem.chatGptLabel,
|
|
||||||
model: 'chatgptCustom'
|
|
||||||
}));
|
|
||||||
|
|
||||||
setCustomGPTModels(fetchedModels);
|
|
||||||
});
|
|
||||||
|
|
||||||
const icon = getIcon({
|
|
||||||
size: 20,
|
|
||||||
sender: chatGptLabel || model,
|
|
||||||
isCreatedByUser: false,
|
|
||||||
model,
|
|
||||||
chatGptLabel,
|
|
||||||
promptPrefix,
|
|
||||||
error: false,
|
|
||||||
className: 'mr-2'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (model !== 'chatgptCustom')
|
|
||||||
// regular model
|
|
||||||
return (
|
|
||||||
<DropdownMenuRadioItem
|
|
||||||
value={value}
|
|
||||||
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{name}
|
|
||||||
{model === 'chatgpt' && <sup>$</sup>}
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
);
|
|
||||||
else if (model === 'chatgptCustom' && chatGptLabel === null && promptPrefix === null)
|
|
||||||
// base chatgptCustom model, click to add new chatgptCustom.
|
|
||||||
return (
|
|
||||||
<DialogTrigger className="w-full">
|
|
||||||
<DropdownMenuRadioItem
|
|
||||||
value={value}
|
|
||||||
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{name}
|
|
||||||
<sup>$</sup>
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
</DialogTrigger>
|
|
||||||
);
|
|
||||||
|
|
||||||
// else: a chatgptCustom model
|
|
||||||
const handleMouseOver = () => {
|
|
||||||
setIsHovering(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseOut = () => {
|
|
||||||
setIsHovering(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renameHandler = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setRenaming(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
inputRef.current.focus();
|
|
||||||
}, 25);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRename = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
setRenaming(false);
|
|
||||||
if (modelInput === name) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rename.trigger({
|
|
||||||
prevLabel: currentName,
|
|
||||||
chatGptLabel: modelInput,
|
|
||||||
value: modelInput.toLowerCase()
|
|
||||||
});
|
|
||||||
setCurrentName(modelInput);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDelete = async e => {
|
|
||||||
e.preventDefault();
|
|
||||||
await deleteCustom.trigger({ _id: id });
|
|
||||||
onSelect('chatgpt');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = e => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
onRename(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonClass = {
|
|
||||||
className:
|
|
||||||
'invisible group-hover:visible z-50 rounded-md m-0 text-gray-400 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'
|
|
||||||
};
|
|
||||||
|
|
||||||
const itemClass = {
|
|
||||||
className:
|
|
||||||
'relative flex group cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none hover:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:hover:bg-slate-700 dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
value={value}
|
|
||||||
className={itemClass.className}
|
|
||||||
onClick={e => {
|
|
||||||
if (isHovering) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSelect('chatgptCustom', value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentConversation?.chatGptLabel === value && (
|
|
||||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
||||||
<Circle className="h-2 w-2 fill-current" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{icon}
|
|
||||||
|
|
||||||
{renaming === true ? (
|
|
||||||
<input
|
|
||||||
ref={inputRef}
|
|
||||||
key={id}
|
|
||||||
type="text"
|
|
||||||
className="pointer-events-auto z-50 m-0 mr-2 w-3/4 border border-blue-500 bg-transparent p-0 text-sm leading-tight outline-none"
|
|
||||||
value={modelInput}
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
onChange={e => setModelInput(e.target.value)}
|
|
||||||
// onBlur={onRename}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className=" overflow-hidden">{modelInput}</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{value === 'chatgpt' && <sup>$</sup>}
|
|
||||||
<RenameButton
|
|
||||||
twcss={`ml-auto mr-2 ${buttonClass.className}`}
|
|
||||||
onRename={onRename}
|
|
||||||
renaming={renaming}
|
|
||||||
onMouseOver={handleMouseOver}
|
|
||||||
onMouseOut={handleMouseOut}
|
|
||||||
renameHandler={renameHandler}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
{...buttonClass}
|
|
||||||
onMouseOver={handleMouseOver}
|
|
||||||
onMouseOut={handleMouseOut}
|
|
||||||
onClick={onDelete}
|
|
||||||
>
|
|
||||||
<TrashIcon />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
// import ModelDialog from './ModelDialog';
|
import EditPresetDialog from '../../Endpoints/EditPresetDialog';
|
||||||
import EndpointItems from './EndpointItems';
|
import EndpointItems from './EndpointItems';
|
||||||
import { swr } from '~/utils/fetchers';
|
import PresetItems from './PresetItems';
|
||||||
import getIcon from '~/utils/getIcon';
|
import getIcon from '~/utils/getIcon';
|
||||||
|
|
||||||
import { Button } from '../../ui/Button.tsx';
|
import { Button } from '../../ui/Button.tsx';
|
||||||
|
|
@ -18,30 +18,22 @@ import { Dialog } from '../../ui/Dialog.tsx';
|
||||||
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function EndpointMenu() {
|
export default function NewConversationMenu() {
|
||||||
// const [modelSave, setModelSave] = useState(false);
|
// const [modelSave, setModelSave] = useState(false);
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const [presetModelVisible, setPresetModelVisible] = useState(false);
|
||||||
|
const [preset, setPreset] = useState(false);
|
||||||
|
|
||||||
// const models = useRecoilValue(store.models);
|
// const models = useRecoilValue(store.models);
|
||||||
const availableEndpoints = useRecoilValue(store.availableEndpoints);
|
const availableEndpoints = useRecoilValue(store.availableEndpoints);
|
||||||
// const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
|
// const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
|
||||||
|
const presets = useRecoilValue(store.presets);
|
||||||
|
|
||||||
const conversation = useRecoilValue(store.conversation) || {};
|
const conversation = useRecoilValue(store.conversation) || {};
|
||||||
const { endpoint, conversationId } = conversation;
|
const { endpoint, conversationId } = conversation;
|
||||||
// const { model, promptPrefix, chatGptLabel, conversationId } = conversation;
|
// const { model, promptPrefix, chatGptLabel, conversationId } = conversation;
|
||||||
const { newConversation } = store.useConversation();
|
const { newConversation } = store.useConversation();
|
||||||
|
|
||||||
// fetch the list of saved chatgptCustom
|
|
||||||
// const { data, isLoading, mutate } = swr(`/api/customGpts`, res => {
|
|
||||||
// const fetchedModels = res.map(modelItem => ({
|
|
||||||
// ...modelItem,
|
|
||||||
// name: modelItem.chatGptLabel,
|
|
||||||
// model: 'chatgptCustom'
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// setCustomGPTModels(fetchedModels);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// update the default model when availableModels changes
|
// update the default model when availableModels changes
|
||||||
// typically, availableModels changes => modelsFilter or customGPTModels changes
|
// typically, availableModels changes => modelsFilter or customGPTModels changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -56,81 +48,31 @@ export default function EndpointMenu() {
|
||||||
}, [conversation]);
|
}, [conversation]);
|
||||||
|
|
||||||
// set the current model
|
// set the current model
|
||||||
const onChange = (newEndpoint, value = null) => {
|
const onSelectEndpoint = newEndpoint => {
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
|
|
||||||
if (!newEndpoint) return;
|
if (!newEndpoint) return;
|
||||||
else if (newEndpoint === endpoint) return;
|
// else if (newEndpoint === endpoint) return;
|
||||||
else {
|
else {
|
||||||
newConversation({}, newEndpoint);
|
newConversation({}, { endpoint: newEndpoint });
|
||||||
}
|
}
|
||||||
// } else if (newModel === model && value === chatGptLabel) {
|
|
||||||
// // bypass if not changed
|
|
||||||
// return;
|
|
||||||
// } else if (newModel === 'chatgptCustom' && value === null) {
|
|
||||||
// // return;
|
|
||||||
// } else if (newModel !== 'chatgptCustom') {
|
|
||||||
// newConversation({
|
|
||||||
// model: newModel,
|
|
||||||
// chatGptLabel: null,
|
|
||||||
// promptPrefix: null
|
|
||||||
// });
|
|
||||||
// } else if (newModel === 'chatgptCustom') {
|
|
||||||
// const targetModel = models.find(element => element.value == value);
|
|
||||||
// if (targetModel) {
|
|
||||||
// const chatGptLabel = targetModel?.chatGptLabel;
|
|
||||||
// const promptPrefix = targetModel?.promptPrefix;
|
|
||||||
// newConversation({
|
|
||||||
// model: newModel,
|
|
||||||
// chatGptLabel,
|
|
||||||
// promptPrefix
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// const onOpenChange = open => {
|
// set the current model
|
||||||
// mutate();
|
const onSelectPreset = newPreset => {
|
||||||
// if (!open) {
|
setMenuOpen(false);
|
||||||
// setModelSave(false);
|
if (!newPreset) return;
|
||||||
// }
|
// else if (newEndpoint === endpoint) return;
|
||||||
// };
|
else {
|
||||||
|
newConversation({}, newPreset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// const handleSaveState = value => {
|
const onChangePreset = preset => {
|
||||||
// if (!modelSave) {
|
setPresetModelVisible(true);
|
||||||
// return;
|
setPreset(preset);
|
||||||
// }
|
};
|
||||||
|
|
||||||
// setCustomGPTModels(value);
|
|
||||||
// setModelSave(false);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const defaultColorProps = [
|
|
||||||
// 'text-gray-500',
|
|
||||||
// 'hover:bg-gray-100',
|
|
||||||
// 'hover:bg-opacity-20',
|
|
||||||
// 'disabled:hover:bg-transparent',
|
|
||||||
// 'dark:data-[state=open]:bg-gray-800',
|
|
||||||
// 'dark:hover:bg-opacity-20',
|
|
||||||
// 'dark:hover:bg-gray-900',
|
|
||||||
// 'dark:hover:text-gray-400',
|
|
||||||
// 'dark:disabled:hover:bg-transparent'
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// const chatgptColorProps = [
|
|
||||||
// 'text-green-700',
|
|
||||||
// 'data-[state=open]:bg-green-100',
|
|
||||||
// 'dark:text-emerald-300',
|
|
||||||
// 'hover:bg-green-100',
|
|
||||||
// 'disabled:hover:bg-transparent',
|
|
||||||
// 'dark:data-[state=open]:bg-green-900',
|
|
||||||
// 'dark:hover:bg-opacity-50',
|
|
||||||
// 'dark:hover:bg-green-900',
|
|
||||||
// 'dark:hover:text-gray-100',
|
|
||||||
// 'dark:disabled:hover:bg-transparent'
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
|
|
||||||
const icon = getIcon({
|
const icon = getIcon({
|
||||||
size: 32,
|
size: 32,
|
||||||
...conversation,
|
...conversation,
|
||||||
|
|
@ -157,32 +99,51 @@ export default function EndpointMenu() {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="w-56 dark:bg-gray-700"
|
className="min-w-56 dark:bg-gray-700"
|
||||||
onCloseAutoFocus={event => event.preventDefault()}
|
onCloseAutoFocus={event => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<DropdownMenuLabel className="dark:text-gray-300">Select an AI Endpoint</DropdownMenuLabel>
|
<DropdownMenuLabel className="dark:text-gray-300">Select an Endpoint</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuRadioGroup
|
<DropdownMenuRadioGroup
|
||||||
value={endpoint}
|
value={endpoint}
|
||||||
onValueChange={onChange}
|
onValueChange={onSelectEndpoint}
|
||||||
className="overflow-y-auto"
|
className="overflow-y-auto"
|
||||||
>
|
>
|
||||||
{availableEndpoints.length ? (
|
{availableEndpoints.length ? (
|
||||||
<EndpointItems
|
<EndpointItems
|
||||||
endpoints={availableEndpoints}
|
endpoints={availableEndpoints}
|
||||||
onSelect={onChange}
|
onSelect={onSelectEndpoint}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DropdownMenuLabel className="dark:text-gray-300">No endpoint available.</DropdownMenuLabel>
|
<DropdownMenuLabel className="dark:text-gray-300">No endpoint available.</DropdownMenuLabel>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
|
|
||||||
|
<div className="mt-6 w-full" />
|
||||||
|
|
||||||
|
<DropdownMenuLabel className="dark:text-gray-300">Select a Preset</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
onValueChange={onSelectPreset}
|
||||||
|
className="overflow-y-auto"
|
||||||
|
>
|
||||||
|
{presets.length ? (
|
||||||
|
<PresetItems
|
||||||
|
presets={presets}
|
||||||
|
onSelect={onSelectPreset}
|
||||||
|
onChangePreset={onChangePreset}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DropdownMenuLabel className="dark:text-gray-300">No preset yet.</DropdownMenuLabel>
|
||||||
|
)}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{/* <ModelDialog
|
<EditPresetDialog
|
||||||
mutate={mutate}
|
open={presetModelVisible}
|
||||||
setModelSave={setModelSave}
|
onOpenChange={setPresetModelVisible}
|
||||||
handleSaveState={handleSaveState}
|
preset={preset}
|
||||||
/> */}
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
65
client/src/components/Input/Endpoints/PresetItem.jsx
Normal file
65
client/src/components/Input/Endpoints/PresetItem.jsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
|
||||||
|
import EditIcon from '../../svg/EditIcon';
|
||||||
|
import getIcon from '~/utils/getIcon';
|
||||||
|
|
||||||
|
export default function PresetItem({ preset = {}, value, onSelect, onChangePreset }) {
|
||||||
|
const { endpoint } = preset;
|
||||||
|
|
||||||
|
const icon = getIcon({
|
||||||
|
size: 20,
|
||||||
|
endpoint: preset?.endpoint,
|
||||||
|
error: false,
|
||||||
|
className: 'mr-2'
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPresetTitle = () => {
|
||||||
|
let _title = `${endpoint}`;
|
||||||
|
|
||||||
|
if (endpoint === 'azureOpenAI' || endpoint === 'openAI') {
|
||||||
|
const { chatGptLabel, model } = preset;
|
||||||
|
if (model) _title += `: ${model}`;
|
||||||
|
if (chatGptLabel) _title += ` as ${chatGptLabel}`;
|
||||||
|
} else if (endpoint === 'bingAI') {
|
||||||
|
const { jailbreak, toneStyle } = preset;
|
||||||
|
if (toneStyle) _title += `: ${toneStyle}`;
|
||||||
|
if (jailbreak) _title += ` as Sydney`;
|
||||||
|
} else if (endpoint === 'chatGPTBrowser') {
|
||||||
|
const { model } = preset;
|
||||||
|
if (model) _title += `: ${model}`;
|
||||||
|
} else if (endpoint === null) {
|
||||||
|
null;
|
||||||
|
} else {
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
return _title;
|
||||||
|
};
|
||||||
|
|
||||||
|
// regular model
|
||||||
|
return (
|
||||||
|
<DropdownMenuRadioItem
|
||||||
|
value={value}
|
||||||
|
className="group dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{preset?.title}
|
||||||
|
<small className="ml-2">({getPresetTitle()})</small>
|
||||||
|
|
||||||
|
{/* <RenameButton
|
||||||
|
twcss={`ml-auto mr-2 ${buttonClass.className}`}
|
||||||
|
onRename={onRename}
|
||||||
|
renaming={renaming}
|
||||||
|
onMouseOver={handleMouseOver}
|
||||||
|
onMouseOut={handleMouseOut}
|
||||||
|
renameHandler={renameHandler}
|
||||||
|
/> */}
|
||||||
|
<div className="flex w-4 flex-1" />
|
||||||
|
<button
|
||||||
|
className="invisible m-0 rounded-md text-gray-400 hover:text-gray-700 group-hover:visible dark:text-gray-400 dark:hover:text-gray-200 "
|
||||||
|
onClick={() => onChangePreset(preset)}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
client/src/components/Input/Endpoints/PresetItems.jsx
Normal file
18
client/src/components/Input/Endpoints/PresetItems.jsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PresetItem from './PresetItem';
|
||||||
|
|
||||||
|
export default function PresetItems({ presets, onSelect, onChangePreset }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{presets.map(preset => (
|
||||||
|
<PresetItem
|
||||||
|
key={preset?.presetId}
|
||||||
|
value={preset}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onChangePreset={onChangePreset}
|
||||||
|
preset={preset}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<div className="hidden md:block px-3 pt-2 pb-1 text-center text-xs text-black/50 dark:text-white/50 md:px-4 md:pt-3 md:pb-4">
|
<div className="hidden px-3 pt-2 pb-1 text-center text-xs text-black/50 dark:text-white/50 md:block md:px-4 md:pt-3 md:pb-4">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/danny-avila/chatgpt-clone"
|
href="https://github.com/danny-avila/chatgpt-clone"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -11,8 +11,8 @@ export default function Footer() {
|
||||||
>
|
>
|
||||||
ChatGPT Clone
|
ChatGPT Clone
|
||||||
</a>
|
</a>
|
||||||
. Serves and searches all conversations reliably. All AI convos under one house. Pay per
|
. Serves and searches all conversations reliably. All AI convos under one house. Pay per call and not
|
||||||
call and not per month (cents compared to dollars).
|
per month (cents compared to dollars).
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Settings2 } from 'lucide-react';
|
import { Settings2 } from 'lucide-react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import ModelSelect from './ModelSelect';
|
import ModelSelect from '../../ui/ModelSelect';
|
||||||
import ModelDropDown from './ModelDropDown';
|
import ModelDropDown from '../../ui/ModelDropDown';
|
||||||
import EndpointOptionsPopover from '../../ui/EndpointOptionsPopover';
|
import EndpointOptionsPopover from '../../Endpoints/EndpointOptionsPopover';
|
||||||
import DialogTemplate from '../../ui/DialogTemplate';
|
import SaveAsPresetDialog from '../../Endpoints/SaveAsPresetDialog';
|
||||||
import { Button } from '../../ui/Button.tsx';
|
import { Button } from '../../ui/Button.tsx';
|
||||||
import { Dialog, DialogTrigger } from '../../ui/Dialog.tsx';
|
import Settings from '../../Endpoints/OpenAI/Settings.jsx';
|
||||||
import Settings from './Settings.jsx';
|
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
@ -16,9 +15,6 @@ function OpenAIOptions() {
|
||||||
const [advancedMode, setAdvancedMode] = useState(false);
|
const [advancedMode, setAdvancedMode] = useState(false);
|
||||||
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
|
const [saveAsDialogShow, setSaveAsDialogShow] = useState(false);
|
||||||
|
|
||||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
|
||||||
const availableModels = endpointsConfig?.['openAI']?.['availableModels'] || [];
|
|
||||||
|
|
||||||
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
const [conversation, setConversation] = useRecoilState(store.conversation) || {};
|
||||||
const { endpoint, conversationId } = conversation;
|
const { endpoint, conversationId } = conversation;
|
||||||
const { model, chatGptLabel, promptPrefix, temperature, top_p, presence_penalty, frequency_penalty } =
|
const { model, chatGptLabel, promptPrefix, temperature, top_p, presence_penalty, frequency_penalty } =
|
||||||
|
|
@ -70,7 +66,7 @@ function OpenAIOptions() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const cardStyle =
|
const cardStyle =
|
||||||
'shadow-md rounded-md min-w-[75px] font-normal bg-white 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';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -96,7 +92,10 @@ function OpenAIOptions() {
|
||||||
endpoint="openAI"
|
endpoint="openAI"
|
||||||
showAbove={true}
|
showAbove={true}
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
className="z-50 flex h-[40px] w-48 min-w-48 items-center justify-center px-4 hover:bg-slate-50 data-[state=open]:bg-slate-50 dark:bg-gray-700 dark:hover:bg-gray-600 dark:data-[state=open]:bg-gray-600"
|
className={cn(
|
||||||
|
cardStyle,
|
||||||
|
'min-w-48 z-50 flex h-[40px] w-48 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
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -134,18 +133,11 @@ function OpenAIOptions() {
|
||||||
saveAsPreset={saveAsPreset}
|
saveAsPreset={saveAsPreset}
|
||||||
switchToSimpleMode={switchToSimpleMode}
|
switchToSimpleMode={switchToSimpleMode}
|
||||||
/>
|
/>
|
||||||
<Dialog
|
<SaveAsPresetDialog
|
||||||
open={saveAsDialogShow}
|
open={saveAsDialogShow}
|
||||||
onOpenChange={setSaveAsDialogShow}
|
onOpenChange={setSaveAsDialogShow}
|
||||||
>
|
conversation={conversation}
|
||||||
<DialogTemplate
|
/>
|
||||||
title="title"
|
|
||||||
description="desc"
|
|
||||||
main="tttt"
|
|
||||||
buttons={null}
|
|
||||||
selection={{}}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import AdjustToneButton from './AdjustToneButton';
|
||||||
import OpenAIOptions from './OpenAIOptions';
|
import OpenAIOptions from './OpenAIOptions';
|
||||||
import BingAIOptions from './BingAIOptions';
|
import BingAIOptions from './BingAIOptions';
|
||||||
// import BingStyles from './BingStyles';
|
// import BingStyles from './BingStyles';
|
||||||
import EndpointMenu from './Endpoints/EndpointMenu';
|
import EndpointMenu from './Endpoints/NewConversationMenu';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { useMessageHandler } from '../../utils/handleSubmit';
|
import { useMessageHandler } from '../../utils/handleSubmit';
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ const DialogContent = React.forwardRef<
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
|
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4 text-black dark:text-white" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,18 @@ import {
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle
|
||||||
} from './Dialog.tsx';
|
} from './Dialog.tsx';
|
||||||
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
export default function DialogTemplate({ title, description, main, buttons, selection }) {
|
export default function DialogTemplate({ title, description, main, buttons, selection, className }) {
|
||||||
const { selectHandler, selectClasses, selectText } = selection;
|
const { selectHandler, selectClasses, selectText } = selection;
|
||||||
|
|
||||||
const defaultSelect = "bg-gray-900 text-white transition-colors hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900"
|
const defaultSelect =
|
||||||
|
'bg-gray-900 text-white transition-colors hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200 dark:focus:ring-gray-400 dark:focus:ring-offset-gray-900';
|
||||||
return (
|
return (
|
||||||
<DialogContent className="shadow-2xl dark:bg-gray-800">
|
<DialogContent className={cn('shadow-2xl dark:bg-gray-800', className || '')}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-gray-800 dark:text-white">{title}</DialogTitle>
|
<DialogTitle className="text-gray-800 dark:text-white">{title}</DialogTitle>
|
||||||
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
<DialogDescription className="text-gray-600 dark:text-gray-300">{description}</DialogDescription>
|
||||||
{description}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{/* <div className="grid gap-4 py-4">
|
{/* <div className="grid gap-4 py-4">
|
||||||
<div className="grid grid-cols-4 items-center gap-4"> //input template
|
<div className="grid grid-cols-4 items-center gap-4"> //input template
|
||||||
|
|
@ -44,10 +44,12 @@ export default function DialogTemplate({ title, description, main, buttons, sele
|
||||||
{main ? main : null}
|
{main ? main : null}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose className="dark:hover:gray-400 border-gray-700">Cancel</DialogClose>
|
<DialogClose className="dark:hover:gray-400 border-gray-700">Cancel</DialogClose>
|
||||||
{ buttons ? buttons : null}
|
{buttons ? buttons : null}
|
||||||
<DialogClose
|
<DialogClose
|
||||||
onClick={selectHandler}
|
onClick={selectHandler}
|
||||||
className={`${selectClasses || defaultSelect} inline-flex h-10 items-center justify-center rounded-md border-none py-2 px-4 text-sm font-semibold`}
|
className={`${
|
||||||
|
selectClasses || defaultSelect
|
||||||
|
} inline-flex h-10 items-center justify-center rounded-md border-none py-2 px-4 text-sm font-semibold`}
|
||||||
>
|
>
|
||||||
{selectText}
|
{selectText}
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
|
|
|
||||||
73
client/src/components/ui/Dropdown.jsx
Normal file
73
client/src/components/ui/Dropdown.jsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React from 'react';
|
||||||
|
import CheckMark from '../svg/CheckMark';
|
||||||
|
import { Listbox } from '@headlessui/react';
|
||||||
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
|
function Dropdown({ value, onChange, options, className, containerClassName }) {
|
||||||
|
return (
|
||||||
|
<div className={cn('flex items-center justify-center gap-2', containerClassName)}>
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Listbox
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
>
|
||||||
|
<Listbox.Button
|
||||||
|
className={cn(
|
||||||
|
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
||||||
|
className || ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="inline-flex w-full truncate">
|
||||||
|
<span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white">
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
<svg
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="h-4 w-4 text-gray-400"
|
||||||
|
height="1em"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</Listbox.Button>
|
||||||
|
<Listbox.Options className="absolute z-10 mt-2 max-h-60 w-full overflow-auto rounded bg-white text-base text-xs ring-1 ring-black/10 focus:outline-none dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 md:w-[100%]">
|
||||||
|
{options.map((item, i) => (
|
||||||
|
<Listbox.Option
|
||||||
|
key={i}
|
||||||
|
value={item}
|
||||||
|
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-900 last:border-0 hover:bg-[#ECECF1] dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-1.5 truncate">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'flex h-6 items-center gap-1 text-gray-800 dark:text-gray-100',
|
||||||
|
value === item ? 'font-semibold' : ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
{value === item && (
|
||||||
|
<span className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-800 dark:text-gray-100">
|
||||||
|
<CheckMark />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Listbox.Option>
|
||||||
|
))}
|
||||||
|
</Listbox.Options>
|
||||||
|
</Listbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dropdown;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||||
import Templates from '../Prompts/Templates';
|
import Templates from '../ui/Templates';
|
||||||
import SunIcon from '../svg/SunIcon';
|
import SunIcon from '../svg/SunIcon';
|
||||||
import LightningIcon from '../svg/LightningIcon';
|
import LightningIcon from '../svg/LightningIcon';
|
||||||
import CautionIcon from '../svg/CautionIcon';
|
import CautionIcon from '../svg/CautionIcon';
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CheckMark from '../../svg/CheckMark';
|
import CheckMark from '../svg/CheckMark';
|
||||||
import { Listbox, Transition } from '@headlessui/react';
|
import { Listbox, Transition } from '@headlessui/react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
function ModelDropDown({ model, setModel, endpoint, showAbove = false, showLabel = true, className = '' }) {
|
function ModelDropDown({
|
||||||
|
model,
|
||||||
|
setModel,
|
||||||
|
endpoint,
|
||||||
|
showAbove = false,
|
||||||
|
showLabel = true,
|
||||||
|
containerClassName,
|
||||||
|
className
|
||||||
|
}) {
|
||||||
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
const endpointsConfig = useRecoilValue(store.endpointsConfig);
|
||||||
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
|
const models = endpointsConfig?.[endpoint]?.['availableModels'] || [];
|
||||||
|
|
||||||
console.log('className', className);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className={cn('flex items-center justify-center gap-2', containerClassName)}>
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<Listbox
|
<Listbox
|
||||||
value={model}
|
value={model}
|
||||||
|
|
@ -26,6 +32,7 @@ function ModelDropDown({ model, setModel, endpoint, showAbove = false, showLabel
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{' '}
|
||||||
{showLabel && (
|
{showLabel && (
|
||||||
<Listbox.Label
|
<Listbox.Label
|
||||||
className="block text-xs text-gray-700 dark:text-gray-500"
|
className="block text-xs text-gray-700 dark:text-gray-500"
|
||||||
|
|
@ -36,7 +43,7 @@ function ModelDropDown({ model, setModel, endpoint, showAbove = false, showLabel
|
||||||
</Listbox.Label>
|
</Listbox.Label>
|
||||||
)}
|
)}
|
||||||
<span className="inline-flex w-full truncate">
|
<span className="inline-flex w-full truncate">
|
||||||
{/* {!showLabel && (
|
{/* {!showLabel && (
|
||||||
<Listbox.Label
|
<Listbox.Label
|
||||||
className="text-xs text-gray-700 dark:text-gray-500 mr-3"
|
className="text-xs text-gray-700 dark:text-gray-500 mr-3"
|
||||||
id="headlessui-listbox-label-:r1:"
|
id="headlessui-listbox-label-:r1:"
|
||||||
|
|
@ -45,14 +52,13 @@ function ModelDropDown({ model, setModel, endpoint, showAbove = false, showLabel
|
||||||
Model
|
Model
|
||||||
</Listbox.Label>
|
</Listbox.Label>
|
||||||
)} */}
|
)} */}
|
||||||
<span className={cn("flex h-6 items-center gap-1 truncate text-sm text-gray-900 dark:text-white", !showLabel ? 'text-xs' : '')}>
|
|
||||||
{!showLabel && (
|
|
||||||
<span
|
<span
|
||||||
className="text-xs text-gray-700 dark:text-gray-500"
|
className={cn(
|
||||||
|
'flex h-6 items-center gap-1 truncate text-sm text-gray-900 dark:text-white',
|
||||||
|
!showLabel ? 'text-xs' : ''
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
Model:
|
{!showLabel && <span className="text-xs text-gray-700 dark:text-gray-500">Model:</span>}
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{`${!showLabel ? '' : ''} ${model}`}
|
{`${!showLabel ? '' : ''} ${model}`}
|
||||||
{/* {`${!showLabel ? 'Model: ' : ''} ${model}`} */}
|
{/* {`${!showLabel ? 'Model: ' : ''} ${model}`} */}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -69,6 +75,7 @@ function ModelDropDown({ model, setModel, endpoint, showAbove = false, showLabel
|
||||||
height="1em"
|
height="1em"
|
||||||
width="1em"
|
width="1em"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
style={showAbove ? { transform: 'scaleY(-1)' } : {}}
|
||||||
>
|
>
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -80,7 +87,7 @@ function ModelDropDown({ model, setModel, endpoint, showAbove = false, showLabel
|
||||||
leave="transition ease-in duration-100"
|
leave="transition ease-in duration-100"
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
className={showAbove ? 'bottom-full mb-1' : 'top-full mt-1'}
|
className={showAbove ? 'bottom-full mb-3' : 'top-full mt-3'}
|
||||||
>
|
>
|
||||||
<Listbox.Options className="absolute z-10 mt-2 max-h-60 w-full overflow-auto rounded bg-white text-base text-xs ring-1 ring-black/10 focus:outline-none dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 md:w-[100%]">
|
<Listbox.Options className="absolute z-10 mt-2 max-h-60 w-full overflow-auto rounded bg-white text-base text-xs ring-1 ring-black/10 focus:outline-none dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 md:w-[100%]">
|
||||||
{models.map((modelOption, i) => (
|
{models.map((modelOption, i) => (
|
||||||
|
|
@ -110,6 +117,68 @@ function ModelDropDown({ model, setModel, endpoint, showAbove = false, showLabel
|
||||||
</Transition>
|
</Transition>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<Listbox.Button
|
||||||
|
className={cn(
|
||||||
|
'relative flex w-full cursor-default flex-col rounded-md border border-black/10 bg-white py-2 pl-3 pr-10 text-left focus:border-green-600 focus:outline-none focus:ring-1 focus:ring-green-600 dark:border-white/20 dark:bg-gray-800 sm:text-sm',
|
||||||
|
className || ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Listbox.Label
|
||||||
|
className="block text-xs text-gray-700 dark:text-gray-500"
|
||||||
|
id="headlessui-listbox-label-:r1:"
|
||||||
|
data-headlessui-state=""
|
||||||
|
>
|
||||||
|
Model
|
||||||
|
</Listbox.Label>
|
||||||
|
<span className="inline-flex w-full truncate">
|
||||||
|
<span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white">
|
||||||
|
{model}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
<svg
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="h-4 w-4 text-gray-400"
|
||||||
|
height="1em"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</Listbox.Button>
|
||||||
|
<Listbox.Options className="absolute z-10 mt-2 max-h-60 w-full overflow-auto rounded bg-white text-base text-xs ring-1 ring-black/10 focus:outline-none dark:bg-gray-800 dark:ring-white/20 dark:last:border-0 md:w-[100%]">
|
||||||
|
{models.map((modelOption, i) => (
|
||||||
|
<Listbox.Option
|
||||||
|
key={i}
|
||||||
|
value={modelOption}
|
||||||
|
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-900 last:border-0 hover:bg-[#ECECF1] dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-1.5 truncate">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'flex h-6 items-center gap-1 text-gray-800 dark:text-gray-100',
|
||||||
|
modelOption === model ? 'font-semibold' : ''
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{modelOption}
|
||||||
|
</span>
|
||||||
|
{modelOption === model && (
|
||||||
|
<span className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-800 dark:text-gray-100">
|
||||||
|
<CheckMark />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Listbox.Option>
|
||||||
|
))}
|
||||||
|
</Listbox.Options> */}
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Button } from '../../ui/Button.tsx';
|
import { Button } from './Button.tsx';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuRadioItem
|
DropdownMenuRadioItem
|
||||||
} from '../../ui/DropdownMenu.tsx';
|
} from './DropdownMenu.tsx';
|
||||||
|
|
||||||
const ModelSelect = ({ model, onChange, availableModels, ...props }) => {
|
const ModelSelect = ({ model, onChange, availableModels, ...props }) => {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
@ -27,7 +27,6 @@ import getDefaultConversation from '~/utils/getDefaultConversation';
|
||||||
// clientId: null,
|
// clientId: null,
|
||||||
// invocationId: 1,
|
// invocationId: 1,
|
||||||
// toneStyle: null,
|
// toneStyle: null,
|
||||||
// suggestions: []
|
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const conversation = atom({
|
const conversation = atom({
|
||||||
|
|
@ -62,10 +61,10 @@ const useConversation = () => {
|
||||||
|
|
||||||
const switchToConversation = useRecoilCallback(
|
const switchToConversation = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
async (_conversation, messages = null, targetEndpoint = null) => {
|
async (_conversation, messages = null, preset = null) => {
|
||||||
const prevConversation = await snapshot.getPromise(conversation);
|
const prevConversation = await snapshot.getPromise(conversation);
|
||||||
const endpointsFilter = await snapshot.getPromise(endpoints.endpointsFilter);
|
const endpointsFilter = await snapshot.getPromise(endpoints.endpointsFilter);
|
||||||
_switchToConversation(_conversation, messages, targetEndpoint, {
|
_switchToConversation(_conversation, messages, preset, {
|
||||||
endpointsFilter,
|
endpointsFilter,
|
||||||
prevConversation
|
prevConversation
|
||||||
});
|
});
|
||||||
|
|
@ -76,7 +75,7 @@ const useConversation = () => {
|
||||||
const _switchToConversation = (
|
const _switchToConversation = (
|
||||||
conversation,
|
conversation,
|
||||||
messages = null,
|
messages = null,
|
||||||
targetEndpoint = null,
|
preset = null,
|
||||||
{ endpointsFilter = {}, prevConversation = {} }
|
{ endpointsFilter = {}, prevConversation = {} }
|
||||||
) => {
|
) => {
|
||||||
let { endpoint = null } = conversation;
|
let { endpoint = null } = conversation;
|
||||||
|
|
@ -87,7 +86,7 @@ const useConversation = () => {
|
||||||
conversation,
|
conversation,
|
||||||
endpointsFilter,
|
endpointsFilter,
|
||||||
prevConversation,
|
prevConversation,
|
||||||
targetEndpoint
|
preset
|
||||||
});
|
});
|
||||||
|
|
||||||
setConversation(conversation);
|
setConversation(conversation);
|
||||||
|
|
@ -95,7 +94,7 @@ const useConversation = () => {
|
||||||
resetLatestMessage();
|
resetLatestMessage();
|
||||||
};
|
};
|
||||||
|
|
||||||
const newConversation = (template = {}, targetEndpoint = null) => {
|
const newConversation = (template = {}, preset) => {
|
||||||
switchToConversation(
|
switchToConversation(
|
||||||
{
|
{
|
||||||
conversationId: 'new',
|
conversationId: 'new',
|
||||||
|
|
@ -103,7 +102,7 @@ const useConversation = () => {
|
||||||
...template
|
...template
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
targetEndpoint
|
preset
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import user from './user';
|
||||||
import text from './text';
|
import text from './text';
|
||||||
import submission from './submission';
|
import submission from './submission';
|
||||||
import search from './search';
|
import search from './search';
|
||||||
|
import preset from './preset';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...conversation,
|
...conversation,
|
||||||
|
|
@ -15,5 +16,6 @@ export default {
|
||||||
...user,
|
...user,
|
||||||
text,
|
text,
|
||||||
...submission,
|
...submission,
|
||||||
...search
|
...search,
|
||||||
|
...preset
|
||||||
};
|
};
|
||||||
|
|
|
||||||
40
client/src/store/preset.js
Normal file
40
client/src/store/preset.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import endpoints from './endpoints';
|
||||||
|
import { atom, selector, useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
// preset structure is as same defination as conversation
|
||||||
|
// sample structure
|
||||||
|
// {
|
||||||
|
// presetId: 'new',
|
||||||
|
// title: 'New Chat',
|
||||||
|
// user: null,
|
||||||
|
// // endpoint: [azureOpenAI, openAI, bingAI, chatGPTBrowser]
|
||||||
|
// endpoint: 'azureOpenAI',
|
||||||
|
// // for azureOpenAI, openAI, chatGPTBrowser only
|
||||||
|
// model: 'gpt-3.5-turbo',
|
||||||
|
// // for azureOpenAI, openAI only
|
||||||
|
// chatGptLabel: null,
|
||||||
|
// promptPrefix: null,
|
||||||
|
// temperature: 1,
|
||||||
|
// top_p: 1,
|
||||||
|
// presence_penalty: 0,
|
||||||
|
// frequency_penalty: 0,
|
||||||
|
// // for bingAI only
|
||||||
|
// jailbreak: false,
|
||||||
|
// jailbreakConversationId: null,
|
||||||
|
// conversationSignature: null,
|
||||||
|
// clientId: null,
|
||||||
|
// invocationId: 1,
|
||||||
|
// toneStyle: null,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// an array of saved presets.
|
||||||
|
// sample structure
|
||||||
|
// [preset1, preset2, preset3]
|
||||||
|
const presets = atom({
|
||||||
|
key: 'presets',
|
||||||
|
default: []
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
presets
|
||||||
|
};
|
||||||
56
client/src/utils/buildPresetByConversation.js
Normal file
56
client/src/utils/buildPresetByConversation.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
const buildPresetByConversation = ({ title, conversation, ...others }) => {
|
||||||
|
const { endpoint } = conversation;
|
||||||
|
|
||||||
|
let preset = {};
|
||||||
|
if (endpoint === 'azureOpenAI' || endpoint === 'openAI') {
|
||||||
|
preset = {
|
||||||
|
endpoint,
|
||||||
|
model: conversation?.model || 'gpt-3.5-turbo',
|
||||||
|
chatGptLabel: conversation?.chatGptLabel || null,
|
||||||
|
promptPrefix: conversation?.promptPrefix || null,
|
||||||
|
temperature: conversation?.temperature || 1,
|
||||||
|
top_p: conversation?.top_p || 1,
|
||||||
|
presence_penalty: conversation?.presence_penalty || 0,
|
||||||
|
frequency_penalty: conversation?.frequency_penalty || 0,
|
||||||
|
title,
|
||||||
|
...others
|
||||||
|
};
|
||||||
|
} else if (endpoint === 'bingAI') {
|
||||||
|
preset = {
|
||||||
|
endpoint,
|
||||||
|
jailbreak: conversation?.jailbreak || false,
|
||||||
|
jailbreakConversationId: conversation?.jailbreakConversationId || null,
|
||||||
|
conversationSignature: null,
|
||||||
|
clientId: null,
|
||||||
|
invocationId: 1,
|
||||||
|
toneStyle: conversation?.toneStyle || 'fast',
|
||||||
|
title,
|
||||||
|
...others
|
||||||
|
};
|
||||||
|
} else if (endpoint === 'chatGPTBrowser') {
|
||||||
|
preset = {
|
||||||
|
endpoint,
|
||||||
|
model: conversation?.model || 'text-davinci-002-render-sha',
|
||||||
|
title,
|
||||||
|
...others
|
||||||
|
};
|
||||||
|
} else if (endpoint === null) {
|
||||||
|
preset = {
|
||||||
|
...conversation,
|
||||||
|
endpoint,
|
||||||
|
title,
|
||||||
|
...others
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.error(`Unknown endpoint ${endpoint}`);
|
||||||
|
preset = {
|
||||||
|
endpoint: null,
|
||||||
|
title,
|
||||||
|
...others
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return preset;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default buildPresetByConversation;
|
||||||
|
|
@ -3,14 +3,19 @@ import axios from 'axios';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import useSWRMutation from 'swr/mutation';
|
import useSWRMutation from 'swr/mutation';
|
||||||
|
|
||||||
const fetcher = (url) => fetch(url, { credentials: 'include' }).then((res) => res.json());
|
const fetcher = url => fetch(url, { credentials: 'include' }).then(res => res.json());
|
||||||
const axiosFetcher = async (url, params) => {
|
const axiosFetcher = async (url, params) => {
|
||||||
console.log(params, 'params');
|
console.log(params, 'params');
|
||||||
return axios.get(url, params);
|
return axios.get(url, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
const postRequest = async (url, { arg }) => {
|
export const postRequest = async (url, { arg }) => {
|
||||||
return await axios.post(url, { withCredentials: true, arg });
|
return await axios({
|
||||||
|
method: 'post',
|
||||||
|
url: url,
|
||||||
|
withCredentials: true,
|
||||||
|
data: { arg }
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const searchFetcher = async (pre, q, pageNumber, callback) => {
|
export const searchFetcher = async (pre, q, pageNumber, callback) => {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ const buildDefaultConversation = ({ conversation, endpoint, lastConversationSetu
|
||||||
return conversation;
|
return conversation;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDefaultConversation = ({ conversation, prevConversation, endpointsFilter, targetEndpoint }) => {
|
const getDefaultConversation = ({ conversation, prevConversation, endpointsFilter, preset }) => {
|
||||||
|
const { endpoint: targetEndpoint } = preset || {};
|
||||||
|
|
||||||
if (targetEndpoint) {
|
if (targetEndpoint) {
|
||||||
// try to use current model
|
// try to use current model
|
||||||
const endpoint = targetEndpoint;
|
const endpoint = targetEndpoint;
|
||||||
|
|
@ -52,7 +54,7 @@ const getDefaultConversation = ({ conversation, prevConversation, endpointsFilte
|
||||||
conversation = buildDefaultConversation({
|
conversation = buildDefaultConversation({
|
||||||
conversation,
|
conversation,
|
||||||
endpoint,
|
endpoint,
|
||||||
lastConversationSetup: {}
|
lastConversationSetup: preset
|
||||||
});
|
});
|
||||||
return conversation;
|
return conversation;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue