Skip to content

Commit 8c5e0f9

Browse files
committed
feat: Add continue message functionality across MessageItem, MessageList, and ChatWindow components
1 parent 29fdda8 commit 8c5e0f9

5 files changed

Lines changed: 188 additions & 64 deletions

File tree

src/renderer/src/components/pages/chat/ChatLogic.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface ChatLogicProps {
1717
onSendMessage: (content: string, customModelId?: string) => Promise<void>
1818
onStopGeneration: () => void
1919
onRetryMessage: (messageId: string) => Promise<void>
20+
onContinueMessage: (messageId: string) => Promise<void>
2021
onEditMessage: (messageId: string, newContent: string) => Promise<void>
2122
onEditAndResendMessage: (messageId: string, newContent: string) => Promise<void>
2223
onToggleFavorite: (messageId: string) => void
@@ -178,6 +179,7 @@ export default function ChatLogic({
178179
onSendMessage: messageOperations.handleSendMessage,
179180
onStopGeneration: handleStopGeneration,
180181
onRetryMessage: messageOperations.handleRetryMessage,
182+
onContinueMessage: messageOperations.handleContinueMessage,
181183
onEditMessage: messageOperations.handleEditMessage,
182184
onEditAndResendMessage: messageOperations.handleEditAndResendMessage,
183185
onToggleFavorite: messageOperations.handleToggleFavorite,

src/renderer/src/components/pages/chat/ChatWindow.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
276276
onSendMessage,
277277
onStopGeneration,
278278
onRetryMessage,
279+
onContinueMessage,
279280
onEditMessage,
280281
onEditAndResendMessage,
281282
onToggleFavorite,
@@ -325,6 +326,7 @@ const ChatWindow = forwardRef<ChatWindowRef, ChatWindowProps>(({ chatId }, ref)
325326
llmConfigs={settings.llmConfigs || []}
326327
selectedMessageId={selectedMessageId}
327328
onRetryMessage={onRetryMessage}
329+
onContinueMessage={onContinueMessage}
328330
onEditMessage={onEditMessage}
329331
onEditAndResendMessage={onEditAndResendMessage}
330332
onToggleFavorite={onToggleFavorite}

src/renderer/src/components/pages/chat/MessageItem.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ interface MessageItemProps {
4343
branchCount?: number
4444
onBranchPrevious?: (messageId: string) => void
4545
onBranchNext?: (messageId: string) => void
46+
// 消息状态
47+
hasChildren?: boolean // 是否有后继消息
4648
// 原有的回调
4749
onRetry?: (messageId: string) => void
50+
onContinue?: (messageId: string) => void
4851
onEdit?: (messageId: string, newContent: string) => void
4952
onEditAndResend?: (messageId: string, newContent: string) => void
5053
onToggleFavorite?: (messageId: string) => void
@@ -73,7 +76,9 @@ const MessageItem = React.memo(function MessageItem({
7376
branchCount = 1,
7477
onBranchPrevious,
7578
onBranchNext,
79+
hasChildren = false,
7680
onRetry,
81+
onContinue,
7782
onEdit,
7883
onEditAndResend,
7984
onToggleFavorite,
@@ -130,6 +135,10 @@ const MessageItem = React.memo(function MessageItem({
130135
onRetry?.(message.id)
131136
}
132137

138+
const handleContinue = () => {
139+
onContinue?.(message.id)
140+
}
141+
133142
const handleEdit = () => {
134143
setIsEditing(true)
135144
setEditContent(currentContent)
@@ -554,6 +563,17 @@ const MessageItem = React.memo(function MessageItem({
554563
/>
555564
</Tooltip>
556565
)}
566+
{message.role === 'user' && onContinue && !hasChildren && (
567+
<Tooltip title="继续">
568+
<Button
569+
type="text"
570+
size="small"
571+
icon={<SendOutlined />}
572+
onClick={handleContinue}
573+
disabled={isLoading || isCurrentlyStreaming}
574+
/>
575+
</Tooltip>
576+
)}
557577
{onDelete && (
558578
<Tooltip title="删除">
559579
<Button
@@ -598,6 +618,7 @@ const MessageItem = React.memo(function MessageItem({
598618
prevProps.hasChildBranches === nextProps.hasChildBranches &&
599619
prevProps.branchIndex === nextProps.branchIndex &&
600620
prevProps.branchCount === nextProps.branchCount &&
621+
prevProps.hasChildren === nextProps.hasChildren &&
601622
prevProps.llmConfigs === nextProps.llmConfigs
602623
)
603624
})

src/renderer/src/components/pages/chat/MessageList.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface MessageListProps {
1717
llmConfigs?: LLMConfig[]
1818
selectedMessageId?: string | null // 新增:选中的消息ID,用于滚动定位
1919
onRetryMessage?: (messageId: string) => void
20+
onContinueMessage?: (messageId: string) => void
2021
onEditMessage?: (messageId: string, newContent: string) => void
2122
onEditAndResendMessage?: (messageId: string, newContent: string) => void
2223
onToggleFavorite?: (messageId: string) => void
@@ -42,6 +43,7 @@ const MessageList = React.memo(function MessageList({
4243
llmConfigs = [],
4344
selectedMessageId,
4445
onRetryMessage,
46+
onContinueMessage,
4547
onEditMessage,
4648
onEditAndResendMessage,
4749
onToggleFavorite,
@@ -347,8 +349,11 @@ const MessageList = React.memo(function MessageList({
347349
branchCount={messageTree.getSiblingBranchCount(message.id)}
348350
onBranchPrevious={(messageId) => handleSiblingBranchSwitch(messageId, 'previous')}
349351
onBranchNext={(messageId) => handleSiblingBranchSwitch(messageId, 'next')}
352+
// 消息状态
353+
hasChildren={messages.some(msg => msg.parentId === message.id)}
350354
// 原有的回调
351355
onRetry={onRetryMessage}
356+
onContinue={onContinueMessage}
352357
onEdit={onEditMessage}
353358
onEditAndResend={onEditAndResendMessage}
354359
onToggleFavorite={onToggleFavorite}

src/renderer/src/components/pages/chat/hooks/useMessageOperations.ts

Lines changed: 158 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface UseMessageOperationsReturn {
2525
customParentId?: string
2626
) => Promise<void>
2727
handleRetryMessage: (messageId: string) => Promise<void>
28+
handleContinueMessage: (messageId: string) => Promise<void>
2829
handleEditMessage: (messageId: string, newContent: string) => Promise<void>
2930
handleEditAndResendMessage: (messageId: string, newContent: string) => Promise<void>
3031
handleToggleFavorite: (messageId: string) => void
@@ -171,6 +172,56 @@ export function useMessageOperations({
171172
[chat, isLoading, aiService, chatId, messageTree, setIsLoading]
172173
)
173174

175+
const handleContinueMessage = useCallback(
176+
async (messageId: string) => {
177+
if (!chat || isLoading) return
178+
179+
// 找到要继续的用户消息
180+
const targetMessage = chat.messages.find((msg: any) => msg.id === messageId)
181+
if (!targetMessage || targetMessage.role !== 'user') return
182+
183+
// 使用当前选中的模型
184+
const llmConfig = aiService.getLLMConfig(selectedModel)
185+
if (!llmConfig) {
186+
message.error('请先在设置中配置LLM')
187+
return
188+
}
189+
190+
setIsLoading(true)
191+
192+
try {
193+
// 获取当前路径上的消息历史,包含到该用户消息为止的所有消息
194+
const currentPath = chat.currentPath || messageTree.getCurrentPath()
195+
const currentPathMessages = currentPath
196+
.map((id: string) => chat.messages.find((msg: any) => msg.id === id))
197+
.filter(Boolean) as ChatMessage[]
198+
199+
// 构建到目标消息为止的消息历史
200+
let messagesToSend: ChatMessage[] = []
201+
const targetIndex = currentPathMessages.findIndex((msg) => msg.id === messageId)
202+
if (targetIndex >= 0) {
203+
messagesToSend = currentPathMessages.slice(0, targetIndex + 1)
204+
} else {
205+
// 如果在当前路径中找不到,直接从所有消息中构建路径
206+
messagesToSend = [targetMessage]
207+
}
208+
209+
// 从该用户消息继续生成AI回复
210+
await aiService.sendAIMessage(messagesToSend, llmConfig, messageId, 'chat', {
211+
continue: {
212+
fromMessageId: messageId
213+
}
214+
})
215+
} catch (error) {
216+
console.error('Continue message failed:', error)
217+
message.error('继续对话失败,请检查网络连接和配置:' + error)
218+
} finally {
219+
setIsLoading(false)
220+
}
221+
},
222+
[chat, isLoading, aiService, chatId, messageTree, setIsLoading, selectedModel]
223+
)
224+
174225
const handleEditMessage = useCallback(
175226
async (messageId: string, newContent: string) => {
176227
if (!chat) return
@@ -193,6 +244,9 @@ export function useMessageOperations({
193244
const targetMessage = chat.messages.find((msg: any) => msg.id === messageId)
194245
if (!targetMessage) return
195246

247+
// 判断当前消息是否有后继消息(子消息)
248+
const hasChildren = chat.messages.some((msg: any) => msg.parentId === messageId)
249+
196250
// 如果是用户消息,使用当前选中的模型;如果是AI消息,使用原消息的模型
197251
const modelIdToUse = targetMessage.role === 'user' ? selectedModel : targetMessage.modelId
198252
const llmConfig = aiService.getLLMConfig(modelIdToUse)
@@ -205,81 +259,120 @@ export function useMessageOperations({
205259

206260
try {
207261
if (targetMessage.role === 'user') {
208-
// 对于用户消息,创建一个新的编辑版本作为兄弟分支
209-
const editedUserMessage = {
210-
id: uuidv4(),
211-
role: 'user' as const,
212-
content: newContent.trim(),
213-
timestamp: Date.now(),
214-
parentId: targetMessage.parentId || null
215-
}
216-
217-
// 添加编辑后的用户消息
218-
addMessageToParent(chatId, editedUserMessage, targetMessage.parentId)
219-
220-
// 获取到编辑消息父节点为止的消息历史
221-
const currentPath = chat.currentPath || messageTree.getCurrentPath()
222-
const currentPathMessages = currentPath
223-
.map((id: string) => chat.messages.find((msg: any) => msg.id === id))
224-
.filter(Boolean) as ChatMessage[]
225-
226-
let messagesToSend: ChatMessage[] = []
227-
if (targetMessage.parentId) {
228-
const parentIndex = currentPathMessages.findIndex(
229-
(msg) => msg.id === targetMessage.parentId
262+
if (!hasChildren) {
263+
// 没有后继消息时,直接编辑原消息内容,然后追加AI回复
264+
const { updatePage } = usePagesStore.getState()
265+
const updatedMessages = chat.messages.map((msg: any) =>
266+
msg.id === messageId ? { ...msg, content: newContent.trim(), timestamp: Date.now() } : msg
267+
)
268+
updatePage(chatId, { messages: updatedMessages })
269+
270+
// 获取当前路径上的消息历史
271+
const currentPath = chat.currentPath || messageTree.getCurrentPath()
272+
const currentPathMessages = currentPath
273+
.map((id: string) => updatedMessages.find((msg: any) => msg.id === id))
274+
.filter(Boolean) as ChatMessage[]
275+
276+
// 直接追加AI回复,就像正常发送消息一样
277+
await aiService.sendAIMessage(
278+
currentPathMessages,
279+
llmConfig,
280+
messageId,
281+
'edit_resend',
282+
{
283+
editResend: {
284+
originalMessageId: messageId,
285+
newContent: newContent,
286+
isDirectAppend: true
287+
}
288+
}
230289
)
231-
if (parentIndex >= 0) {
232-
messagesToSend = [...currentPathMessages.slice(0, parentIndex + 1), editedUserMessage]
290+
} else {
291+
// 有后继消息时,创建兄弟分支
292+
const editedUserMessage = {
293+
id: uuidv4(),
294+
role: 'user' as const,
295+
content: newContent.trim(),
296+
timestamp: Date.now(),
297+
parentId: targetMessage.parentId || null
298+
}
299+
300+
// 添加编辑后的用户消息
301+
addMessageToParent(chatId, editedUserMessage, targetMessage.parentId)
302+
303+
// 获取到编辑消息父节点为止的消息历史
304+
const currentPath = chat.currentPath || messageTree.getCurrentPath()
305+
const currentPathMessages = currentPath
306+
.map((id: string) => chat.messages.find((msg: any) => msg.id === id))
307+
.filter(Boolean) as ChatMessage[]
308+
309+
let messagesToSend: ChatMessage[] = []
310+
if (targetMessage.parentId) {
311+
const parentIndex = currentPathMessages.findIndex(
312+
(msg) => msg.id === targetMessage.parentId
313+
)
314+
if (parentIndex >= 0) {
315+
messagesToSend = [...currentPathMessages.slice(0, parentIndex + 1), editedUserMessage]
316+
} else {
317+
messagesToSend = [editedUserMessage]
318+
}
233319
} else {
234320
messagesToSend = [editedUserMessage]
235321
}
236-
} else {
237-
messagesToSend = [editedUserMessage]
238-
}
239322

240-
// 生成新的AI回复
241-
await aiService.sendAIMessage(
242-
messagesToSend,
243-
llmConfig,
244-
editedUserMessage.id,
245-
'edit_resend',
246-
{
247-
editResend: {
248-
originalMessageId: messageId,
249-
newContent: newContent
323+
// 生成新的AI回复
324+
await aiService.sendAIMessage(
325+
messagesToSend,
326+
llmConfig,
327+
editedUserMessage.id,
328+
'edit_resend',
329+
{
330+
editResend: {
331+
originalMessageId: messageId,
332+
newContent: newContent
333+
}
250334
}
251-
}
252-
)
335+
)
336+
}
253337
} else {
254-
// 对于AI消息,从其父消息重新生成
255-
const currentPath = chat.currentPath || messageTree.getCurrentPath()
256-
const currentPathMessages = currentPath
257-
.map((id: string) => chat.messages.find((msg: any) => msg.id === id))
258-
.filter(Boolean) as ChatMessage[]
259-
260-
let messagesToSend: ChatMessage[] = []
261-
if (targetMessage.parentId) {
262-
const parentIndex = currentPathMessages.findIndex(
263-
(msg) => msg.id === targetMessage.parentId
338+
if (!hasChildren) {
339+
// AI消息没有后继消息时,只需要直接编辑内容,不需要重新生成
340+
const { updatePage } = usePagesStore.getState()
341+
const updatedMessages = chat.messages.map((msg: any) =>
342+
msg.id === messageId ? { ...msg, content: newContent.trim(), timestamp: Date.now() } : msg
264343
)
265-
if (parentIndex >= 0) {
266-
messagesToSend = currentPathMessages.slice(0, parentIndex + 1)
344+
updatePage(chatId, { messages: updatedMessages })
345+
} else {
346+
// 有后继消息时,从父消息重新生成作为兄弟分支
347+
const currentPath = chat.currentPath || messageTree.getCurrentPath()
348+
const currentPathMessages = currentPath
349+
.map((id: string) => chat.messages.find((msg: any) => msg.id === id))
350+
.filter(Boolean) as ChatMessage[]
351+
352+
let messagesToSend: ChatMessage[] = []
353+
if (targetMessage.parentId) {
354+
const parentIndex = currentPathMessages.findIndex(
355+
(msg) => msg.id === targetMessage.parentId
356+
)
357+
if (parentIndex >= 0) {
358+
messagesToSend = currentPathMessages.slice(0, parentIndex + 1)
359+
}
267360
}
268-
}
269361

270-
// 生成新的AI回复作为兄弟分支
271-
await aiService.sendAIMessage(
272-
messagesToSend,
273-
llmConfig,
274-
targetMessage.parentId,
275-
'edit_resend',
276-
{
277-
editResend: {
278-
originalMessageId: messageId,
279-
newContent: newContent
362+
// 生成新的AI回复作为兄弟分支
363+
await aiService.sendAIMessage(
364+
messagesToSend,
365+
llmConfig,
366+
targetMessage.parentId,
367+
'edit_resend',
368+
{
369+
editResend: {
370+
originalMessageId: messageId,
371+
newContent: newContent
372+
}
280373
}
281-
}
282-
)
374+
)
375+
}
283376
}
284377
} catch (error) {
285378
console.error('Edit and resend failed:', error)
@@ -421,6 +514,7 @@ export function useMessageOperations({
421514
return {
422515
handleSendMessage,
423516
handleRetryMessage,
517+
handleContinueMessage,
424518
handleEditMessage,
425519
handleEditAndResendMessage,
426520
handleToggleFavorite,

0 commit comments

Comments
 (0)