`;
-exports[`Test Chat Prompt Match Snapshots: empty 1`] = `
-
-
-
-`;
+exports[`Test Chat Prompt Match Snapshots: empty 1`] = `
`;
+
+exports[`Test Chat Prompt Match Snapshots: empty title 1`] = `
`;
diff --git a/src/chat/__tests__/prompt.test.tsx b/src/chat/__tests__/prompt.test.tsx
index cd4f87346..7f487844c 100644
--- a/src/chat/__tests__/prompt.test.tsx
+++ b/src/chat/__tests__/prompt.test.tsx
@@ -26,6 +26,11 @@ describe('Test Chat Prompt', () => {
expect(
render(
).asFragment()
).toMatchSnapshot('default');
+ const _empty = generatePrompt();
+ _empty.title = '';
+ expect(render(
).asFragment()).toMatchSnapshot(
+ 'empty title'
+ );
});
it('Should support components', () => {
diff --git a/src/chat/markdown/index.tsx b/src/chat/markdown/index.tsx
index 1718e8870..2e4247018 100644
--- a/src/chat/markdown/index.tsx
+++ b/src/chat/markdown/index.tsx
@@ -48,8 +48,17 @@ export default memo(
hr() {
return
;
},
+ p: (data) => {
+ // avoid validateDOMNesting error for div as a descendant of p
+ if (data.node.children.every((child) => child.type === 'text')) {
+ return
{data.children}
;
+ } else {
+ return
{data.children}
;
+ }
+ },
...components,
}}
+ includeElementIndex
{...rest}
>
{children}
diff --git a/src/chat/prompt/index.tsx b/src/chat/prompt/index.tsx
index 091adad6c..2b041a823 100644
--- a/src/chat/prompt/index.tsx
+++ b/src/chat/prompt/index.tsx
@@ -28,6 +28,8 @@ export default function Prompt({ data, className }: IPromptProps) {
}, {});
}, [components, data?.id]);
+ if (!data?.title) return null;
+
return (
diff --git a/src/useTyping/__tests__/useTyping.test.ts b/src/useTyping/__tests__/useTyping.test.ts
index 919cc96e7..4bf0a4c7d 100644
--- a/src/useTyping/__tests__/useTyping.test.ts
+++ b/src/useTyping/__tests__/useTyping.test.ts
@@ -63,6 +63,46 @@ describe('Test useTyping hook', () => {
expect(result.current.isTyping).toBe(false);
});
+ it('Should typing whole tag', () => {
+ const { result, rerender } = renderHook((props: any) => {
+ const [text, setText] = useState('');
+ const ref = useRef(0);
+ const typing = useTyping({
+ onTyping(post) {
+ setText(post);
+ },
+ });
+
+ useEffect(() => {
+ if (props?.start) {
+ typing.start();
+ let p = 0;
+ typing.push('');
+ ref.current = window.setInterval(() => {
+ typing.push(testText[p]);
+ p++;
+ if (p >= testText.length) {
+ typing.close();
+ window.clearInterval(ref.current);
+ }
+ }, 50);
+ }
+
+ return () => {
+ window.clearInterval(ref.current);
+ };
+ }, [props?.start]);
+
+ return { text, isTyping: typing.isTyping };
+ });
+
+ // 开启打字机效果
+ rerender({ start: true });
+
+ jest.advanceTimersByTime(50);
+ expect(result.current.text).toBe('');
+ });
+
it('Should waiting for new text', () => {
const { result } = renderHook(() => {
const [text, setText] = useState('');
diff --git a/src/useTyping/index.ts b/src/useTyping/index.ts
index c2ef141be..8482981ec 100644
--- a/src/useTyping/index.ts
+++ b/src/useTyping/index.ts
@@ -35,13 +35,29 @@ export default function useTyping({ onTyping }: { onTyping: (post: string) => vo
typingCountOnTime.current = Math.ceil(remainWordsLength / typingTimes);
}
+ function getNextChunkPosition() {
+ const rest = queue.current.slice(beginIndex.current);
+ const chunk = rest.slice(0, typingCountOnTime.current);
+ const validHTMLTagRegex = /<[a-zA-Z]{0,4}\s[^<]*>/;
+ // 确保在 typing 的过程中,HTML 标签不被分割
+ if (validHTMLTagRegex.test(rest) && !validHTMLTagRegex.test(chunk)) {
+ const match = rest.match(validHTMLTagRegex)!;
+ const tag = match[0];
+ const index = rest.indexOf(tag);
+ return beginIndex.current + index + tag.length;
+ }
+ return beginIndex.current + typingCountOnTime.current;
+ }
+
function startTyping() {
if (interval.current) return;
interval.current = window.setInterval(() => {
if (beginIndex.current < queue.current.length) {
const str = queue.current;
- onTyping(str.slice(0, beginIndex.current + typingCountOnTime.current));
- beginIndex.current += typingCountOnTime.current;
+ const idx = getNextChunkPosition();
+ const next = str.slice(0, idx);
+ onTyping(next);
+ beginIndex.current = next.length;
} else if (!isStart.current) {
// 如果发送了全部的消息且信号关闭,则清空队列
window.clearInterval(interval.current);