diff --git a/api/models/CustomGpt.js b/api/models/CustomGpt.js
index 7df5f45f5f..33bb75b124 100644
--- a/api/models/CustomGpt.js
+++ b/api/models/CustomGpt.js
@@ -60,6 +60,17 @@ module.exports = {
return { message: 'Error updating customGpt' };
}
},
+ updateByLabel: async ({ prevLabel, ...update }) => {
+ try {
+ return await CustomGpt.findOneAndUpdate({ chatGptLabel: prevLabel }, update, {
+ new: true,
+ upsert: true
+ }).exec();
+ } catch (error) {
+ console.log(error);
+ return { message: 'Error updating customGpt' };
+ }
+ },
deleteCustomGpts: async (filter) => {
try {
return await CustomGpt.deleteMany(filter).exec();
diff --git a/api/models/index.js b/api/models/index.js
index 7b77a7c79e..569cc5cd29 100644
--- a/api/models/index.js
+++ b/api/models/index.js
@@ -1,5 +1,5 @@
const { saveMessage, deleteMessages } = require('./Message');
-const { getCustomGpts, updateCustomGpt, deleteCustomGpts } = require('./CustomGpt');
+const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt');
const { saveConvo } = require('./Conversation');
module.exports = {
@@ -8,5 +8,6 @@ module.exports = {
saveConvo,
getCustomGpts,
updateCustomGpt,
+ updateByLabel,
deleteCustomGpts
};
diff --git a/api/server/routes/customGpts.js b/api/server/routes/customGpts.js
index d99ecb9062..a27b640842 100644
--- a/api/server/routes/customGpts.js
+++ b/api/server/routes/customGpts.js
@@ -1,6 +1,6 @@
const express = require('express');
const router = express.Router();
-const { getCustomGpts, updateCustomGpt, deleteCustomGpts } = require('../../models');
+const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('../../models');
router.get('/', async (req, res) => {
const models = (await getCustomGpts()).map(model => {
@@ -8,20 +8,14 @@ router.get('/', async (req, res) => {
model._id = model._id.toString();
return model;
});
- // console.log(models);
res.status(200).send(models);
});
-router.post('/delete/:_id', async (req, res) => {
- const { _id } = req.params;
- let filter = {};
-
- if (_id) {
- filter = { _id };
- }
+router.post('/delete', async (req, res) => {
+ const { arg } = req.body;
try {
- const dbResponse = await deleteCustomGpts(filter);
+ const dbResponse = await deleteCustomGpts(arg);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
@@ -44,8 +38,14 @@ router.post('/delete/:_id', async (req, res) => {
router.post('/', async (req, res) => {
const update = req.body.arg;
+ let setter = updateCustomGpt;
+
+ if (update.prevLabel) {
+ setter = updateByLabel;
+ }
+
try {
- const dbResponse = await updateCustomGpt(update);
+ const dbResponse = await setter(update);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
diff --git a/client/src/components/Models/MenuItems.jsx b/client/src/components/Models/MenuItems.jsx
index 399dd9e87f..aa899e8cf3 100644
--- a/client/src/components/Models/MenuItems.jsx
+++ b/client/src/components/Models/MenuItems.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import ModelItem from './ModelItem';
-export default function MenuItems({ models }) {
+export default function MenuItems({ models, onSelect }) {
return (
<>
{models.map((modelItem, i) => (
@@ -9,6 +9,7 @@ export default function MenuItems({ models }) {
key={i}
modelName={modelItem.name}
value={modelItem.value}
+ onSelect={onSelect}
/>
))}
>
diff --git a/client/src/components/Models/ModelDialog.jsx b/client/src/components/Models/ModelDialog.jsx
index d7dd1a9321..93005976e3 100644
--- a/client/src/components/Models/ModelDialog.jsx
+++ b/client/src/components/Models/ModelDialog.jsx
@@ -1,6 +1,6 @@
import React, { useState, useRef } from 'react';
import TextareaAutosize from 'react-textarea-autosize';
-import { useDispatch } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import { setModel, setCustomGpt } from '~/store/submitSlice';
import manualSWR from '~/utils/fetchers';
import { Button } from '../ui/Button.tsx';
@@ -16,8 +16,9 @@ import {
DialogTitle
} from '../ui/Dialog.tsx';
-export default function ModelDialog({ mutate, modelMap, setModelSave, handleSaveState }) {
+export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
const dispatch = useDispatch();
+ const { modelMap, initial } = useSelector((state) => state.models);
const [chatGptLabel, setChatGptLabel] = useState('');
const [promptPrefix, setPromptPrefix] = useState('');
const [saveText, setSaveText] = useState('Save');
@@ -65,9 +66,12 @@ export default function ModelDialog({ mutate, modelMap, setModelSave, handleSave
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 } : {};
diff --git a/client/src/components/Models/ModelItem.jsx b/client/src/components/Models/ModelItem.jsx
index b9d8218455..8b7d8d082d 100644
--- a/client/src/components/Models/ModelItem.jsx
+++ b/client/src/components/Models/ModelItem.jsx
@@ -1,13 +1,22 @@
-import React, { useState } from 'react';
+import React, { useState, useRef } from 'react';
import { useSelector } from 'react-redux';
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';
-export default function ModelItem({ modelName, value }) {
+export default function ModelItem({ modelName, value, onSelect }) {
+ const { customModel } = useSelector((state) => state.submit);
const { initial } = useSelector((state) => state.models);
const [isHovering, setIsHovering] = useState(false);
+ const [renaming, setRenaming] = useState(false);
+ const [currentName, setCurrentName] = useState(modelName);
+ const [modelInput, setModelInput] = useState(modelName);
+ const inputRef = useRef(null);
+ const rename = manualSWR(`http://localhost:3080/api/customGpts`, 'post');
+ const deleteCustom = manualSWR(`http://localhost:3080/api/customGpts/delete`, 'post');
if (value === 'chatgptCustom') {
return (
@@ -23,6 +32,18 @@ export default function ModelItem({ modelName, value }) {
);
}
+ if (initial[value]) {
+ return (
+
+ {modelName}
+ {value === 'chatgpt' && $}
+
+ );
+ }
+
const handleMouseOver = () => {
setIsHovering(true);
};
@@ -31,31 +52,101 @@ export default function ModelItem({ modelName, value }) {
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 === modelName) {
+ return;
+ }
+ rename.trigger({
+ prevLabel: currentName,
+ chatGptLabel: modelInput,
+ value: modelInput.toLowerCase()
+ });
+ setCurrentName(modelInput);
+ };
+
+ const onDelete = async (e) => {
+ e.preventDefault();
+ await deleteCustom.trigger({ value: currentName.toLowerCase() });
+ // await mutate();
+ onSelect('chatgpt', true);
+ };
+
+ const handleKeyDown = (e) => {
+ if (e.key === 'Enter') {
+ onRename(e);
+ }
+ };
+
const buttonClass = {
className:
- 'rounded-md m-0 text-gray-400 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'
+ '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 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'
};
const showButtons = isHovering && !initial[value];
return (
- {
+ onSelect(value, true);
+ }}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
>
- {modelName}
- {value === 'chatgpt' && $}
+ {customModel === value && (
+
+
+
+ )}
+ {renaming === true ? (
+ e.stopPropagation()}
+ onChange={(e) => setModelInput(e.target.value)}
+ onBlur={onRename}
+ onKeyDown={handleKeyDown}
+ />
+ ) : (
+ modelInput
+ )}
+ {value === 'chatgpt' && $}
{showButtons && (
<>
-
-
+
);
}
diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx
index 832d95ace0..80b5dd9dc3 100644
--- a/client/src/components/Models/ModelMenu.jsx
+++ b/client/src/components/Models/ModelMenu.jsx
@@ -24,13 +24,10 @@ import { Dialog } from '../ui/Dialog.tsx';
export default function ModelMenu() {
const dispatch = useDispatch();
const [modelSave, setModelSave] = useState(false);
+ const [menuOpen, setMenuOpen] = useState(false);
const { model, customModel } = useSelector((state) => state.submit);
const { models, modelMap, initial } = useSelector((state) => state.models);
const { trigger } = manualSWR(`http://localhost:3080/api/customGpts`, 'get', (res) => {
- if (models.length + res.length === models.length) {
- return;
- }
-
const fetchedModels = res.map((modelItem) => ({
...modelItem,
name: modelItem.chatGptLabel
@@ -53,7 +50,7 @@ export default function ModelMenu() {
localStorage.setItem('model', JSON.stringify(model));
}, [model]);
- const onChange = (value) => {
+ const onChange = (value, custom = false) => {
if (!value) {
return;
} else if (value === 'chatgptCustom') {
@@ -62,12 +59,18 @@ export default function ModelMenu() {
dispatch(setModel(value));
dispatch(setDisabled(false));
dispatch(setCustomModel(null));
+ if (custom) {
+ trigger();
+ }
} else if (!initial[value]) {
const chatGptLabel = modelMap[value]?.chatGptLabel;
const promptPrefix = modelMap[value]?.promptPrefix;
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
dispatch(setModel('chatgptCustom'));
dispatch(setCustomModel(value));
+ if (custom) {
+ setMenuOpen((prevOpen) => !prevOpen);
+ }
} else if (!modelMap[value]) {
dispatch(setCustomModel(null));
}
@@ -101,7 +104,9 @@ export default function ModelMenu() {
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',
@@ -110,9 +115,11 @@ export default function ModelMenu() {
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',
@@ -124,14 +131,17 @@ export default function ModelMenu() {
return (