diff --git a/api/models/schema/convoSchema.js b/api/models/schema/convoSchema.js index 7e11088e2d..9cb13ebc0b 100644 --- a/api/models/schema/convoSchema.js +++ b/api/models/schema/convoSchema.js @@ -9,10 +9,6 @@ const convoSchema = mongoose.Schema( index: true, meiliIndex: true }, - parentMessageId: { - type: String, - required: true - }, title: { type: String, default: 'New Chat', diff --git a/client/package.json b/client/package.json index 13610d2f23..69296c91b8 100644 --- a/client/package.json +++ b/client/package.json @@ -33,6 +33,7 @@ "axios": "^1.3.4", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", + "copy-to-clipboard": "^3.3.3", "crypto-browserify": "^3.12.0", "lodash": "^4.17.21", "lucide-react": "^0.113.0", diff --git a/client/src/components/Input/BingAIOptions/index.jsx b/client/src/components/Input/BingAIOptions/index.jsx new file mode 100644 index 0000000000..b1f5783455 --- /dev/null +++ b/client/src/components/Input/BingAIOptions/index.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { useRecoilValue, useRecoilState } from 'recoil'; +import { cn } from '~/utils'; +import { Tabs, TabsList, TabsTrigger } from '../../ui/Tabs.tsx'; + +import store from '~/store'; + +function BingAIOptions() { + const [conversation, setConversation] = useRecoilState(store.conversation) || {}; + const { endpoint, conversationId } = conversation; + + if (endpoint !== 'bingAI') return null; + if (conversationId !== 'new') return null; + + const changeHandler = value => { + setConversation(prevState => ({ ...prevState, toneStyle: value })); + }; + + const { toneStyle } = conversation; + + 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'; + const defaultClasses = + '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 selectedClass = val => val + '-tab ' + defaultSelected; + + return ( +
+ + + + {'Creative'} + + + {'Fast'} + + + {'Balanced'} + + + {'Precise'} + + + +
+ ); +} + +export default BingAIOptions; diff --git a/client/src/components/Input/OpenAIOptions/index.jsx b/client/src/components/Input/OpenAIOptions/index.jsx index 0d1050df2c..4b8f87a1a1 100644 --- a/client/src/components/Input/OpenAIOptions/index.jsx +++ b/client/src/components/Input/OpenAIOptions/index.jsx @@ -1,15 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { useSetRecoilState } from 'recoil'; +import { useRecoilState } from 'recoil'; import ModelSelect from './ModelSelect'; import { Button } from '../../ui/Button.tsx'; import Settings from './Settings.jsx'; import store from '~/store'; -function OpenAIOptions({ conversation = {} }) { - const { endpoint } = conversation; +function OpenAIOptions() { const [advancedMode, setAdvancedMode] = useState(false); - const setConversation = useSetRecoilState(store.conversation); + const [conversation, setConversation] = useRecoilState(store.conversation) || {}; + const { endpoint, conversationId } = conversation; const triggerAdvancedMode = () => setAdvancedMode(prev => !prev); @@ -48,6 +48,7 @@ function OpenAIOptions({ conversation = {} }) { }, [conversation, advancedMode]); if (endpoint !== 'openAI') return null; + if (conversationId !== 'new') return null; const { model } = conversation; @@ -84,22 +85,28 @@ function OpenAIOptions({ conversation = {} }) {
-
- Advanced settings for OpenAI endpoint - +
+
+ Advanced settings for OpenAI endpoint + +
+
-
); diff --git a/client/src/components/Input/SubmitButton.jsx b/client/src/components/Input/SubmitButton.jsx index a2d65574ca..1198a4eee2 100644 --- a/client/src/components/Input/SubmitButton.jsx +++ b/client/src/components/Input/SubmitButton.jsx @@ -1,65 +1,78 @@ import React from 'react'; +import StopGeneratingIcon from '../svg/StopGeneratingIcon'; -export default function SubmitButton({ submitMessage, disabled, isSubmitting }) { +export default function SubmitButton({ submitMessage, handleStopGenerating, disabled, isSubmitting }) { const clickHandler = e => { e.preventDefault(); submitMessage(); }; - if (isSubmitting) { + if (isSubmitting) return ( + ); + // // previous three dot animation + // return ( + // + // ); + else + return ( + ); - } - return ( - - ); } { diff --git a/client/src/components/Input/index.jsx b/client/src/components/Input/index.jsx index 3eb86caaef..c33e230da3 100644 --- a/client/src/components/Input/index.jsx +++ b/client/src/components/Input/index.jsx @@ -3,12 +3,11 @@ import { useRecoilValue, useRecoilState } from 'recoil'; import SubmitButton from './SubmitButton'; import AdjustToneButton from './AdjustToneButton'; import OpenAIOptions from './OpenAIOptions'; -import BingStyles from './BingStyles'; +import BingAIOptions from './BingAIOptions'; +// import BingStyles from './BingStyles'; import EndpointMenu from './Endpoints/EndpointMenu'; import Footer from './Footer'; import TextareaAutosize from 'react-textarea-autosize'; -import RegenerateIcon from '../svg/RegenerateIcon'; -import StopGeneratingIcon from '../svg/StopGeneratingIcon'; import { useMessageHandler } from '../../utils/handleSubmit'; import store from '~/store'; @@ -28,10 +27,10 @@ export default function TextChat({ isSearchView = false }) { // TODO: do we need this? const disabled = false; - const { ask, regenerate, stopGenerating } = useMessageHandler(); + const { ask, stopGenerating } = useMessageHandler(); - const bingStylesRef = useRef(null); - const [showBingToneSetting, setShowBingToneSetting] = useState(false); + // const bingStylesRef = useRef(null); + // const [showBingToneSetting, setShowBingToneSetting] = useState(false); const isNotAppendable = latestMessage?.cancelled || latestMessage?.error; @@ -41,32 +40,28 @@ export default function TextChat({ isSearchView = false }) { setText(''); }, [conversation?.conversationId]); - // controls the height of Bing tone style tabs - useEffect(() => { - if (!inputRef.current) { - return; // wait for the ref to be available - } + // // controls the height of Bing tone style tabs + // useEffect(() => { + // if (!inputRef.current) { + // return; // wait for the ref to be available + // } - const resizeObserver = new ResizeObserver(() => { - const newHeight = inputRef.current.clientHeight; - if (newHeight >= 24) { - // 24 is the default height of the input - bingStylesRef.current.style.bottom = 15 + newHeight + 'px'; - } - }); - resizeObserver.observe(inputRef.current); - return () => resizeObserver.disconnect(); - }, [inputRef]); + // const resizeObserver = new ResizeObserver(() => { + // const newHeight = inputRef.current.clientHeight; + // if (newHeight >= 24) { + // // 24 is the default height of the input + // // bingStylesRef.current.style.bottom = 15 + newHeight + 'px'; + // } + // }); + // resizeObserver.observe(inputRef.current); + // return () => resizeObserver.disconnect(); + // }, [inputRef]); const submitMessage = () => { ask({ text }); setText(''); }; - const handleRegenerate = () => { - if (latestMessage && !latestMessage?.isCreatedByUser) regenerate(latestMessage); - }; - const handleStopGenerating = () => { stopGenerating(); }; @@ -125,78 +120,62 @@ export default function TextChat({ isSearchView = false }) { return ''; }; - const handleBingToneSetting = () => { - setShowBingToneSetting(show => !show); - }; + // const handleBingToneSetting = () => { + // setShowBingToneSetting(show => !show); + // }; if (isSearchView) return <>; return ( <> -
-
-
- - {isSubmitting ? ( - - ) : latestMessage && !latestMessage?.isCreatedByUser ? ( - - ) : null} - - - -
- - - - {messages?.length && conversation?.model === 'sydney' ? ( - - ) : null} +
+
+ + + + +
+
+ +
+
+ + + + {/* {messages?.length && conversation?.model === 'sydney' ? ( + + ) : null} */} +
-
- -
+ +
+
); diff --git a/client/src/components/Messages/HoverButtons.jsx b/client/src/components/Messages/HoverButtons.jsx index 6820050611..100602e523 100644 --- a/client/src/components/Messages/HoverButtons.jsx +++ b/client/src/components/Messages/HoverButtons.jsx @@ -1,26 +1,74 @@ import React from 'react'; -// import Clipboard from '../svg/Clipboard'; +import Clipboard from '../svg/Clipboard'; import EditIcon from '../svg/EditIcon'; +import RegenerateIcon from '../svg/RegenerateIcon'; + +export default function HoverButtons({ + isEditting, + enterEdit, + copyToClipboard, + conversation, + isSubmitting, + message, + regenerate +}) { + const { endpoint, jailbreak = false } = conversation; + + const branchingSupported = + // azureOpenAI, openAI, chatGPTBrowser support branching, so edit enabled + !!['azureOpenAI', 'openAI', 'chatGPTBrowser'].find(e => e === endpoint) || + // Sydney in bingAI supports branching, so edit enabled + (endpoint === 'bingAI' && jailbreak); + + const editEnabled = + !message?.error && + message?.isCreatedByUser && + !message?.searchResult && + !isEditting && + branchingSupported; + + // for now, once branching is supported, regerate will be enabled + const regenerateEnabled = + !message?.error && + !message?.isCreatedByUser && + !message?.searchResult && + !isEditting && + !isSubmitting && + branchingSupported; -export default function HoverButtons({ visible, onClick, endpoint }) { - const enabled = !!['azureOpenAI', 'openAI', 'chatGPTBrowser'].find(e => e === endpoint); - console.log(enabled); return (
- {visible && enabled ? ( - <> - - + {editEnabled ? ( + ) : null} - {/* */} + {regenerateEnabled ? ( + + ) : null} + +
); } diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index b692af17fc..62d14c62ff 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil'; +import copy from 'copy-to-clipboard'; import SubRow from './Content/SubRow'; import Content from './Content/Content'; import MultiMessage from './MultiMessage'; @@ -29,7 +30,7 @@ export default function Message({ const textEditor = useRef(null); const last = !message?.children?.length; const edit = message.messageId == currentEditId; - const { ask } = useMessageHandler(); + const { ask, regenerate } = useMessageHandler(); const { switchToConversation } = store.useConversation(); const blinker = submitting && isSubmitting; @@ -87,6 +88,14 @@ export default function Message({ enterEdit(true); }; + const regenerateMessage = () => { + if (!isSubmitting && !message?.isCreatedByUser) regenerate(message); + }; + + const copyToClipboard = () => { + copy(message?.text); + }; + const clickSearchResult = async () => { if (!searchResult) return; const convoResponse = await fetchById('convos', message.conversationId); @@ -177,9 +186,13 @@ export default function Message({ )}
enterEdit()} + isEditting={edit} + isSubmitting={isSubmitting} + message={message} + conversation={conversation} + enterEdit={() => enterEdit()} + regenerate={() => regenerateMessage()} + copyToClipboard={() => copyToClipboard()} /> - - - - + stroke="currentColor" + fill="none" + strokeWidth="2" + viewBox="0 0 24 24" + strokeLinecap="round" + strokeLinejoin="round" + className="h-4 w-4" + height="1em" + width="1em" + xmlns="http://www.w3.org/2000/svg" + > + + + + ); } diff --git a/client/src/components/svg/StopGeneratingIcon.jsx b/client/src/components/svg/StopGeneratingIcon.jsx index 4c134cafe9..28af7cd0cc 100644 --- a/client/src/components/svg/StopGeneratingIcon.jsx +++ b/client/src/components/svg/StopGeneratingIcon.jsx @@ -2,6 +2,26 @@ import React from 'react'; export default function StopGeneratingIcon() { return ( - + + + ); } diff --git a/client/src/mobile.css b/client/src/mobile.css index 227563af28..8ee1e0e0a5 100644 --- a/client/src/mobile.css +++ b/client/src/mobile.css @@ -45,7 +45,7 @@ display: none; } - .resubmit-edit-button { + .hover-button { display: block; visibility: visible; }