Skip to content

Commit f96e477

Browse files
author
zhaoge
committed
feat: optimize components' design and code style
1 parent cfab543 commit f96e477

File tree

7 files changed

+172
-225
lines changed

7 files changed

+172
-225
lines changed

src/chat/codeBlock/index.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,31 @@ import { oneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
44
import classNames from 'classnames';
55

66
import Copy from '../../copy';
7+
import { Message as MessageEntity } from '../entity';
78
import { CopyOptions } from '../useContext';
89
import './index.scss';
910

10-
export interface ICodeBlockProps {
11+
export interface ICodeBlockProps<M extends MessageEntity = MessageEntity> {
1112
copy?: boolean | CopyOptions;
1213
className?: string;
1314
style?: React.CSSProperties;
15+
message?: M;
1416
convert?: boolean;
15-
toolbars?: React.ReactNode | (() => React.ReactNode);
17+
toolbars?: React.ReactNode | ((code: string, message?: M) => React.ReactNode);
1618
options?: Partial<SyntaxHighlighterProps>;
1719
children: React.ReactNode & React.ReactNode[];
1820
}
1921

20-
export default function CodeBlock({
22+
export default function CodeBlock<M extends MessageEntity = MessageEntity>({
2123
className,
2224
style,
2325
toolbars,
26+
message,
2427
copy: rawCopy,
2528
convert,
2629
children,
2730
options: { lineNumberStyle = {}, ...rest } = {},
28-
}: ICodeBlockProps) {
31+
}: ICodeBlockProps<M>) {
2932
const { value, language } = useMemo(() => {
3033
const child = children[0] as React.ReactElement;
3134
const match = /language-(\w+)/.exec(child.props.className || '');
@@ -63,9 +66,9 @@ export default function CodeBlock({
6366
{language.toLocaleLowerCase()}
6467
</span>
6568
<div className="dtc__aigc__codeblock__tool">
69+
{typeof toolbars === 'function' ? toolbars(text, message) : toolbars}
6670
{/* FIXME:Copy 组件后续可以支持一下 disabled 属性 */}
6771
{!copy.disabled && <Copy text={text} {...copy.options} />}
68-
{typeof toolbars === 'function' ? toolbars() : toolbars}
6972
</div>
7073
</div>
7174
<SyntaxHighlighter

src/chat/content/index.tsx

Lines changed: 142 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -7,156 +7,169 @@ import Prompt, { IPromptProps } from '../prompt';
77
import { useContext } from '../useContext';
88
import './index.scss';
99

10-
export interface IContentProps {
10+
export interface IContentProps<
11+
M extends IMessageProps = IMessageProps,
12+
P extends IPromptProps = IPromptProps
13+
> {
1114
data: PromptEntity[];
1215
placeholder?: React.ReactNode;
1316
scrollable?: boolean;
1417
onRegenerate?: (data: MessageEntity, prompt: PromptEntity) => void;
1518
onStop?: (data: MessageEntity, prompt: PromptEntity) => void;
16-
replacePrompt?: (promptProps: IPromptProps) => React.ReactNode;
17-
replaceMessage?: (messageProps: IMessageProps) => React.ReactNode;
19+
replacePrompt?: (promptProps: P) => React.ReactNode;
20+
replaceMessage?: (messageProps: M) => React.ReactNode;
1821
}
1922

2023
export interface IContentRef {
2124
nativeElement: HTMLDivElement | null;
2225
scrollToBottom: () => void;
2326
}
2427

25-
const Content = forwardRef<IContentRef, IContentProps>(function (
26-
{ data, placeholder, scrollable = true, onRegenerate, onStop, replacePrompt, replaceMessage },
27-
forwardedRef
28-
) {
29-
const { maxRegenerateCount, copy, regenerate } = useContext();
30-
const containerRef = useRef<HTMLDivElement>(null);
28+
const Content = forwardRef(
29+
<M extends IMessageProps = IMessageProps, P extends IPromptProps = IPromptProps>(
30+
{
31+
data,
32+
placeholder,
33+
scrollable = true,
34+
onRegenerate,
35+
onStop,
36+
replacePrompt,
37+
replaceMessage,
38+
}: IContentProps<M, P>,
39+
forwardedRef: React.Ref<IContentRef>
40+
) => {
41+
const { maxRegenerateCount, copy, regenerate } = useContext();
42+
const containerRef = useRef<HTMLDivElement>(null);
3143

32-
const [isStickyAtBottom, setIsStickyAtBottom] = useState<boolean>(true);
33-
const raf = useRef(0);
44+
const [isStickyAtBottom, setIsStickyAtBottom] = useState<boolean>(true);
45+
const raf = useRef(0);
3446

35-
useImperativeHandle(forwardedRef, () => ({
36-
nativeElement: containerRef.current,
37-
scrollToBottom: () => {
38-
raf.current = window.requestAnimationFrame(() => {
39-
containerRef.current?.scrollTo({
40-
top: containerRef.current?.scrollHeight,
41-
left: 0,
42-
behavior: 'instant' as any,
47+
useImperativeHandle(forwardedRef, () => ({
48+
nativeElement: containerRef.current,
49+
scrollToBottom: () => {
50+
raf.current = window.requestAnimationFrame(() => {
51+
containerRef.current?.scrollTo({
52+
top: containerRef.current?.scrollHeight,
53+
left: 0,
54+
behavior: 'instant' as any,
55+
});
4356
});
44-
});
45-
},
46-
}));
57+
},
58+
}));
4759

48-
const checkIfScrolledToBottom = () => {
49-
if (!containerRef.current) return false;
50-
const threshold = 5;
51-
const { scrollTop, clientHeight, scrollHeight } = containerRef.current;
52-
return scrollTop + clientHeight >= scrollHeight - threshold;
53-
};
60+
const checkIfScrolledToBottom = () => {
61+
if (!containerRef.current) return false;
62+
const threshold = 5;
63+
const { scrollTop, clientHeight, scrollHeight } = containerRef.current;
64+
return scrollTop + clientHeight >= scrollHeight - threshold;
65+
};
5466

55-
const dataValid = !!(Array.isArray(data) && data.length);
56-
const lastPrompt = data[data.length - 1];
57-
const lastMessage = dataValid
58-
? lastPrompt.messages?.[lastPrompt.messages.length - 1]
59-
: undefined;
67+
const dataValid = !!(Array.isArray(data) && data.length);
68+
const lastPrompt = data[data.length - 1];
69+
const lastMessage = dataValid
70+
? lastPrompt.messages?.[lastPrompt.messages.length - 1]
71+
: undefined;
6072

61-
useLayoutEffect(() => {
62-
const handleScroll = () => {
63-
if (!containerRef.current) {
64-
return;
65-
}
66-
setIsStickyAtBottom(checkIfScrolledToBottom());
67-
};
68-
containerRef.current?.addEventListener('scroll', handleScroll);
69-
return () => {
70-
containerRef.current?.removeEventListener('scroll', handleScroll);
71-
window.cancelAnimationFrame(raf.current);
72-
};
73-
}, []);
73+
useLayoutEffect(() => {
74+
const handleScroll = () => {
75+
if (!containerRef.current) {
76+
return;
77+
}
78+
setIsStickyAtBottom(checkIfScrolledToBottom());
79+
};
80+
containerRef.current?.addEventListener('scroll', handleScroll);
81+
return () => {
82+
containerRef.current?.removeEventListener('scroll', handleScroll);
83+
window.cancelAnimationFrame(raf.current);
84+
};
85+
}, []);
7486

75-
useLayoutEffect(() => {
76-
raf.current = window.requestAnimationFrame(() => {
77-
if (!containerRef.current) {
78-
return;
79-
}
80-
if (dataValid) {
81-
containerRef.current.scrollTop = containerRef.current.scrollHeight;
82-
} else {
83-
containerRef.current.scrollTop = 0;
84-
}
85-
});
86-
}, [lastMessage?.id]);
87+
useLayoutEffect(() => {
88+
raf.current = window.requestAnimationFrame(() => {
89+
if (!containerRef.current) {
90+
return;
91+
}
92+
if (dataValid) {
93+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
94+
} else {
95+
containerRef.current.scrollTop = 0;
96+
}
97+
});
98+
}, [lastMessage?.id]);
8799

88-
useLayoutEffect(() => {
89-
raf.current = window.requestAnimationFrame(() => {
90-
if (!containerRef.current) {
91-
return;
92-
}
93-
if (lastMessage?.status === MessageStatus.GENERATING && isStickyAtBottom) {
94-
containerRef.current.scrollTop = containerRef.current.scrollHeight;
95-
}
96-
});
97-
}, [lastMessage?.status, lastMessage?.content, isStickyAtBottom]);
100+
useLayoutEffect(() => {
101+
raf.current = window.requestAnimationFrame(() => {
102+
if (!containerRef.current) {
103+
return;
104+
}
105+
if (lastMessage?.status === MessageStatus.GENERATING && isStickyAtBottom) {
106+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
107+
}
108+
});
109+
}, [lastMessage?.status, lastMessage?.content, isStickyAtBottom]);
98110

99-
return (
100-
<div
101-
className={classNames(
102-
'dtc__aigc__content__container',
103-
!scrollable && 'dtc__aigc__content__container--disabled'
104-
)}
105-
ref={containerRef}
106-
>
107-
{dataValid ? (
108-
<div className="dtc__aigc__content__inner__holder">
109-
{data.map((row, idx) => {
110-
const defaultRegenerate =
111-
idx === data.length - 1 && row.messages.length < maxRegenerateCount;
112-
const messageProps: IMessageProps = {
113-
prompt: row,
114-
data: row.messages,
115-
regenerate:
116-
typeof regenerate === 'function'
117-
? regenerate(row, idx, data)
118-
: regenerate ?? defaultRegenerate,
119-
copy,
120-
onRegenerate: (message) => onRegenerate?.(message, row),
121-
onStop: (message) => onStop?.(message, row),
122-
onLazyRendered: (renderFn) => {
123-
// 在触发懒加载之前判断是否在底部,如果是则加载完成后滚动到底部
124-
const scrolledToBottom = checkIfScrolledToBottom();
125-
renderFn().then(() => {
126-
window.requestAnimationFrame(() => {
127-
setIsStickyAtBottom(scrolledToBottom);
128-
if (scrolledToBottom && containerRef.current) {
129-
containerRef.current.scrollTop =
130-
containerRef.current.scrollHeight;
131-
}
111+
return (
112+
<div
113+
className={classNames(
114+
'dtc__aigc__content__container',
115+
!scrollable && 'dtc__aigc__content__container--disabled'
116+
)}
117+
ref={containerRef}
118+
>
119+
{dataValid ? (
120+
<div className="dtc__aigc__content__inner__holder">
121+
{data.map((row, idx) => {
122+
const defaultRegenerate =
123+
idx === data.length - 1 && row.messages.length < maxRegenerateCount;
124+
const messageProps = {
125+
prompt: row,
126+
data: row.messages,
127+
regenerate:
128+
typeof regenerate === 'function'
129+
? regenerate(row, idx, data)
130+
: regenerate ?? defaultRegenerate,
131+
copy,
132+
onRegenerate: (message) => onRegenerate?.(message, row),
133+
onStop: (message) => onStop?.(message, row),
134+
onLazyRendered: (renderFn) => {
135+
// 在触发懒加载之前判断是否在底部,如果是则加载完成后滚动到底部
136+
const scrolledToBottom = checkIfScrolledToBottom();
137+
renderFn().then(() => {
138+
window.requestAnimationFrame(() => {
139+
setIsStickyAtBottom(scrolledToBottom);
140+
if (scrolledToBottom && containerRef.current) {
141+
containerRef.current.scrollTop =
142+
containerRef.current.scrollHeight;
143+
}
144+
});
132145
});
133-
});
134-
},
135-
};
136-
const promptProps: IPromptProps = {
137-
data: row,
138-
};
139-
return (
140-
<React.Fragment key={row.id}>
141-
{replacePrompt ? (
142-
replacePrompt(promptProps)
143-
) : (
144-
<Prompt {...promptProps} />
145-
)}
146-
{replaceMessage ? (
147-
replaceMessage(messageProps)
148-
) : (
149-
<Message {...messageProps} />
150-
)}
151-
</React.Fragment>
152-
);
153-
})}
154-
</div>
155-
) : (
156-
<React.Fragment>{placeholder}</React.Fragment>
157-
)}
158-
</div>
159-
);
160-
});
146+
},
147+
} as M;
148+
const promptProps = {
149+
data: row,
150+
} as P;
151+
return (
152+
<React.Fragment key={row.id}>
153+
{replacePrompt ? (
154+
replacePrompt(promptProps)
155+
) : (
156+
<Prompt {...promptProps} />
157+
)}
158+
{replaceMessage ? (
159+
replaceMessage(messageProps)
160+
) : (
161+
<Message {...messageProps} />
162+
)}
163+
</React.Fragment>
164+
);
165+
})}
166+
</div>
167+
) : (
168+
<React.Fragment>{placeholder}</React.Fragment>
169+
)}
170+
</div>
171+
);
172+
}
173+
);
161174

162175
export default Content;

0 commit comments

Comments
 (0)