mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 21:29:08 +03:00
* add delete chat message functionality
* Implement message editing functionality in AIAssistant
* add functionality to delete messages in AI Assistant chat in assistant state
* remove duplicated `text-xs` class
* add a 'group' class for improved styling and refining action button visibility on hover
* Refine action button visibility in AIAssistantPanel to show only for user messages on hover, improving user experience.
* remove `"` from the message to edit
* Enhance editing message display in AIAssistantPanel by updating styling for improved visibility and user experience.
* Refactor AIAssistantPanel state management by simplifying original message content initialization and clarifying comments for better code readability.
* clean up action button rendering in AIAssistantPanel message component
* adjust the chat message layout (space)
* simplify the editing mode message
* show the edit button even when user is already in the edit mode
* Update AssistantChatForm styling for editing mode, positioning the editing message above the input field without changing the layout
* use `ButtonTooltip` for edit button to show tooltip
* simplify the condiiton to show the edit buton
* add the delete action button
* Refactor action button rendering in AIAssistantPanel to improve clarity and maintainability. Ensure buttons are only shown for user messages on hover, and simplify the conditional rendering logic.
* Remove commented-out code for cleaner implementation.
* improve unused onDeleteAfter prop and consolidating delete logic into a single `deleteMessageFromHere` function for improved clarity
* abort streaming when a user deletes a message
* delete unnecessary `originalMessageContent` state variable
* delete `confirmEdit` and move its logic into `sendMessageToAssistant`
* add `strokeWidth={1.5}` to `Pencil` icon
* add `opacity-50` to any messages that are after the message being edited, and it cancels the edit when those messages are clicked
* add a pulse animation to the user’s message being edited
* add more space between mesages and `AssistantChatForm`
* add framer-motion animation to `Editing message` element
* update strokeWidth of Trash2 icon in Message component for better visibility
* refactor Message component to make onDelete prop required
* add `DeleteMessageConfirmModal` for message deletion confirmation in Message component
* refactor message deletion logic in AIAssistant component to streamline editing process
* cancel editing when a user clicks the edit button again or the edit button in subsequent messages
* Add success toast notification for message deletion
* add Pencil icon to AIAssistantPanel component and fixed duplicated tailwind class
* Update AIAssistantPanel to use the Pencil icon
* Refactor AIAssistant to set editing message content from text parts
* Remove unused `reload` prop from AIAssistant component
* `setIsSticky(false)` when a user deletes all the messages or clear the chat to remove the `ArrowDown` icon scroll down button
* refactor: remove deleteMessagesBefore function from AiAssistantState
* refactor: simplify AssistantChatForm layout by removing unnecessary `z-index`
* enable the input field when a user wants to edit the message while the response is streaming
* Smol fixes
* remove conosle log
* Add state for resubmitting in AI Assistant panel
* Remove classes that hide the focus styles + fix focusing when editing message to ensure cursor is at the end of the text
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
164 lines
5.2 KiB
TypeScript
164 lines
5.2 KiB
TypeScript
import { ArrowUp, Loader2, Square } from 'lucide-react'
|
|
import { ChangeEvent, FormEvent, forwardRef, KeyboardEvent, memo, useRef } from 'react'
|
|
|
|
import { useBreakpoint } from 'common'
|
|
import { ExpandingTextArea } from 'ui'
|
|
import { cn } from 'ui/src/lib/utils'
|
|
import { ButtonTooltip } from '../ButtonTooltip'
|
|
import { type SqlSnippet } from './AIAssistant.types'
|
|
import { getSnippetContent, SnippetRow } from './SnippetRow'
|
|
|
|
export interface FormProps {
|
|
/* The ref for the textarea, optional. Exposed for the CommandsPopover to attach events. */
|
|
textAreaRef?: React.RefObject<HTMLTextAreaElement>
|
|
/* The loading state of the form */
|
|
loading: boolean
|
|
/* The disabled state of the form */
|
|
disabled?: boolean
|
|
/* The value of the textarea */
|
|
value?: string
|
|
/* The function to handle the value change */
|
|
onValueChange: (value: ChangeEvent<HTMLTextAreaElement>) => void
|
|
/**
|
|
* If true, include SQL snippets in the message sent to onSubmit
|
|
*/
|
|
includeSnippetsInMessage?: boolean
|
|
/**
|
|
* The function to handle the form submission
|
|
*/
|
|
onSubmit: (message: string) => void
|
|
/**
|
|
* The function to handle stopping the stream
|
|
*/
|
|
onStop?: () => void
|
|
/* The placeholder of the textarea */
|
|
placeholder?: string
|
|
/* SQL snippets to display above the form - can be strings or objects with label and content */
|
|
sqlSnippets?: SqlSnippet[]
|
|
/* Function to handle removing a SQL snippet */
|
|
onRemoveSnippet?: (index: number) => void
|
|
/* Additional class name for the snippets container */
|
|
snippetsClassName?: string
|
|
/* Additional class name for the form wrapper */
|
|
className?: string
|
|
/* If currently editing an existing message */
|
|
isEditing?: boolean
|
|
}
|
|
|
|
const AssistantChatFormComponent = forwardRef<HTMLFormElement, FormProps>(
|
|
(
|
|
{
|
|
loading = false,
|
|
disabled = false,
|
|
value = '',
|
|
textAreaRef,
|
|
onValueChange,
|
|
onSubmit,
|
|
onStop,
|
|
placeholder,
|
|
sqlSnippets,
|
|
onRemoveSnippet,
|
|
snippetsClassName,
|
|
includeSnippetsInMessage = false,
|
|
className,
|
|
isEditing = false,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
const formRef = useRef<HTMLFormElement>(null)
|
|
const isMobile = useBreakpoint('md')
|
|
|
|
const handleSubmit = (event?: FormEvent<HTMLFormElement>) => {
|
|
if (event) event.preventDefault()
|
|
if (!value || (loading && !isEditing)) return
|
|
|
|
let finalMessage = value
|
|
if (includeSnippetsInMessage && sqlSnippets && sqlSnippets.length > 0) {
|
|
const sqlSnippetsString = sqlSnippets
|
|
.map((snippet: SqlSnippet) => '```sql\n' + getSnippetContent(snippet) + '\n```')
|
|
.join('\n')
|
|
finalMessage = [value, sqlSnippetsString].filter(Boolean).join('\n\n')
|
|
}
|
|
|
|
onSubmit(finalMessage)
|
|
}
|
|
|
|
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
event.preventDefault()
|
|
handleSubmit()
|
|
}
|
|
}
|
|
|
|
const canSubmit = !loading && !!value
|
|
|
|
return (
|
|
<div className="w-full">
|
|
<form
|
|
id="assistant-chat"
|
|
ref={formRef}
|
|
{...props}
|
|
onSubmit={handleSubmit}
|
|
className={cn('relative', className)}
|
|
>
|
|
{sqlSnippets && sqlSnippets.length > 0 && (
|
|
<SnippetRow
|
|
snippets={sqlSnippets}
|
|
onRemoveSnippet={onRemoveSnippet}
|
|
className="absolute top-0 left-0 right-0 px-1.5 py-1.5"
|
|
/>
|
|
)}
|
|
<ExpandingTextArea
|
|
autoFocus={!isMobile}
|
|
ref={textAreaRef}
|
|
disabled={disabled}
|
|
className={cn(
|
|
'text-sm pr-10 max-h-64',
|
|
sqlSnippets && sqlSnippets.length > 0 && 'pt-10'
|
|
)}
|
|
placeholder={placeholder}
|
|
spellCheck={false}
|
|
rows={3}
|
|
value={value}
|
|
onChange={(event) => onValueChange(event)}
|
|
onKeyDown={handleKeyDown}
|
|
/>
|
|
<div className="absolute right-1.5 bottom-1.5 flex gap-3 items-center">
|
|
{loading ? (
|
|
onStop ? (
|
|
<ButtonTooltip
|
|
type="outline"
|
|
aria-label="Stop response"
|
|
icon={<Square fill="currentColor" className="scale-75" />}
|
|
onClick={onStop}
|
|
className="w-7 h-7 rounded-full p-0 text-center flex items-center justify-center"
|
|
tooltip={{ content: { side: 'top', text: 'Stop response' } }}
|
|
/>
|
|
) : (
|
|
<Loader2 size={22} className="animate-spin size-7 text-muted" strokeWidth={1} />
|
|
)
|
|
) : (
|
|
<ButtonTooltip
|
|
htmlType="submit"
|
|
aria-label="Send message"
|
|
icon={<ArrowUp />}
|
|
disabled={!canSubmit}
|
|
className={cn(
|
|
'w-7 h-7 rounded-full p-0 text-center flex items-center justify-center',
|
|
!canSubmit ? 'opacity-50' : 'opacity-100'
|
|
)}
|
|
tooltip={{ content: { side: 'top', text: 'Send message' } }}
|
|
/>
|
|
)}
|
|
</div>
|
|
</form>
|
|
</div>
|
|
)
|
|
}
|
|
)
|
|
|
|
AssistantChatFormComponent.displayName = 'AssistantChatFormComponent'
|
|
|
|
export const AssistantChatForm = memo(AssistantChatFormComponent)
|