Skip to content

feat(chat): support render empty prompt #575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/chat/__tests__/__snapshots__/markdown.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ exports[`Test Chat Markdown Match Snapshots: typing 1`] = `
<div
class="dtc__aigc__markdown dtc__aigc__markdown--blink test"
>
<p>
<div>
<code
class="dtc__aigc__markdown__inlineCode"
>
inline code test
</code>
</p>
</div>


<div
Expand Down
22 changes: 3 additions & 19 deletions src/chat/__tests__/__snapshots__/prompt.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,6 @@ exports[`Test Chat Prompt Match Snapshots: default 1`] = `
</DocumentFragment>
`;

exports[`Test Chat Prompt Match Snapshots: empty 1`] = `
<DocumentFragment>
<section
class="dtc__prompt__container"
>
<div
class="dtc__prompt__wrapper"
>
<div
class="dtc__prompt__content"
>
<div
class="dtc__aigc__markdown"
/>
</div>
</div>
</section>
</DocumentFragment>
`;
exports[`Test Chat Prompt Match Snapshots: empty 1`] = `<DocumentFragment />`;

exports[`Test Chat Prompt Match Snapshots: empty title 1`] = `<DocumentFragment />`;
5 changes: 5 additions & 0 deletions src/chat/__tests__/prompt.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ describe('Test Chat Prompt', () => {
expect(
render(<Prompt className="test" data={generatePrompt()} />).asFragment()
).toMatchSnapshot('default');
const _empty = generatePrompt();
_empty.title = '';
expect(render(<Prompt className="test" data={_empty} />).asFragment()).toMatchSnapshot(
'empty title'
);
});

it('Should support components', () => {
Expand Down
9 changes: 9 additions & 0 deletions src/chat/markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,17 @@ export default memo(
hr() {
return <hr color="#ebecf0" className="dtc__aigc__markdown__hr" />;
},
p: (data) => {
// avoid validateDOMNesting error for div as a descendant of p
if (data.node.children.every((child) => child.type === 'text')) {
return <p>{data.children}</p>;
} else {
return <div>{data.children}</div>;
}
},
...components,
}}
includeElementIndex
{...rest}
>
{children}
Expand Down
2 changes: 2 additions & 0 deletions src/chat/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export default function Prompt({ data, className }: IPromptProps) {
}, {});
}, [components, data?.id]);

if (!data?.title) return null;

return (
<section className={classNames('dtc__prompt__container', className)}>
<div className="dtc__prompt__wrapper">
Expand Down
40 changes: 40 additions & 0 deletions src/useTyping/__tests__/useTyping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('<step type="1">');
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('<step type="1">');
});

it('Should waiting for new text', () => {
const { result } = renderHook(() => {
const [text, setText] = useState('');
Expand Down
20 changes: 18 additions & 2 deletions src/useTyping/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[^<]*>/;
Copy link
Preview

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex limits tag names to a maximum of 4 letters, which may not cover standard HTML tags longer than 4 characters (e.g., 'section' or 'article'). Consider using a more general pattern such as /<[a-zA-Z]+\b[^>]*>/ to ensure all valid tags are correctly handled.

Suggested change
const validHTMLTagRegex = /<[a-zA-Z]{0,4}\s[^<]*>/;
const validHTMLTagRegex = /<[a-zA-Z]+\b[^>]*>/;

Copilot uses AI. Check for mistakes.

// 确保在 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);
Expand Down
Loading