mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(ui): fix attachment logic on queued mothership messages (#4191)
* fix(ui): fix attachment logic on queued mothership messages * Add focus after hitting pencil button for queued message * fix copilot layout
This commit is contained in:
+21
-9
@@ -10,7 +10,10 @@ import {
|
||||
} from '@/app/workspace/[workspaceId]/home/components/message-content'
|
||||
import { PendingTagIndicator } from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags'
|
||||
import { QueuedMessages } from '@/app/workspace/[workspaceId]/home/components/queued-messages'
|
||||
import { UserInput } from '@/app/workspace/[workspaceId]/home/components/user-input'
|
||||
import {
|
||||
UserInput,
|
||||
type UserInputHandle,
|
||||
} from '@/app/workspace/[workspaceId]/home/components/user-input'
|
||||
import { UserMessageContent } from '@/app/workspace/[workspaceId]/home/components/user-message-content'
|
||||
import type {
|
||||
ChatMessage,
|
||||
@@ -36,14 +39,12 @@ interface MothershipChatProps {
|
||||
messageQueue: QueuedMessage[]
|
||||
onRemoveQueuedMessage: (id: string) => void
|
||||
onSendQueuedMessage: (id: string) => Promise<void>
|
||||
onEditQueuedMessage: (id: string) => void
|
||||
onEditQueuedMessage: (id: string) => QueuedMessage | undefined
|
||||
userId?: string
|
||||
chatId?: string
|
||||
onContextAdd?: (context: ChatContext) => void
|
||||
onContextRemove?: (context: ChatContext) => void
|
||||
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
|
||||
editValue?: string
|
||||
onEditValueConsumed?: () => void
|
||||
layout?: 'mothership-view' | 'copilot-view'
|
||||
initialScrollBlocked?: boolean
|
||||
animateInput?: boolean
|
||||
@@ -91,8 +92,6 @@ export function MothershipChat({
|
||||
onContextAdd,
|
||||
onContextRemove,
|
||||
onWorkspaceResourceSelect,
|
||||
editValue,
|
||||
onEditValueConsumed,
|
||||
layout = 'mothership-view',
|
||||
initialScrollBlocked = false,
|
||||
animateInput = false,
|
||||
@@ -106,11 +105,24 @@ export function MothershipChat({
|
||||
})
|
||||
const hasMessages = messages.length > 0
|
||||
const initialScrollDoneRef = useRef(false)
|
||||
const userInputRef = useRef<UserInputHandle>(null)
|
||||
const handleSendQueuedHead = useCallback(() => {
|
||||
const topMessage = messageQueue[0]
|
||||
if (!topMessage) return
|
||||
void onSendQueuedMessage(topMessage.id)
|
||||
}, [messageQueue, onSendQueuedMessage])
|
||||
const handleEditQueued = useCallback(
|
||||
(id: string) => {
|
||||
const msg = onEditQueuedMessage(id)
|
||||
if (msg) userInputRef.current?.loadQueuedMessage(msg)
|
||||
},
|
||||
[onEditQueuedMessage]
|
||||
)
|
||||
const handleEditQueuedTail = useCallback(() => {
|
||||
const tail = messageQueue[messageQueue.length - 1]
|
||||
if (!tail) return
|
||||
handleEditQueued(tail.id)
|
||||
}, [messageQueue, handleEditQueued])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!hasMessages) {
|
||||
@@ -205,9 +217,10 @@ export function MothershipChat({
|
||||
messageQueue={messageQueue}
|
||||
onRemove={onRemoveQueuedMessage}
|
||||
onSendNow={onSendQueuedMessage}
|
||||
onEdit={onEditQueuedMessage}
|
||||
onEdit={handleEditQueued}
|
||||
/>
|
||||
<UserInput
|
||||
ref={userInputRef}
|
||||
onSubmit={onSubmit}
|
||||
isSending={isStreamActive}
|
||||
onStopGeneration={onStopGeneration}
|
||||
@@ -215,9 +228,8 @@ export function MothershipChat({
|
||||
userId={userId}
|
||||
onContextAdd={onContextAdd}
|
||||
onContextRemove={onContextRemove}
|
||||
editValue={editValue}
|
||||
onEditValueConsumed={onEditValueConsumed}
|
||||
onSendQueuedHead={handleSendQueuedHead}
|
||||
onEditQueuedTail={handleEditQueuedTail}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+52
-7
@@ -1,10 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { ArrowUp, ChevronDown, ChevronRight, Pencil, Trash2 } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ArrowUp, ChevronDown, ChevronRight, Paperclip, Pencil, Trash2 } from 'lucide-react'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import { UserMessageContent } from '@/app/workspace/[workspaceId]/home/components/user-message-content'
|
||||
import type { QueuedMessage } from '@/app/workspace/[workspaceId]/home/types'
|
||||
|
||||
const NARROW_WIDTH_PX = 320
|
||||
|
||||
interface QueuedMessagesProps {
|
||||
messageQueue: QueuedMessage[]
|
||||
onRemove: (id: string) => void
|
||||
@@ -14,11 +17,28 @@ interface QueuedMessagesProps {
|
||||
|
||||
export function QueuedMessages({ messageQueue, onRemove, onSendNow, onEdit }: QueuedMessagesProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(true)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [isNarrow, setIsNarrow] = useState(false)
|
||||
|
||||
if (messageQueue.length === 0) return null
|
||||
const hasMessages = messageQueue.length > 0
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current
|
||||
if (!el) return
|
||||
const ro = new ResizeObserver((entries) => {
|
||||
setIsNarrow(entries[0].contentRect.width < NARROW_WIDTH_PX)
|
||||
})
|
||||
ro.observe(el)
|
||||
return () => ro.disconnect()
|
||||
}, [hasMessages])
|
||||
|
||||
if (!hasMessages) return null
|
||||
|
||||
return (
|
||||
<div className='-mb-3 mx-3.5 overflow-hidden rounded-t-[16px] border border-[var(--border-1)] border-b-0 bg-[var(--surface-3)] pb-3'>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className='-mb-3 mx-3.5 overflow-hidden rounded-t-[16px] border border-[var(--border-1)] border-b-0 bg-[var(--surface-3)] pb-3'
|
||||
>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
@@ -39,16 +59,41 @@ export function QueuedMessages({ messageQueue, onRemove, onSendNow, onEdit }: Qu
|
||||
{messageQueue.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className='flex items-center gap-2 px-3.5 py-1.5 transition-colors hover-hover:bg-[var(--surface-active)]'
|
||||
className='flex items-center gap-2 py-1.5 pr-2 pl-3.5 transition-colors hover-hover:bg-[var(--surface-active)]'
|
||||
>
|
||||
<div className='flex h-[16px] w-[16px] shrink-0 items-center justify-center'>
|
||||
<div className='h-[10px] w-[10px] rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--text-tertiary)_40%,transparent)]' />
|
||||
</div>
|
||||
|
||||
<div className='min-w-0 flex-1'>
|
||||
<p className='truncate text-[var(--text-primary)] text-small'>{msg.content}</p>
|
||||
<div className='min-w-0 flex-1 overflow-hidden'>
|
||||
<UserMessageContent
|
||||
content={msg.content}
|
||||
contexts={msg.contexts}
|
||||
plainMentions
|
||||
className='!truncate !whitespace-nowrap !text-small !leading-[20px]'
|
||||
/>
|
||||
</div>
|
||||
|
||||
{msg.fileAttachments && msg.fileAttachments.length > 0 && (
|
||||
<span className='inline-flex min-w-0 max-w-[40%] shrink items-center gap-1 rounded-[5px] bg-[var(--surface-5)] px-[5px] py-0.5 text-[var(--text-primary)] text-small'>
|
||||
<Paperclip className='h-[12px] w-[12px] shrink-0 text-[var(--text-icon)]' />
|
||||
{isNarrow ? (
|
||||
<span className='shrink-0 text-[var(--text-secondary)]'>
|
||||
{msg.fileAttachments.length}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className='truncate'>{msg.fileAttachments[0].filename}</span>
|
||||
{msg.fileAttachments.length > 1 && (
|
||||
<span className='shrink-0 text-[var(--text-secondary)]'>
|
||||
+{msg.fileAttachments.length - 1}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className='flex shrink-0 items-center gap-0.5'>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { UserInput } from './user-input'
|
||||
export { UserInput, type UserInputHandle } from './user-input'
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import type React from 'react'
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import { SIM_RESOURCE_DRAG_TYPE, SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/resource-types'
|
||||
@@ -26,6 +35,7 @@ import {
|
||||
import type {
|
||||
FileAttachmentForApi,
|
||||
MothershipResource,
|
||||
QueuedMessage,
|
||||
} from '@/app/workspace/[workspaceId]/home/types'
|
||||
import {
|
||||
useContextManagement,
|
||||
@@ -91,8 +101,6 @@ function getCaretAnchor(
|
||||
|
||||
interface UserInputProps {
|
||||
defaultValue?: string
|
||||
editValue?: string
|
||||
onEditValueConsumed?: () => void
|
||||
onSubmit: (
|
||||
text: string,
|
||||
fileAttachments?: FileAttachmentForApi[],
|
||||
@@ -105,21 +113,28 @@ interface UserInputProps {
|
||||
onContextAdd?: (context: ChatContext) => void
|
||||
onContextRemove?: (context: ChatContext) => void
|
||||
onSendQueuedHead?: () => void
|
||||
onEditQueuedTail?: () => void
|
||||
}
|
||||
|
||||
export function UserInput({
|
||||
defaultValue = '',
|
||||
editValue,
|
||||
onEditValueConsumed,
|
||||
onSubmit,
|
||||
isSending,
|
||||
onStopGeneration,
|
||||
isInitialView = true,
|
||||
userId,
|
||||
onContextAdd,
|
||||
onContextRemove,
|
||||
onSendQueuedHead,
|
||||
}: UserInputProps) {
|
||||
export interface UserInputHandle {
|
||||
loadQueuedMessage: (msg: QueuedMessage) => void
|
||||
}
|
||||
|
||||
export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function UserInput(
|
||||
{
|
||||
defaultValue = '',
|
||||
onSubmit,
|
||||
isSending,
|
||||
onStopGeneration,
|
||||
isInitialView = true,
|
||||
userId,
|
||||
onContextAdd,
|
||||
onContextRemove,
|
||||
onSendQueuedHead,
|
||||
onEditQueuedTail,
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>()
|
||||
const { navigateToSettings } = useSettingsNavigation()
|
||||
const { data: workflowsById = {} } = useWorkflowMap(workspaceId)
|
||||
@@ -136,18 +151,6 @@ export function UserInput({
|
||||
setPrevDefaultValue(defaultValue)
|
||||
}
|
||||
|
||||
const [prevEditValue, setPrevEditValue] = useState(editValue)
|
||||
if (editValue && editValue !== prevEditValue) {
|
||||
setPrevEditValue(editValue)
|
||||
setValue(editValue)
|
||||
} else if (!editValue && prevEditValue) {
|
||||
setPrevEditValue(editValue)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (editValue) onEditValueConsumed?.()
|
||||
}, [editValue, onEditValueConsumed])
|
||||
|
||||
const files = useFileAttachments({
|
||||
userId: userId || session?.user?.id,
|
||||
workspaceId,
|
||||
@@ -269,6 +272,8 @@ export function UserInput({
|
||||
contextRef.current = contextManagement
|
||||
const onSendQueuedHeadRef = useRef(onSendQueuedHead)
|
||||
onSendQueuedHeadRef.current = onSendQueuedHead
|
||||
const onEditQueuedTailRef = useRef(onEditQueuedTail)
|
||||
onEditQueuedTailRef.current = onEditQueuedTail
|
||||
const isSendingRef = useRef(isSending)
|
||||
isSendingRef.current = isSending
|
||||
|
||||
@@ -277,6 +282,34 @@ export function UserInput({
|
||||
const atInsertPosRef = useRef<number | null>(null)
|
||||
const pendingCursorRef = useRef<number | null>(null)
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
loadQueuedMessage: (msg: QueuedMessage) => {
|
||||
setValue(msg.content)
|
||||
const restored: AttachedFile[] = (msg.fileAttachments ?? []).map((a) => ({
|
||||
id: a.id,
|
||||
name: a.filename,
|
||||
size: a.size,
|
||||
type: a.media_type,
|
||||
path: a.path ?? '',
|
||||
key: a.key,
|
||||
uploading: false,
|
||||
}))
|
||||
files.restoreAttachedFiles(restored)
|
||||
contextManagement.setSelectedContexts(msg.contexts ?? [])
|
||||
requestAnimationFrame(() => {
|
||||
const textarea = textareaRef.current
|
||||
if (!textarea) return
|
||||
textarea.focus()
|
||||
const end = textarea.value.length
|
||||
textarea.setSelectionRange(end, end)
|
||||
})
|
||||
},
|
||||
}),
|
||||
[files.restoreAttachedFiles, contextManagement.setSelectedContexts, textareaRef]
|
||||
)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const textarea = textareaRef.current
|
||||
if (!textarea) return
|
||||
@@ -430,6 +463,7 @@ export function UserInput({
|
||||
filename: f.name,
|
||||
media_type: f.type,
|
||||
size: f.size,
|
||||
...(f.path ? { path: f.path } : {}),
|
||||
}))
|
||||
|
||||
onSubmit(
|
||||
@@ -452,6 +486,15 @@ export function UserInput({
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'ArrowUp' && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
||||
const isEmpty = valueRef.current.length === 0 && filesRef.current.attachedFiles.length === 0
|
||||
if (isEmpty && onEditQueuedTailRef.current) {
|
||||
e.preventDefault()
|
||||
onEditQueuedTailRef.current()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
|
||||
e.preventDefault()
|
||||
const hasSubmitPayload =
|
||||
@@ -763,4 +806,4 @@ export function UserInput({
|
||||
{files.isDragging && <DropOverlay />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
+28
-5
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { ContextMentionIcon } from '@/app/workspace/[workspaceId]/home/components/context-mention-icon'
|
||||
import type { ChatMessageContext } from '@/app/workspace/[workspaceId]/home/types'
|
||||
import { useWorkflows } from '@/hooks/queries/workflows'
|
||||
@@ -12,6 +13,9 @@ const USER_MESSAGE_CLASSES =
|
||||
interface UserMessageContentProps {
|
||||
content: string
|
||||
contexts?: ChatMessageContext[]
|
||||
className?: string
|
||||
/** When true, render mentions as plain inline text (no icon/pill) so truncation flows naturally. */
|
||||
plainMentions?: boolean
|
||||
}
|
||||
|
||||
function escapeRegex(str: string): string {
|
||||
@@ -64,17 +68,23 @@ function MentionHighlight({ context }: { context: ChatMessageContext }) {
|
||||
)
|
||||
}
|
||||
|
||||
export function UserMessageContent({ content, contexts }: UserMessageContentProps) {
|
||||
export function UserMessageContent({
|
||||
content,
|
||||
contexts,
|
||||
className,
|
||||
plainMentions = false,
|
||||
}: UserMessageContentProps) {
|
||||
const trimmed = content.trim()
|
||||
const classes = cn(USER_MESSAGE_CLASSES, className)
|
||||
|
||||
if (!contexts || contexts.length === 0) {
|
||||
return <p className={USER_MESSAGE_CLASSES}>{trimmed}</p>
|
||||
return <p className={classes}>{trimmed}</p>
|
||||
}
|
||||
|
||||
const ranges = computeMentionRanges(content, contexts)
|
||||
|
||||
if (ranges.length === 0) {
|
||||
return <p className={USER_MESSAGE_CLASSES}>{trimmed}</p>
|
||||
return <p className={classes}>{trimmed}</p>
|
||||
}
|
||||
|
||||
const elements: React.ReactNode[] = []
|
||||
@@ -88,7 +98,20 @@ export function UserMessageContent({ content, contexts }: UserMessageContentProp
|
||||
elements.push(<span key={`text-${i}-${lastIndex}`}>{before}</span>)
|
||||
}
|
||||
|
||||
elements.push(<MentionHighlight key={`mention-${i}-${range.start}`} context={range.context} />)
|
||||
if (plainMentions) {
|
||||
elements.push(
|
||||
<span
|
||||
key={`mention-${i}-${range.start}`}
|
||||
className='font-medium text-[var(--text-primary)]'
|
||||
>
|
||||
{content.slice(range.start, range.end)}
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
elements.push(
|
||||
<MentionHighlight key={`mention-${i}-${range.start}`} context={range.context} />
|
||||
)
|
||||
}
|
||||
lastIndex = range.end
|
||||
}
|
||||
|
||||
@@ -97,5 +120,5 @@ export function UserMessageContent({ content, contexts }: UserMessageContentProp
|
||||
elements.push(<span key={`tail-${lastIndex}`}>{tail}</span>)
|
||||
}
|
||||
|
||||
return <p className={USER_MESSAGE_CLASSES}>{elements}</p>
|
||||
return <p className={classes}>{elements}</p>
|
||||
}
|
||||
|
||||
@@ -159,26 +159,6 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
})
|
||||
)
|
||||
|
||||
const [editingInputValue, setEditingInputValue] = useState('')
|
||||
const [prevChatId, setPrevChatId] = useState(chatId)
|
||||
const clearEditingValue = useCallback(() => setEditingInputValue(''), [])
|
||||
|
||||
// Clear editing value when navigating to a different chat (guarded render-phase update)
|
||||
if (chatId !== prevChatId) {
|
||||
setPrevChatId(chatId)
|
||||
setEditingInputValue('')
|
||||
}
|
||||
|
||||
const handleEditQueuedMessage = useCallback(
|
||||
(id: string) => {
|
||||
const msg = editQueuedMessage(id)
|
||||
if (msg) {
|
||||
setEditingInputValue(msg.content)
|
||||
}
|
||||
},
|
||||
[editQueuedMessage]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href)
|
||||
if (activeResourceId) {
|
||||
@@ -375,13 +355,11 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
messageQueue={messageQueue}
|
||||
onRemoveQueuedMessage={removeFromQueue}
|
||||
onSendQueuedMessage={sendNow}
|
||||
onEditQueuedMessage={handleEditQueuedMessage}
|
||||
onEditQueuedMessage={editQueuedMessage}
|
||||
userId={session?.user?.id}
|
||||
chatId={resolvedChatId}
|
||||
onContextAdd={handleContextAdd}
|
||||
onWorkspaceResourceSelect={handleWorkspaceResourceSelect}
|
||||
editValue={editingInputValue}
|
||||
onEditValueConsumed={clearEditingValue}
|
||||
animateInput={isInputEntering}
|
||||
onInputAnimationEnd={isInputEntering ? () => setIsInputEntering(false) : undefined}
|
||||
initialScrollBlocked={resources.length > 0 && isResourceCollapsed}
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface FileAttachmentForApi {
|
||||
filename: string
|
||||
media_type: string
|
||||
size: number
|
||||
path?: string
|
||||
}
|
||||
|
||||
export interface QueuedMessage {
|
||||
|
||||
+14
@@ -301,6 +301,19 @@ export function useFileAttachments(props: UseFileAttachmentsProps) {
|
||||
setAttachedFiles([])
|
||||
}, [attachedFiles])
|
||||
|
||||
/**
|
||||
* Replaces the current attached files with a given set.
|
||||
* Cleans up preview URLs from the prior set before replacing.
|
||||
*/
|
||||
const restoreAttachedFiles = useCallback((files: AttachedFile[]) => {
|
||||
setAttachedFiles((prev) => {
|
||||
prev.forEach((f) => {
|
||||
if (f.previewUrl) URL.revokeObjectURL(f.previewUrl)
|
||||
})
|
||||
return files
|
||||
})
|
||||
}, [])
|
||||
|
||||
return {
|
||||
// State
|
||||
attachedFiles,
|
||||
@@ -321,6 +334,7 @@ export function useFileAttachments(props: UseFileAttachmentsProps) {
|
||||
handleDragOver,
|
||||
handleDrop,
|
||||
clearAttachedFiles,
|
||||
restoreAttachedFiles,
|
||||
processFiles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,17 +392,6 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
|
||||
wasCopilotSendingRef.current = copilotIsSending
|
||||
}, [copilotIsSending, loadCopilotChats])
|
||||
|
||||
const [copilotEditingInputValue, setCopilotEditingInputValue] = useState('')
|
||||
const clearCopilotEditingValue = useCallback(() => setCopilotEditingInputValue(''), [])
|
||||
|
||||
const handleCopilotEditQueuedMessage = useCallback(
|
||||
(id: string) => {
|
||||
const msg = copilotEditQueuedMessage(id)
|
||||
if (msg) setCopilotEditingInputValue(msg.content)
|
||||
},
|
||||
[copilotEditQueuedMessage]
|
||||
)
|
||||
|
||||
const handleCopilotStopGeneration = useCallback(() => {
|
||||
captureEvent(posthogRef.current, 'task_generation_aborted', {
|
||||
workspace_id: workspaceId,
|
||||
@@ -865,11 +854,9 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
|
||||
messageQueue={copilotMessageQueue}
|
||||
onRemoveQueuedMessage={copilotRemoveFromQueue}
|
||||
onSendQueuedMessage={copilotSendNow}
|
||||
onEditQueuedMessage={handleCopilotEditQueuedMessage}
|
||||
onEditQueuedMessage={copilotEditQueuedMessage}
|
||||
userId={session?.user?.id}
|
||||
chatId={copilotResolvedChatId}
|
||||
editValue={copilotEditingInputValue}
|
||||
onEditValueConsumed={clearCopilotEditingValue}
|
||||
layout='copilot-view'
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user