Skip to content
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

feat(ui): add support of Edit button in chat side panel #3255

Merged
merged 6 commits into from
Oct 21, 2024
Merged
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
51 changes: 43 additions & 8 deletions ee/tabby-ui/components/chat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ function ChatRenderer(
const [userMessage, threadRunOptions] = generateRequestPayload(
qaPair.user
)

return regenerate({
threadId,
userMessageId: qaPair.user.id,
Expand All @@ -176,6 +177,35 @@ function ChatRenderer(
}
}

const onEditMessage = async (userMessageId: string) => {
if (!threadId) return

const qaPair = qaPairs.find(o => o.user.id === userMessageId)
if (!qaPair?.user || !qaPair.assistant) return

const userMessage = qaPair.user
let nextClientContext: Context[] = []

// restore client context
if (userMessage.relevantContext?.length) {
nextClientContext = nextClientContext.concat(userMessage.relevantContext)
}

setRelevantContext(uniqWith(nextClientContext, isEqual))

// delete message pair
const nextQaPairs = qaPairs.filter(o => o.user.id !== userMessageId)
setQaPairs(nextQaPairs)
setInput(userMessage.message)
if (userMessage.activeContext) {
onNavigateToContext(userMessage.activeContext, {
openInEditor: true
})
}

deleteThreadMessagePair(threadId, qaPair?.user.id, qaPair?.assistant?.id)
}

// Reload the last AI chat response
const onReload = async () => {
if (!qaPairs?.length) return
Expand All @@ -195,7 +225,7 @@ function ChatRenderer(

const handleMessageAction = (
userMessageId: string,
actionType: 'delete' | 'regenerate'
actionType: MessageActionType
) => {
switch (actionType) {
case 'delete':
Expand All @@ -204,6 +234,9 @@ function ChatRenderer(
case 'regenerate':
onRegenerateResponse(userMessageId)
break
case 'edit':
onEditMessage(userMessageId)
break
default:
break
}
Expand Down Expand Up @@ -291,8 +324,9 @@ function ChatRenderer(
userMessage: UserMessage
): [CreateMessageInput, ThreadRunOptionsInput] => {
// use selectContext or activeContext for code query
const contextForCodeQuery =
userMessage?.selectContext || userMessage?.activeContext
const contextForCodeQuery: FileContext | undefined =
userMessage.selectContext || userMessage.activeContext

const codeQuery: InputMaybe<CodeQueryInput> = contextForCodeQuery
? {
content: contextForCodeQuery.content ?? '',
Expand Down Expand Up @@ -350,13 +384,14 @@ function ChatRenderer(
}\n${'```'}\n`
}

const newUserMessage = {
const newUserMessage: UserMessage = {
...userMessage,
message: userMessage.message + selectCodeSnippet,
// For forward compatibility
activeSelection: activeSelection || userMessage.activeContext,
// If no id is provided, set a fallback id.
id: userMessage.id ?? nanoid()
id: userMessage.id ?? nanoid(),
selectContext: userMessage.selectContext,
// For forward compatibility
activeContext: activeSelection || userMessage.activeContext
}

const nextQaPairs = [
Expand All @@ -374,7 +409,7 @@ function ChatRenderer(

setQaPairs(nextQaPairs)

return sendUserMessage(...generateRequestPayload(newUserMessage))
sendUserMessage(...generateRequestPayload(newUserMessage))
}
)

Expand Down
20 changes: 18 additions & 2 deletions ee/tabby-ui/components/chat/question-answer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ import {
import { CopyButton } from '../copy-button'
import { ErrorMessageBlock, MessageMarkdown } from '../message-markdown'
import { Button } from '../ui/button'
import { IconFile, IconRefresh, IconTrash, IconUser } from '../ui/icons'
import {
IconEdit,
IconFile,
IconRefresh,
IconTrash,
IconUser
} from '../ui/icons'
import { Separator } from '../ui/separator'
import { Skeleton } from '../ui/skeleton'
import { MyAvatar } from '../user-avatar'
Expand Down Expand Up @@ -202,7 +208,17 @@ function UserMessageCardActions(props: { message: UserMessage }) {
<Button
variant="ghost"
size="icon"
onClick={e => handleMessageAction?.(message.id, 'delete')}
onClick={e => handleMessageAction(message.id, 'edit')}
>
<IconEdit />
<span className="sr-only">Edit message</span>
</Button>
)}
{!isLoading && (
<Button
variant="ghost"
size="icon"
onClick={e => handleMessageAction(message.id, 'delete')}
>
<IconTrash />
<span className="sr-only">Delete message</span>
Expand Down
2 changes: 1 addition & 1 deletion ee/tabby-ui/lib/types/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export type SearchReponse = {
num_hits?: number
}

export type MessageActionType = 'delete' | 'regenerate'
export type MessageActionType = 'delete' | 'regenerate' | 'edit'

type Keys<T> = T extends any ? keyof T : never
type Pick<T, K extends Keys<T>> = T extends { [k in K]?: any }
Expand Down