diff --git a/api/server/utils/handleText.js b/api/server/utils/handleText.js
index b8d1710662..3cb5bfa148 100644
--- a/api/server/utils/handleText.js
+++ b/api/server/utils/handleText.js
@@ -1,7 +1,6 @@
const partialRight = require('lodash/partialRight');
const { sendMessage } = require('./streamResponse');
const { getCitations, citeText } = require('./citations');
-const cursor = '█';
const citationRegex = /\[\^\d+?\^]/g;
const addSpaceIfNeeded = (text) => (text.length > 0 && !text.endsWith(' ') ? text + ' ' : text);
@@ -51,7 +50,7 @@ const createOnProgress = ({ generation = '', onProgress: _onProgress }) => {
const sendIntermediateMessage = (res, payload, extraTokens = '') => {
tokens += extraTokens;
sendMessage(res, {
- text: tokens?.length === 0 ? cursor : tokens,
+ text: tokens?.length === 0 ? '' : tokens,
message: true,
initial: i === 0,
...payload,
diff --git a/client/src/components/Chat/Messages/Content/Markdown.tsx b/client/src/components/Chat/Messages/Content/Markdown.tsx
index b32d62b4d8..e1d0268b5d 100644
--- a/client/src/components/Chat/Messages/Content/Markdown.tsx
+++ b/client/src/components/Chat/Messages/Content/Markdown.tsx
@@ -1,14 +1,14 @@
-import { useRecoilValue } from 'recoil';
-import React, { useState, useEffect } from 'react';
-import type { TMessage } from 'librechat-data-provider';
-import rehypeHighlight from 'rehype-highlight';
-import type { PluggableList } from 'unified';
-import ReactMarkdown from 'react-markdown';
-import supersub from 'remark-supersub';
-import rehypeKatex from 'rehype-katex';
-import remarkMath from 'remark-math';
+import { memo } from 'react';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
+import remarkMath from 'remark-math';
+import supersub from 'remark-supersub';
+import rehypeKatex from 'rehype-katex';
+import { useRecoilValue } from 'recoil';
+import ReactMarkdown from 'react-markdown';
+import rehypeHighlight from 'rehype-highlight';
+import type { TMessage } from 'librechat-data-provider';
+import type { PluggableList } from 'unified';
import CodeBlock from '~/components/Messages/Content/CodeBlock';
import { langSubset, validateIframe, processLaTeX } from '~/utils';
import { useChatContext } from '~/Providers';
@@ -26,7 +26,7 @@ type TContentProps = {
showCursor?: boolean;
};
-const code = React.memo(({ inline, className, children }: TCodeProps) => {
+const code = memo(({ inline, className, children }: TCodeProps) => {
const match = /language-(\w+)/.exec(className || '');
const lang = match && match[1];
@@ -37,48 +37,25 @@ const code = React.memo(({ inline, className, children }: TCodeProps) => {
}
});
-const p = React.memo(({ children }: { children: React.ReactNode }) => {
+const p = memo(({ children }: { children: React.ReactNode }) => {
return
{children}
;
});
-const Markdown = React.memo(({ content, message, showCursor }: TContentProps) => {
- const [cursor, setCursor] = useState('█');
+const cursor = ' ⬤';
+const Markdown = memo(({ content, message, showCursor }: TContentProps) => {
const { isSubmitting, latestMessage } = useChatContext();
const LaTeXParsing = useRecoilValue(store.LaTeXParsing);
- const isInitializing = content === '█';
+ const isInitializing = content === '';
const { isEdited, messageId } = message ?? {};
const isLatestMessage = messageId === latestMessage?.messageId;
- const _content = content?.replace('z-index: 1;', '') ?? '';
- const currentContent = LaTeXParsing ? processLaTeX(_content) : _content;
-
- useEffect(() => {
- let timer1: NodeJS.Timeout, timer2: NodeJS.Timeout;
-
- if (!showCursor) {
- setCursor('ㅤ');
- return;
- }
-
- if (isSubmitting && isLatestMessage) {
- timer1 = setInterval(() => {
- setCursor('ㅤ');
- timer2 = setTimeout(() => {
- setCursor('█');
- }, 200);
- }, 1000);
- } else {
- setCursor('ㅤ');
- }
-
- // This is the cleanup function that React will run when the component unmounts
- return () => {
- clearInterval(timer1);
- clearTimeout(timer2);
- };
- }, [isSubmitting, isLatestMessage, showCursor]);
+ let currentContent = content;
+ if (!isInitializing) {
+ currentContent = currentContent?.replace('z-index: 1;', '') ?? '';
+ currentContent = LaTeXParsing ? processLaTeX(currentContent) : currentContent;
+ }
const rehypePlugins: PluggableList = [
[rehypeKatex, { output: 'mathml' }],
@@ -93,6 +70,11 @@ const Markdown = React.memo(({ content, message, showCursor }: TContentProps) =>
[rehypeRaw],
];
+ if (isInitializing) {
+ rehypePlugins.pop();
+ return ;
+ }
+
let isValidIframe: string | boolean | null = false;
if (!isEdited) {
isValidIframe = validateIframe(currentContent);
@@ -116,7 +98,7 @@ const Markdown = React.memo(({ content, message, showCursor }: TContentProps) =>
}
}
>
- {isLatestMessage && isSubmitting && !isInitializing
+ {isLatestMessage && isSubmitting && !isInitializing && showCursor
? currentContent + cursor
: currentContent}
diff --git a/client/src/hooks/useChatHelpers.ts b/client/src/hooks/useChatHelpers.ts
index fd48524631..c807669268 100644
--- a/client/src/hooks/useChatHelpers.ts
+++ b/client/src/hooks/useChatHelpers.ts
@@ -179,9 +179,7 @@ export default function useChatHelpers(index = 0, paramId: string | undefined) {
// construct the placeholder response message
const generation = editedText ?? latestMessage?.text ?? '';
- const responseText = isEditOrContinue
- ? generation
- : '█';
+ const responseText = isEditOrContinue ? generation : '';
const responseMessageId = editedMessageId ?? latestMessage?.messageId ?? null;
const initialResponse: TMessage = {
diff --git a/client/src/hooks/useMessageHandler.ts b/client/src/hooks/useMessageHandler.ts
index b39962a252..63757f9fbb 100644
--- a/client/src/hooks/useMessageHandler.ts
+++ b/client/src/hooks/useMessageHandler.ts
@@ -87,9 +87,7 @@ const useMessageHandler = () => {
// construct the placeholder response message
const generation = editedText ?? latestMessage?.text ?? '';
- const responseText = isEditOrContinue
- ? generation
- : '█';
+ const responseText = isEditOrContinue ? generation : '';
const responseMessageId = editedMessageId ?? latestMessage?.messageId ?? null;
const initialResponse: TMessage = {
@@ -99,7 +97,6 @@ const useMessageHandler = () => {
messageId: responseMessageId ?? `${isRegenerate ? messageId : fakeMessageId}_`,
conversationId,
unfinished: false,
- submitting: true,
isCreatedByUser: false,
isEdited: isEditOrContinue,
error: false,
diff --git a/client/src/hooks/useSSE.ts b/client/src/hooks/useSSE.ts
index 13e04935b1..a59f330454 100644
--- a/client/src/hooks/useSSE.ts
+++ b/client/src/hooks/useSSE.ts
@@ -332,10 +332,6 @@ export default function useSSE(submission: TSubmission | null, index = 0) {
} else if (response.status === 204) {
const responseMessage = {
...submission.initialResponse,
- text: submission.initialResponse.text.replace(
- '█',
- '',
- ),
};
return {
diff --git a/client/src/style.css b/client/src/style.css
index 4a30683c8f..30e5589507 100644
--- a/client/src/style.css
+++ b/client/src/style.css
@@ -1314,7 +1314,7 @@ html {
.result-streaming {
-webkit-animation: blink 1s steps(5, start) infinite;
animation: blink 1s steps(5, start) infinite;
- content:"▋";
+ content:"⬤ ";
margin-left: 0.25rem;
vertical-align: baseline;
}
@@ -1777,4 +1777,49 @@ html {
.border-token-surface-tertiary {
border-color: #ececf1;
border-color: var(--surface-tertiary)
+}
+
+@-webkit-keyframes pulseSize {
+ 0%,
+ to {
+ -webkit-transform:scaleX(1);
+ transform:scaleX(1)
+ }
+ 50% {
+ -webkit-transform:scale3d(1.25,1.25,1);
+ transform:scale3d(1.25,1.25,1)
+ }
+}
+@keyframes pulseSize {
+ 0%,
+ to {
+ -webkit-transform:scaleX(1);
+ transform:scaleX(1)
+ }
+ 50% {
+ -webkit-transform:scale3d(1.25,1.25,1);
+ transform:scale3d(1.25,1.25,1)
+ }
+}
+.result-thinking:empty:last-child:after {
+ -webkit-font-smoothing:subpixel-antialiased;
+ -webkit-animation:pulseSize 1.25s ease-in-out infinite;
+ animation:pulseSize 1.25s ease-in-out infinite;
+ -webkit-backface-visibility:hidden;
+ backface-visibility:hidden;
+ background-color:#0d0d0d;
+ background-color:var(--text-primary);
+ border-radius:50%;
+ box-sizing:border-box;
+ content:" ";
+ display:block;
+ height:12px;
+ position:absolute;
+ top:32px;
+ -webkit-transform:translateZ(0);
+ transform:translateZ(0);
+ -webkit-transform-origin:center;
+ transform-origin:center;
+ width:12px;
+ will-change:transform
}
\ No newline at end of file
diff --git a/client/src/utils/convos.spec.ts b/client/src/utils/convos.spec.ts
index 1d21d8654c..1ec1c00b1e 100644
--- a/client/src/utils/convos.spec.ts
+++ b/client/src/utils/convos.spec.ts
@@ -152,7 +152,7 @@ describe('Conversation Utilities with Fake Data', () => {
const allConversations = pages.flatMap((p) => p.conversations);
const grouped = groupConversationsByDate(allConversations);
- expect(grouped).toHaveLength(1);
+ expect(grouped).toHaveLength(2);
expect(grouped[0][1]).toBeInstanceOf(Array);
});
});