Skip to content
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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"resize-observer-polyfill": "^1.5.1",
"standard-version": "^9.5.0",
"stylelint": "^14.9.1",
"ts-jest": "^29.0.3",
"ts-jest": "29.0.3",
"typescript": "~4.5.2"
},
"dependencies": {
Expand All @@ -126,8 +126,9 @@
"react-markdown": "~8.0.6",
"react-resizable": "^3.0.5",
"react-syntax-highlighter": "~15.5.0",
"rehype-raw": "^6.0.0",
"remark-gfm": "~3.0.1",
"shortid": "^2.2.16",
"shortid": "2.2.16",
"showdown": "^1.9.0"
},
"config": {
Expand Down
26,773 changes: 10,513 additions & 16,260 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions src/chat/__tests__/__snapshots__/think.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Think component snapshot renders completed state with children correctly 1`] = `
<div>
<div
class="dtc__custom__think"
>
<section
class="dtc-flex dtc__custom__think__btn"
style="flex-wrap: nowrap; justify-content: normal; align-items: center; gap: 4px;"
>
已完成深度思考
<span
color="#64698B"
data-mock-icon="UpOutlined"
size="12"
style="transform: none;"
/>
</section>
<div
class="dtc__custom__think__content"
>
<div
class="dtc__custom__think__line"
/>
<div
class="dtc__custom__think__text"
>
这里是 Think 的内容示例(支持 Markdown)

- 列表项 A
- 列表项 B
</div>
</div>
</div>
</div>
`;

exports[`Think component snapshot renders loading state correctly 1`] = `
<div>
<div
class="dtc__custom__think"
>
<section
class="dtc-flex dtc__custom__think__btn"
style="flex-wrap: nowrap; justify-content: normal; align-items: center; gap: 4px;"
>
<span
class="gradient-text"
>
思考中...
</span>
<span
color="#64698B"
data-mock-icon="UpOutlined"
size="12"
style="transform: none;"
/>
</section>
<div
class="dtc__custom__think__content"
>
<div
class="dtc__custom__think__line"
/>
<div
class="dtc__custom__think__text"
>
正在思考中,暂无内容。
</div>
</div>
</div>
</div>
`;
51 changes: 44 additions & 7 deletions src/chat/__tests__/message.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import '@testing-library/jest-dom/extend-expect';

import { Message as MessageEntity, MessageStatus, Prompt as PromptEntity } from '../entity';
import Message from '../message';
import Chat from '..';
import Chat from '../';

jest.mock('remark-gfm', () => () => {});

Expand Down Expand Up @@ -232,12 +232,24 @@ describe('Test Chat Message', () => {
prompt.messages[0].status = MessageStatus.DONE;
const onRegenerate = jest.fn();
const { container, getByText } = render(
<Message
prompt={prompt}
regenerate
data={prompt.messages}
onRegenerate={onRegenerate}
/>
<Chat
chat={{} as any}
messageHeader={
<div
className="dtc__message__extra__render"
data-testid="fakeMessageExtraRender"
>
ExtraDom
</div>
}
>
<Message
prompt={prompt}
regenerate
data={prompt.messages}
onRegenerate={onRegenerate}
/>
</Chat>
);

const nodeList = container
Expand Down Expand Up @@ -301,4 +313,29 @@ describe('Test Chat Message', () => {
expect(ele.dataset.messageid).toBe('1');
expect(ele.dataset.promptid).toBe('1');
});

it('Should support extraRender', () => {
const prompt = generatePrompt();
prompt.messages[0].status = MessageStatus.DONE;
const { container, getByTestId } = render(
<Chat
chat={{} as any}
messageHeader={
<div
className="dtc__message__extra__render"
data-testid="fakeMessageExtraRender"
>
ExtraDom
</div>
}
>
<Message prompt={prompt} data={prompt.messages} />
</Chat>
);
expect(getByTestId('fakeMessageExtraRender')).toBeInTheDocument();
const nodeList = container.querySelectorAll<HTMLDivElement>('.dtc__message__extra__render');
const ele = nodeList?.item(nodeList?.length - 1);
expect(ele).not.toBeNull();
expect(ele?.textContent).toBe('ExtraDom');
});
});
22 changes: 21 additions & 1 deletion src/chat/__tests__/prompt.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { render } from '@testing-library/react';

import { Prompt as PromptEntity } from '../entity';
import Prompt from '../prompt';
import Chat from '..';
import Chat from '../';

jest.mock('remark-gfm', () => () => {});
class BasePrompt extends PromptEntity {}
Expand Down Expand Up @@ -57,4 +57,24 @@ describe('Test Chat Prompt', () => {

expect(getByTestId('fakeCode').dataset.promptid).toBe('1');
});

it('Should support extraRender', () => {
const data = generatePrompt();
const { container } = render(
<Chat
chat={{} as any}
promptFooter={
<div className="dtc__prompt__extra__render" data-testid="fakePromptExtraRender">
PromptExtraDom
</div>
}
>
<Prompt data={data} />
</Chat>
);
const nodeList = container.querySelectorAll<HTMLDivElement>('.dtc__prompt__extra__render');
const ele = nodeList?.item(nodeList?.length - 1);
expect(ele).not.toBeNull();
expect(ele?.textContent).toBe('PromptExtraDom');
});
});
19 changes: 19 additions & 0 deletions src/chat/__tests__/think.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { render } from '@testing-library/react';

import Think from '../think';

describe('Think component snapshot', () => {
it('renders loading state correctly', () => {
const data = { children: '正在思考中,暂无内容。' } as any;
const { container } = render(<Think data={data} loading />);
expect(container).toMatchSnapshot();
});

it('renders completed state with children correctly', () => {
const markdown = `这里是 Think 的内容示例(支持 Markdown)\n\n- 列表项 A\n- 列表项 B`;
const data = { children: markdown } as any;
const { container } = render(<Think data={data} loading={false} />);
expect(container).toMatchSnapshot();
});
});
13 changes: 8 additions & 5 deletions src/chat/codeBlock/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,31 @@ import { oneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import classNames from 'classnames';

import Copy from '../../copy';
import { Message as MessageEntity } from '../entity';
import { CopyOptions } from '../useContext';
import './index.scss';

export interface ICodeBlockProps {
export interface ICodeBlockProps<M extends MessageEntity = MessageEntity> {
copy?: boolean | CopyOptions;
className?: string;
style?: React.CSSProperties;
message?: M;
convert?: boolean;
toolbars?: React.ReactNode | (() => React.ReactNode);
toolbars?: React.ReactNode | ((code: string, message?: M) => React.ReactNode);
options?: Partial<SyntaxHighlighterProps>;
children: React.ReactNode & React.ReactNode[];
}

export default function CodeBlock({
export default function CodeBlock<M extends MessageEntity = MessageEntity>({
className,
style,
toolbars,
message,
copy: rawCopy,
convert,
children,
options: { lineNumberStyle = {}, ...rest } = {},
}: ICodeBlockProps) {
}: ICodeBlockProps<M>) {
const { value, language } = useMemo(() => {
const child = children[0] as React.ReactElement;
const match = /language-(\w+)/.exec(child.props.className || '');
Expand Down Expand Up @@ -63,9 +66,9 @@ export default function CodeBlock({
{language.toLocaleLowerCase()}
</span>
<div className="dtc__aigc__codeblock__tool">
{typeof toolbars === 'function' ? toolbars(text, message) : toolbars}
{/* FIXME:Copy 组件后续可以支持一下 disabled 属性 */}
{!copy.disabled && <Copy text={text} {...copy.options} />}
{typeof toolbars === 'function' ? toolbars() : toolbars}
</div>
</div>
<SyntaxHighlighter
Expand Down
2 changes: 2 additions & 0 deletions src/chat/demos/basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export default function () {
</Button>
),
}}
messageHeader={<div>这是自定义header</div>}
promptFooter={<div>这是自定义footer</div>}
>
<Chat.Content
data={chat.conversation.get()?.prompts || []}
Expand Down
32 changes: 31 additions & 1 deletion src/chat/demos/markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import { Components } from 'react-markdown';
import { Chat } from 'dt-react-component';
import rehypeRaw from 'rehype-raw';

const children = `
# 大标题
Expand Down Expand Up @@ -35,7 +37,35 @@ SELECT * FROM table_name;
| 单元格 | 单元格 | 单元格 |
| 单元格 | 单元格 | 单元格 |
`;
const childrenTsx = `<customtsx>
<span class=\"command-tag-Lynton\">这是自定义的标签</span>
</customtsx>`;

export default function () {
return <Chat.Markdown>{children}</Chat.Markdown>;
return (
<Chat
chat={{} as any}
components={
{
customtsx: ({ children }: any) => {
return <div className="hhhh">{children}</div>;
},
} as Components
}
rehypePlugins={[rehypeRaw]}
>
<Chat.Markdown>{children}</Chat.Markdown>
<Chat.Content
data={[
{
messages: [
{
content: childrenTsx,
},
],
},
]}
/>
</Chat>
);
}
20 changes: 12 additions & 8 deletions src/chat/demos/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Message, MessageStatus, Prompt } from 'dt-react-component/chat/entity';

class BasicPrompt extends Prompt {}
class BasicMessage extends Message {}

const CustomRender = () => {
return <div style={{ color: 'red' }}>这是自定义header</div>;
};
export default function () {
const [status, setStatus] = useState<MessageStatus>(MessageStatus.DONE);

Expand Down Expand Up @@ -36,13 +38,15 @@ export default function () {
置为完成
</Button>
</Space>
<Chat.Message
prompt={data}
data={data.messages}
regenerate
onStop={() => setStatus(MessageStatus.STOPPED)}
onRegenerate={() => console.log('regenerate')}
/>
<Chat chat={{} as any} messageHeader={<CustomRender />}>
<Chat.Message
prompt={data}
data={data.messages}
regenerate
onStop={() => setStatus(MessageStatus.STOPPED)}
onRegenerate={() => console.log('regenerate')}
/>
</Chat>
</>
);
}
30 changes: 17 additions & 13 deletions src/chat/demos/prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { Chat } from 'dt-react-component';
import { Prompt } from 'dt-react-component/chat/entity';

const updateReducer = (num: number): number => (num + 1) % 1_000_000;

const CustomRender = () => {
return <div style={{ color: 'red' }}>这是自定义footer</div>;
};
export default function () {
const [value, setValue] = useState<string | undefined>('');
const [, update] = useReducer(updateReducer, 0);
Expand All @@ -17,18 +19,20 @@ export default function () {

return (
<Space direction="vertical" style={{ width: '100%' }}>
<Chat.Prompt data={prompt.current} />
<Space>
<Chat.Input
value={value}
onChange={setValue}
placeholder="请输入"
button={{
disabled: !value?.trim(),
}}
onPressEnter={() => value?.trim() && setContent(value.trim())}
/>
</Space>
<Chat chat={{} as any} promptFooter={<CustomRender />}>
<Chat.Prompt data={prompt.current} />
<Space>
<Chat.Input
value={value}
onChange={setValue}
placeholder="请输入"
button={{
disabled: !value?.trim(),
}}
onPressEnter={() => value?.trim() && setContent(value.trim())}
/>
</Space>
</Chat>
</Space>
);
}
Loading
Loading