diff --git a/client/package-lock.json b/client/package-lock.json index 2c9320b5fb..c7665d2d23 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,6 +12,7 @@ "@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.2", + "@radix-ui/react-hover-card": "^1.0.5", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-slider": "^1.1.1", "@radix-ui/react-tabs": "^1.0.3", @@ -2974,6 +2975,27 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.0.5.tgz", + "integrity": "sha512-jXRuZEkxSWdHZbVyL0J46cm7pQjmOMpwJEFKY+VqAJnV+FxS+zIZExI1OCeIiDwCBzUy6If1FfouOsfqBxr86g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.3", + "@radix-ui/react-popper": "1.1.1", + "@radix-ui/react-portal": "1.0.2", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", @@ -15802,6 +15824,23 @@ "@radix-ui/react-use-callback-ref": "1.0.0" } }, + "@radix-ui/react-hover-card": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.0.5.tgz", + "integrity": "sha512-jXRuZEkxSWdHZbVyL0J46cm7pQjmOMpwJEFKY+VqAJnV+FxS+zIZExI1OCeIiDwCBzUy6If1FfouOsfqBxr86g==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.3", + "@radix-ui/react-popper": "1.1.1", + "@radix-ui/react-portal": "1.0.2", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.0" + } + }, "@radix-ui/react-id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", diff --git a/client/package.json b/client/package.json index 547e18ee2c..13610d2f23 100644 --- a/client/package.json +++ b/client/package.json @@ -22,6 +22,7 @@ "@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.2", + "@radix-ui/react-hover-card": "^1.0.5", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-slider": "^1.1.1", "@radix-ui/react-tabs": "^1.0.3", diff --git a/client/src/components/Input/OpenAIOptions/ModelSelect.jsx b/client/src/components/Input/OpenAIOptions/ModelSelect.jsx new file mode 100644 index 0000000000..73e2a7303b --- /dev/null +++ b/client/src/components/Input/OpenAIOptions/ModelSelect.jsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import { Button } from '../../ui/Button.tsx'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuSeparator, + DropdownMenuTrigger, + DropdownMenuRadioItem +} from '../../ui/DropdownMenu.tsx'; + +const ModelSelect = ({ model, onChange, models, ...props }) => { + const [menuOpen, setMenuOpen] = useState(false); + + models = ['gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301']; + + return ( + + + + + event.preventDefault()} + > + Select a model + + + {models.map(model => ( + + {model} + + ))} + + + + ); +}; + +export default ModelSelect; diff --git a/client/src/components/Input/OpenAIOptions/OptionHover.jsx b/client/src/components/Input/OpenAIOptions/OptionHover.jsx new file mode 100644 index 0000000000..8f36468ee9 --- /dev/null +++ b/client/src/components/Input/OpenAIOptions/OptionHover.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { + // HoverCard, + // HoverCardTrigger, + HoverCardPortal, + HoverCardContent +} from '~/components/ui/HoverCard.tsx'; + +// import { cn } from '~/utils/'; + +const types = { + temp: { + description: + 'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.' + }, + max: { + description: + 'The max tokens to generate. The total length of input tokens and generated tokens is limited by the model\'s context length.' + }, + 'top-p': { + description: + 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.' + }, + freq: { + description: + 'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.' + }, + pres: { + description: + 'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.' + }, +}; + +function OptionHover({ type, side }) { + + const options = {}; + + if (type === 'pres') { + options.sideOffset = 45; + } + + return ( + + +
+

{types[type].description}

+
+
+
+ ); +} + +export default OptionHover; diff --git a/client/src/components/Input/OpenAIOptions/Settings.jsx b/client/src/components/Input/OpenAIOptions/Settings.jsx index 60d06ec1fe..6ea499464d 100644 --- a/client/src/components/Input/OpenAIOptions/Settings.jsx +++ b/client/src/components/Input/OpenAIOptions/Settings.jsx @@ -3,17 +3,24 @@ import TextareaAutosize from 'react-textarea-autosize'; import { Input } from '~/components/ui/Input.tsx'; import { Label } from '~/components/ui/Label.tsx'; import { Slider } from '~/components/ui/Slider.tsx'; +import OptionHover from './OptionHover'; +import { + HoverCard, + HoverCardTrigger + // HoverCardContent, +} from '~/components/ui/HoverCard.tsx'; import { cn } from '~/utils/'; 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-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'; -const optionText = 'p-0 shadow-none text-right pr-1 h-8'; +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'; function Settings() { const [chatGptLabel, setChatGptLabel] = useState(''); const [promptPrefix, setPromptPrefix] = useState(''); const [temperature, setTemperature] = useState(1); - const [maxLength, setMaxLength] = useState(2048); + const [maxTokens, setMaxTokens] = useState(2048); const [topP, setTopP] = useState(1); const [freqP, setFreqP] = useState(0); const [presP, setPresP] = useState(0); @@ -32,151 +39,216 @@ function Settings() { More info here */} -
-
- - 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 dark:bg-gray-700 + <> +
+
+ + 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 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)]" - /> -
-
- - setPromptPrefix(e.target.value)} - placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" - className={cn(defaultTextProps, 'col-span-3 flex h-10 max-h-10 w-full resize-none px-3 py-2 ')} - onFocus={() => { - textareaRef.current.classList.remove('max-h-10'); - textareaRef.current.classList.add('max-h-52'); - }} - onBlur={() => { - textareaRef.current.classList.remove('max-h-52'); - textareaRef.current.classList.add('max-h-10'); - }} - ref={textareaRef} - /> -
-
-
- - setTemperature(e.target.value)} - placeholder="1.0" - className={cn(defaultTextProps, `w-9 ${optionText}`)} />
-
+
- setMaxLength(e.target.value)} - placeholder="1.0" - className={cn(defaultTextProps, `w-11 ${optionText}`)} + setPromptPrefix(e.target.value)} + placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" + className={cn(defaultTextProps, 'col-span-3 flex h-10 max-h-10 w-full resize-none px-3 py-2 ')} + onFocus={() => { + textareaRef.current.classList.remove('max-h-10'); + textareaRef.current.classList.add('max-h-52'); + }} + onBlur={() => { + textareaRef.current.classList.remove('max-h-52'); + textareaRef.current.classList.add('max-h-10'); + }} + ref={textareaRef} />
-
+
+ + + {/*
*/} + + setTemperature(e.target.value)} + className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/temp:border-gray-200'))} + /> + {/*
*/} +
+ +
+ {/*
*/} + + + + setMaxTokens(e.target.value)} + className={cn(defaultTextProps, cn(optionText, 'w-11 group-hover/max:border-gray-200'))} + /> + + + + {/*
*/} +
-
- setTemperature(value)} - max={2} - min={0} - step={0.01} - className="w-full" - /> - setMaxLength(value)} - max={2048} - min={1} - step={1} - className="w-full" - /> -
- -
-
- - setTopP(e.target.value)} - placeholder="1.0" - className={cn(defaultTextProps, `w-9 ${optionText}`)} +
+ setTemperature(value)} + max={2} + min={0} + step={0.01} + className="w-full" + /> + setMaxTokens(value)} + max={2048} // should be dynamic to the currently selected model + min={1} + step={1} + className="w-full" />
-
- - setFreqP(e.target.value)} - placeholder="1.0" - className={cn(defaultTextProps, `w-9 ${optionText}`)} +
+ + + + setTopP(e.target.value)} + className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/top:border-gray-200'))} + /> + + + + + + + setFreqP(e.target.value)} + className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/freq:border-gray-200'))} + /> + + + +
+
+ setTopP(value)} + max={1} + min={0} + step={0.01} + className="w-full" + /> + setFreqP(value)} + max={2} + min={-2} + step={0.01} + className="w-full" + /> +
+
+ + + + setPresP(e.target.value)} + className={cn(defaultTextProps, cn(optionText, 'w-10 group-hover/pres:border-gray-200'))} + /> + + + +
+
+ setPresP(value)} + max={2} + min={0} + step={0.01} + className="w-full opacity-0" + /> + setPresP(value)} + max={2} + min={-2} + step={0.01} + className="w-full" />
-
- setTopP(value)} - max={2} - min={0} - step={0.01} - className="w-full" - /> - setFreqP(value)} - max={2} - min={0} - step={0.01} - className="w-full" - /> -
-
+
); } diff --git a/client/src/components/Input/OpenAIOptions/index.jsx b/client/src/components/Input/OpenAIOptions/index.jsx index 04138e6d77..0d1050df2c 100644 --- a/client/src/components/Input/OpenAIOptions/index.jsx +++ b/client/src/components/Input/OpenAIOptions/index.jsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect, forwardRef } from 'react'; -import { Tabs, TabsList, TabsTrigger } from '../../ui/Tabs.tsx'; -import { useRecoilValue, useRecoilState } from 'recoil'; +import React, { useEffect, useState } from 'react'; +import { useSetRecoilState } from 'recoil'; +import ModelSelect from './ModelSelect'; import { Button } from '../../ui/Button.tsx'; import Settings from './Settings.jsx'; @@ -8,29 +8,75 @@ import store from '~/store'; function OpenAIOptions({ conversation = {} }) { const { endpoint } = conversation; - const { advancedMode, setAdvancedMode } = useState(false); + const [advancedMode, setAdvancedMode] = useState(false); + const setConversation = useSetRecoilState(store.conversation); const triggerAdvancedMode = () => setAdvancedMode(prev => !prev); + const switchToSimpleMode = () => { + setConversation(prevState => ({ + ...prevState, + chatGptLabel: null, + promptPrefix: null, + temperature: 0.8, + top_p: 1, + presence_penalty: 1 + })); + setAdvancedMode(false); + }; + + const setModel = newModel => { + setConversation(prevState => ({ + ...prevState, + model: newModel + })); + }; + + useEffect(() => { + const { endpoint, chatGptLabel, promptPrefix, temperature, top_p, presence_penalty } = conversation; + + if (endpoint !== 'openAI') return; + + const mustInAdvancedMode = + chatGptLabel !== null || + promptPrefix !== null || + temperature !== 0.8 || + top_p !== 1 || + presence_penalty !== 1; + + if (mustInAdvancedMode && !advancedMode) setAdvancedMode(true); + }, [conversation, advancedMode]); + if (endpoint !== 'openAI') return null; const { model } = conversation; const cardStyle = - 'shadow-md px-4 h-[40px] rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white'; + 'shadow-md rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white'; return ( <> -
+
+ - +
+
); diff --git a/client/src/components/ui/HoverCard.tsx b/client/src/components/ui/HoverCard.tsx new file mode 100644 index 0000000000..c5282c135c --- /dev/null +++ b/client/src/components/ui/HoverCard.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as HoverCardPrimitive from "@radix-ui/react-hover-card" + +import { cn } from "../../utils" + +const HoverCard = HoverCardPrimitive.Root + +const HoverCardTrigger = HoverCardPrimitive.Trigger + +const HoverCardPortal = HoverCardPrimitive.Portal + +const HoverCardContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 6, ...props }, ref) => ( + +)) +HoverCardContent.displayName = HoverCardPrimitive.Content.displayName + +export { HoverCard, HoverCardTrigger, HoverCardContent, HoverCardPortal } diff --git a/client/src/components/ui/Slider.tsx b/client/src/components/ui/Slider.tsx index e522d0c7a7..64d196e4ab 100644 --- a/client/src/components/ui/Slider.tsx +++ b/client/src/components/ui/Slider.tsx @@ -17,10 +17,10 @@ const Slider = React.forwardRef< )} {...props} > - - + + - + )) Slider.displayName = SliderPrimitive.Root.displayName diff --git a/client/src/style.css b/client/src/style.css index 86e343e63c..4d143cd4d2 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -28,6 +28,31 @@ transition: all 1s ease-in-out; } */ +.openAIOptions-simple-container { + pointer-events: none; + opacity: 0; + transition: all 0.5s ease-in-out; +} + +.openAIOptions-simple-container.show { + pointer-events: fill; + opacity: 1; +} + +.openAIOptions-advanced-container { + pointer-events: none; + opacity: 0; + transition: all 0.2s ease-in-out; + transform: scaleY(0); + transform-origin: bottom center; +} + +.openAIOptions-advanced-container.show { + pointer-events: fill; + opacity: 1; + transform: scaleY(1) +} + .bing-styles { position: absolute; left: 0;