🧠 feat: Prompt caching switch, prompt query params; refactor: static cache, prompt/markdown styling, trim copied code, switch new chat to convo URL (#3784)

* refactor: Update staticCache to use oneDayInSeconds for sMaxAge and maxAge

* refactor: role updates

* style: first pass cursor

* style: Update nested list styles in style.css

* feat: setIsSubmitting to true in message handler to prevent edge case where submitting turns false during message stream

* feat: Add logic to redirect to conversation page after creating a new conversation

* refactor: Trim code string before copying in CodeBlock component

* feat: configSchema bookmarks and presets defaults

* feat: Update loadDefaultInterface to handle undefined config

* refactor: use  for compression check

* feat: first pass, query params

* fix: styling issues for prompt cards

* feat: anthropic prompt caching UI switch

* chore: Update static file cache control defaults/comments in .env.example

* ci: fix tests

* ci: fix tests

* chore:  use "submitting" class server error connection suspense fallback
This commit is contained in:
Danny Avila 2024-08-26 15:34:46 -04:00 committed by GitHub
parent bd701c197e
commit 5694ad4e55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 519 additions and 112 deletions

View file

@ -17,6 +17,7 @@ import {
useAutoSave,
useRequiresKey,
useHandleKeyUp,
useQueryParams,
useSubmitMessage,
} from '~/hooks';
import { TextareaAutosize } from '~/components/ui';
@ -37,6 +38,7 @@ import store from '~/store';
const ChatForm = ({ index = 0 }) => {
const submitButtonRef = useRef<HTMLButtonElement>(null);
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
useQueryParams({ textAreaRef });
const SpeechToText = useRecoilValue(store.speechToText);
const TextToSpeech = useRecoilValue(store.textToSpeech);
@ -61,7 +63,7 @@ const ChatForm = ({ index = 0 }) => {
const { handlePaste, handleKeyDown, handleCompositionStart, handleCompositionEnd } = useTextarea({
textAreaRef,
submitButtonRef,
disabled: !!requiresKey,
disabled: !!(requiresKey ?? false),
});
const {
@ -105,12 +107,12 @@ const ChatForm = ({ index = 0 }) => {
const invalidAssistant = useMemo(
() =>
isAssistantsEndpoint(conversation?.endpoint) &&
(!conversation?.assistant_id ||
!assistantMap[conversation.endpoint ?? ''][conversation.assistant_id ?? '']),
(!(conversation?.assistant_id ?? '') ||
!assistantMap?.[conversation?.endpoint ?? ''][conversation?.assistant_id ?? '']),
[conversation?.assistant_id, conversation?.endpoint, assistantMap],
);
const disableInputs = useMemo(
() => !!(requiresKey || invalidAssistant),
() => !!((requiresKey ?? false) || invalidAssistant),
[requiresKey, invalidAssistant],
);
@ -162,6 +164,8 @@ const ChatForm = ({ index = 0 }) => {
{endpoint && (
<TextareaAutosize
{...registerProps}
// TODO: remove autofocus due to a11y issues
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
ref={(e) => {
ref(e);

View file

@ -25,7 +25,7 @@ export const ErrorMessage = ({
<div className="text-message mb-[0.625rem] flex min-h-[20px] flex-col items-start gap-3 overflow-x-auto">
<div className="markdown prose dark:prose-invert light w-full break-words dark:text-gray-100">
<div className="absolute">
<p className="relative">
<p className="submitting relative">
<span className="result-thinking" />
</p>
</div>

View file

@ -29,6 +29,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
maxOutputTokens,
maxContextTokens,
resendFiles,
promptCache,
} = conversation ?? {};
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
{
@ -47,6 +48,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
const setTopP = setOption('topP');
const setTopK = setOption('topK');
const setResendFiles = setOption('resendFiles');
const setPromptCache = setOption('promptCache');
const setModel = (newModel: string) => {
const modelSetter = setOption('model');
@ -188,7 +190,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="temp" side={ESide.Left} />
<OptionHover endpoint={conversation.endpoint ?? ''} type="temp" side={ESide.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
@ -228,7 +230,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topp" side={ESide.Left} />
<OptionHover endpoint={conversation.endpoint ?? ''} type="topp" side={ESide.Left} />
</HoverCard>
<HoverCard openDelay={300}>
@ -269,7 +271,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
className="flex h-4 w-full"
/>
</HoverCardTrigger>
<OptionHover endpoint={conversation?.endpoint ?? ''} type="topk" side={ESide.Left} />
<OptionHover endpoint={conversation.endpoint ?? ''} type="topk" side={ESide.Left} />
</HoverCard>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
@ -310,7 +312,7 @@ export default function Settings({ conversation, setOption, models, readonly }:
/>
</HoverCardTrigger>
<OptionHover
endpoint={conversation?.endpoint ?? ''}
endpoint={conversation.endpoint ?? ''}
type="maxoutputtokens"
side={ESide.Left}
/>
@ -329,13 +331,34 @@ export default function Settings({ conversation, setOption, models, readonly }:
className="flex"
/>
<OptionHover
endpoint={conversation?.endpoint ?? ''}
endpoint={conversation.endpoint ?? ''}
type="resend"
side={ESide.Bottom}
/>
</div>
</HoverCardTrigger>
</HoverCard>
<HoverCard openDelay={500}>
<HoverCardTrigger className="grid w-full">
<div className="flex justify-between">
<Label htmlFor="prompt-cache" className="text-left text-sm font-medium">
{localize('com_endpoint_prompt_cache')}{' '}
</Label>
<Switch
id="prompt-cache"
checked={promptCache ?? true}
onCheckedChange={(checked: boolean) => setPromptCache(checked)}
disabled={readonly}
className="flex"
/>
<OptionHover
endpoint={conversation.endpoint ?? ''}
type="promptcache"
side={ESide.Bottom}
/>
</div>
</HoverCardTrigger>
</HoverCard>
</div>
</div>
);

View file

@ -26,6 +26,7 @@ const types = {
topk: 'com_endpoint_anthropic_topk',
maxoutputtokens: 'com_endpoint_anthropic_maxoutputtokens',
resend: openAI.resend,
promptcache: 'com_endpoint_anthropic_prompt_cache',
},
google: {
temp: 'com_endpoint_google_temp',
@ -44,7 +45,7 @@ const types = {
function OptionHover({ endpoint, type, side }: TOptionHoverProps) {
const localize = useLocalize();
const text = types?.[endpoint]?.[type];
const text = types[endpoint]?.[type];
if (!text) {
return null;
}

View file

@ -37,7 +37,7 @@ const CodeBar: React.FC<CodeBarProps> = React.memo(({ lang, codeRef, error, plug
const codeString = codeRef.current?.textContent;
if (codeString != null) {
setIsCopied(true);
copy(codeString, { format: 'text/plain' });
copy(codeString.trim(), { format: 'text/plain' });
setTimeout(() => {
setIsCopied(false);
@ -70,16 +70,17 @@ const CodeBlock: React.FC<CodeBlockProps> = ({
error,
}) => {
const codeRef = useRef<HTMLElement>(null);
const language = plugin || error ? 'json' : lang;
const isNonCode = !!(plugin === true || error === true);
const language = isNonCode ? 'json' : lang;
return (
<div className="w-full rounded-md bg-gray-900 text-xs text-white/80">
<CodeBar lang={lang} codeRef={codeRef} plugin={!!plugin} error={error} />
<CodeBar lang={lang} codeRef={codeRef} plugin={plugin === true} error={error} />
<div className={cn(classProp, 'overflow-y-auto p-4')}>
<code
ref={codeRef}
className={cn(
plugin || error ? '!whitespace-pre-wrap' : `hljs language-${language} !whitespace-pre`,
isNonCode ? '!whitespace-pre-wrap' : `hljs language-${language} !whitespace-pre`,
)}
>
{codeChildren}

View file

@ -61,7 +61,7 @@ export default function DashGroupItem({
};
const saveRename = () => {
updateGroup.mutate({ payload: { name: nameInputField }, id: group._id || '' });
updateGroup.mutate({ payload: { name: nameInputField }, id: group._id ?? '' });
};
const handleBlur = () => {
@ -77,13 +77,13 @@ export default function DashGroupItem({
}
};
const handleRename = (e: React.MouseEvent | React.KeyboardEvent) => {
const handleRename = (e: Event) => {
e.stopPropagation();
setNameEditFlag(true);
};
const handleDelete = () => {
deletePromptGroupMutation.mutate({ id: group._id || '' });
deletePromptGroupMutation.mutate({ id: group._id ?? '' });
};
return (
@ -156,7 +156,7 @@ export default function DashGroupItem({
</h3>
</div>
<div className="flex flex-row items-center gap-1">
{groupIsGlobal && (
{groupIsGlobal === true && (
<EarthIcon
className="icon-md text-green-400"
aria-label={localize('com_ui_global_group')}
@ -230,7 +230,7 @@ export default function DashGroupItem({
</div>
</div>
<div className="ellipsis text-balance text-sm text-gray-600 dark:text-gray-400">
{group.oneliner ? group.oneliner : group.productionPrompt?.prompt ?? ''}
{group.oneliner ?? '' ? group.oneliner : group.productionPrompt?.prompt ?? ''}
</div>
</>
)}

View file

@ -39,7 +39,7 @@ export default function List({
</div>
)}
<div className="flex-grow overflow-y-auto">
<div className="overflow-y-auto">
<div className="overflow-y-auto overflow-x-hidden">
{isLoading && isChatRoute && (
<Skeleton className="my-2 flex h-[84px] w-full rounded-2xl border-0 px-3 pb-4 pt-3" />
)}

View file

@ -28,7 +28,9 @@ export default function ListCard({
</div>
<div>{children}</div>
</div>
<div className="ellipsis select-none text-balance text-sm text-text-secondary">{snippet}</div>
<div className="ellipsis max-w-full select-none text-balance text-sm text-text-secondary">
{snippet}
</div>
</button>
);
}