Skip to content

Commit 18f4e35

Browse files
committed
feat(gpt-runner-web): optimize chat panel toolbar
1 parent 18037b8 commit 18f4e35

File tree

4 files changed

+111
-126
lines changed

4 files changed

+111
-126
lines changed

packages/gpt-runner-web/client/src/components/chat-message-code-block/index.tsx

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,25 @@
11
import type { FC } from 'react'
2-
import { useCallback } from 'react'
32
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
43
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'
5-
import { IconButton } from '../icon-button'
64
import { CodeBlockHeader, CodeBlockWrapper } from './chat-message-code-block.styles'
75

6+
export interface BuildCodeToolbarState {
7+
contents: string
8+
}
9+
810
export interface MessageCodeBlockProps {
911
contents: string
1012
language: string
11-
onCopyCode?: (value: string) => void
12-
onInsertCode?: (value: string) => void
13-
onDiffCode?: (value: string) => void
13+
buildCodeToolbar?: (state: BuildCodeToolbarState) => React.ReactNode
1414
}
1515

1616
export const MessageCodeBlock: FC<MessageCodeBlockProps> = (props) => {
17-
const { contents, language, onCopyCode, onInsertCode, onDiffCode } = props
18-
19-
const handleCopyCode = useCallback(() => {
20-
onCopyCode?.(contents)
21-
}, [contents, onCopyCode])
22-
23-
const handleInsertCode = useCallback(() => {
24-
onInsertCode?.(contents)
25-
}, [contents, onInsertCode])
26-
27-
const handleDiffCode = useCallback(() => {
28-
onDiffCode?.(contents)
29-
}, [contents, onDiffCode])
17+
const { contents, language, buildCodeToolbar } = props
3018

3119
return (
3220
<CodeBlockWrapper>
3321
<CodeBlockHeader>
34-
<IconButton
35-
text='Copy'
36-
iconClassName='codicon-copy'
37-
onClick={handleCopyCode}
38-
>
39-
</IconButton>
40-
41-
<IconButton
42-
text='Insert'
43-
iconClassName='codicon-insert'
44-
onClick={handleInsertCode}
45-
>
46-
</IconButton>
47-
48-
<IconButton
49-
text='Diff'
50-
iconClassName='codicon-arrow-swap'
51-
onClick={handleDiffCode}
52-
>
53-
</IconButton>
22+
{buildCodeToolbar?.({ contents })}
5423
</CodeBlockHeader>
5524
<SyntaxHighlighter
5625
useInlineStyles={true}

packages/gpt-runner-web/client/src/components/chat-message-item/index.tsx

Lines changed: 14 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
import type { FC } from 'react'
2-
import { useCallback } from 'react'
32
import clsx from 'clsx'
43
import type { SingleChatMessage } from '@nicepkg/gpt-runner-shared/common'
54
import { ChatMessageStatus, ChatRole } from '@nicepkg/gpt-runner-shared/common'
65
import { MessageTextView } from '../chat-message-text-view'
76
import { Icon } from '../icon'
8-
import { IconButton } from '../icon-button'
97
import { useHover } from '../../hooks/use-hover.hook'
108
import type { MessageCodeBlockProps } from '../chat-message-code-block'
119
import { MsgAvatarWrapper, MsgContent, MsgContentFooterWrapper, MsgContentWrapper, MsgWrapper } from './chat-message-item.styles'
1210

11+
export interface BuildMessageToolbarState extends SingleChatMessage {
12+
status: ChatMessageStatus
13+
}
1314
export interface MessageItemProps extends SingleChatMessage, Partial<MessageCodeBlockProps> {
1415
status: ChatMessageStatus
1516
showToolbar?: 'always' | 'hover' | 'never'
16-
showRegenerateIcon?: boolean
17-
onCopyMessage?: (value: string) => void
18-
onEditMessage?: (value: string) => void
19-
onRegenerateMessage?: () => void
20-
onDeleteMessage?: () => void
17+
buildMessageToolbar?: (state: BuildMessageToolbarState) => React.ReactNode
2118
}
2219

2320
export const MessageItem: FC<MessageItemProps> = (props) => {
@@ -26,78 +23,31 @@ export const MessageItem: FC<MessageItemProps> = (props) => {
2623
text,
2724
status,
2825
showToolbar = 'hover',
29-
showRegenerateIcon = false,
30-
onCopyMessage,
31-
onEditMessage,
32-
onRegenerateMessage,
33-
onDeleteMessage,
34-
onCopyCode,
35-
onDiffCode,
36-
onInsertCode,
26+
buildCodeToolbar,
27+
buildMessageToolbar,
3728
} = props
3829

3930
const [hoverContentRef, isContentHover] = useHover()
4031
const contents = status === ChatMessageStatus.Pending ? `${text}\u{258A}` : text
4132

42-
const handleCopyMessage = useCallback(() => {
43-
onCopyMessage?.(text)
44-
}, [text, onCopyMessage])
45-
46-
const handleEditMessage = useCallback(() => {
47-
onEditMessage?.(text)
48-
}, [text, onEditMessage])
49-
50-
const handleRegenerateMessage = useCallback(() => {
51-
onRegenerateMessage?.()
52-
}, [onRegenerateMessage])
53-
54-
const handleDeleteMessage = useCallback(() => {
55-
onDeleteMessage?.()
56-
}, [onDeleteMessage])
57-
58-
const renderContent = useCallback(({ showToolbar }: Pick<MessageItemProps, 'showToolbar'>) => {
33+
const renderContent = ({ showToolbar }: Pick<MessageItemProps, 'showToolbar'>) => {
5934
return <MsgContent $showToolbar={showToolbar}
6035
$isMe={name === ChatRole.User}
6136
>
6237
<MessageTextView
6338
contents={contents}
64-
onCopyCode={onCopyCode}
65-
onDiffCode={onDiffCode}
66-
onInsertCode={onInsertCode}
39+
buildCodeToolbar={buildCodeToolbar}
6740
/>
6841

6942
<MsgContentFooterWrapper className='msg-content-footer'>
70-
<IconButton
71-
text='Copy'
72-
iconClassName='codicon-copy'
73-
onClick={handleCopyMessage}
74-
>
75-
</IconButton>
76-
77-
<IconButton
78-
text='Edit'
79-
iconClassName='codicon-edit'
80-
onClick={handleEditMessage}
81-
>
82-
</IconButton>
83-
84-
{showRegenerateIcon && <IconButton
85-
text={status === ChatMessageStatus.Error ? 'Retry' : 'Regenerate'}
86-
iconClassName='codicon-sync'
87-
disabled={[ChatMessageStatus.Pending, ChatMessageStatus.Idle].includes(status)}
88-
onClick={handleRegenerateMessage}
89-
></IconButton>}
90-
91-
<IconButton
92-
text='Delete'
93-
iconClassName='codicon-trash'
94-
onClick={handleDeleteMessage}
95-
>
96-
</IconButton>
97-
43+
{buildMessageToolbar?.({
44+
name,
45+
text,
46+
status,
47+
})}
9848
</MsgContentFooterWrapper>
9949
</MsgContent>
100-
}, [name, contents, showToolbar, showRegenerateIcon, status])
50+
}
10151

10252
return (
10353
<MsgWrapper $isMe={name === ChatRole.User}>

packages/gpt-runner-web/client/src/components/chat-message-text-view/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { MessageCodeBlock } from '../chat-message-code-block'
66

77
export interface MessageTextViewProps extends Partial<MessageCodeBlockProps> {
88
contents: string
9+
910
}
1011

1112
export const MessageTextView: FC<MessageTextViewProps> = (props) => {

packages/gpt-runner-web/client/src/pages/chat/chat-panel.tsx

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ChatMessagePanel } from '../../components/chat-message-panel'
77
import { ChatMessageInput } from '../../components/chat-message-input'
88
import { IconButton } from '../../components/icon-button'
99
import { useChatInstance } from '../../hooks/use-chat-instance.hook'
10+
import type { MessageItemProps } from '../../components/chat-message-item'
1011

1112
export interface ChatPanelProps {
1213
scrollDownRef: RefObject<any>
@@ -16,6 +17,7 @@ export interface ChatPanelProps {
1617
export const ChatPanel: FC<ChatPanelProps> = (props) => {
1718
const { scrollDownRef, chatId } = props
1819
const { chatInstance, updateCurrentChatInstance, generateCurrentChatAnswer, regenerateCurrentLastChatAnswer, stopCurrentGeneratingChatAnswer } = useChatInstance({ chatId })
20+
const status = chatInstance?.status ?? ChatMessageStatus.Success
1921

2022
const handleCopy = useCallback((value: string) => {
2123
copy(value)
@@ -63,28 +65,94 @@ export const ChatPanel: FC<ChatPanelProps> = (props) => {
6365
const handleRegenerateMessage = () => {
6466
if (!isLast)
6567
return
68+
69+
if (status === ChatMessageStatus.Pending) {
70+
// is generating, stop first
71+
stopCurrentGeneratingChatAnswer()
72+
}
73+
6674
regenerateCurrentLastChatAnswer()
6775
}
6876

6977
const handleDeleteMessage = () => {
78+
if (status === ChatMessageStatus.Pending) {
79+
// is generating, stop first
80+
stopCurrentGeneratingChatAnswer()
81+
}
82+
7083
updateCurrentChatInstance({
7184
messages: chatInstance.messages.filter((_, index) => index !== i),
7285
}, false)
7386
}
7487

88+
const buildCodeToolbar: MessageItemProps['buildCodeToolbar'] = ({ contents }) => {
89+
return <>
90+
<IconButton
91+
text='Copy'
92+
iconClassName='codicon-copy'
93+
onClick={() => handleCopy(contents)}
94+
>
95+
</IconButton>
96+
97+
<IconButton
98+
text='Insert'
99+
iconClassName='codicon-insert'
100+
>
101+
</IconButton>
102+
103+
<IconButton
104+
text='Diff'
105+
iconClassName='codicon-arrow-swap'
106+
>
107+
</IconButton>
108+
</>
109+
}
110+
111+
const buildMessageToolbar: MessageItemProps['buildMessageToolbar'] = ({ text }) => {
112+
return <>
113+
<IconButton
114+
text='Copy'
115+
iconClassName='codicon-copy'
116+
onClick={() => handleCopy(text)}
117+
>
118+
</IconButton>
119+
120+
<IconButton
121+
text='Edit'
122+
iconClassName='codicon-edit'
123+
onClick={() => handleEditMessage(text)}
124+
>
125+
</IconButton>
126+
127+
{isAi && isLast && <IconButton
128+
text={status === ChatMessageStatus.Error ? 'Retry' : 'Regenerate'}
129+
iconClassName='codicon-sync'
130+
onClick={handleRegenerateMessage}
131+
></IconButton>}
132+
133+
<IconButton
134+
text='Delete'
135+
iconClassName='codicon-trash'
136+
onClick={handleDeleteMessage}
137+
>
138+
</IconButton>
139+
140+
{status === ChatMessageStatus.Pending && isLast && <IconButton
141+
text='Stop'
142+
iconClassName='codicon-chrome-maximize'
143+
hoverShowText={false}
144+
onClick={handleStopGenerateAnswer}
145+
></IconButton>}
146+
</>
147+
}
148+
75149
return {
76150
...message,
77-
status: isLast ? chatInstance.status : ChatMessageStatus.Success,
151+
status: isLast ? status : ChatMessageStatus.Success,
78152
showToolbar: isLastTwo ? 'always' : 'hover',
79-
showRegenerateIcon: isAi && isLast,
80-
onCopyCode: handleCopy,
81-
onDiffCode: undefined,
82-
onInsertCode: undefined,
83-
onCopyMessage: handleCopy,
84-
onEditMessage: handleEditMessage,
85-
onRegenerateMessage: handleRegenerateMessage,
86-
onDeleteMessage: handleDeleteMessage,
87-
}
153+
buildCodeToolbar,
154+
buildMessageToolbar,
155+
} satisfies MessageItemProps
88156
}) ?? [],
89157
}
90158

@@ -109,32 +177,29 @@ export const ChatPanel: FC<ChatPanelProps> = (props) => {
109177
iconClassName='codicon-add'></IconButton>
110178

111179
{/* right icon */}
112-
{chatInstance?.status === ChatMessageStatus.Pending && <IconButton
113-
style={{
114-
marginLeft: 'auto',
115-
}}
116-
disabled={chatInstance?.status !== ChatMessageStatus.Pending}
117-
text='Stop'
118-
iconClassName='codicon-chrome-maximize'
119-
onClick={handleStopGenerateAnswer}
120-
></IconButton>}
121-
122-
{chatInstance?.status === ChatMessageStatus.Success && <IconButton
180+
<IconButton
123181
style={{
124182
marginLeft: 'auto',
125183
}}
126-
disabled={chatInstance?.status !== ChatMessageStatus.Success}
184+
disabled={status === ChatMessageStatus.Pending}
127185
text='Continue'
128186
iconClassName='codicon-debug-continue-small'
129187
onClick={handleContinueGenerateAnswer}
188+
></IconButton>
189+
190+
{status === ChatMessageStatus.Pending && <IconButton
191+
text='Stop'
192+
iconClassName='codicon-chrome-maximize'
193+
hoverShowText={false}
194+
onClick={handleStopGenerateAnswer}
130195
></IconButton>}
131196

132-
<IconButton
197+
{status !== ChatMessageStatus.Pending && <IconButton
133198
disabled={!chatInstance?.inputtingPrompt}
134199
text='Send'
135200
hoverShowText={false}
136201
iconClassName='codicon-send'
137-
onClick={handleGenerateAnswer}></IconButton>
202+
onClick={handleGenerateAnswer}></IconButton>}
138203
</>
139204
}
140205

0 commit comments

Comments
 (0)