Skip to content

Commit 56288be

Browse files
fix(ui): fix attachment logic on queued mothership messages
1 parent cd8c5bd commit 56288be

8 files changed

Lines changed: 132 additions & 70 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import {
1010
} from '@/app/workspace/[workspaceId]/home/components/message-content'
1111
import { PendingTagIndicator } from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags'
1212
import { QueuedMessages } from '@/app/workspace/[workspaceId]/home/components/queued-messages'
13-
import { UserInput } from '@/app/workspace/[workspaceId]/home/components/user-input'
13+
import {
14+
UserInput,
15+
type UserInputHandle,
16+
} from '@/app/workspace/[workspaceId]/home/components/user-input'
1417
import { UserMessageContent } from '@/app/workspace/[workspaceId]/home/components/user-message-content'
1518
import type {
1619
ChatMessage,
@@ -36,14 +39,12 @@ interface MothershipChatProps {
3639
messageQueue: QueuedMessage[]
3740
onRemoveQueuedMessage: (id: string) => void
3841
onSendQueuedMessage: (id: string) => Promise<void>
39-
onEditQueuedMessage: (id: string) => void
42+
onEditQueuedMessage: (id: string) => QueuedMessage | undefined
4043
userId?: string
4144
chatId?: string
4245
onContextAdd?: (context: ChatContext) => void
4346
onContextRemove?: (context: ChatContext) => void
4447
onWorkspaceResourceSelect?: (resource: MothershipResource) => void
45-
editValue?: string
46-
onEditValueConsumed?: () => void
4748
layout?: 'mothership-view' | 'copilot-view'
4849
initialScrollBlocked?: boolean
4950
animateInput?: boolean
@@ -91,8 +92,6 @@ export function MothershipChat({
9192
onContextAdd,
9293
onContextRemove,
9394
onWorkspaceResourceSelect,
94-
editValue,
95-
onEditValueConsumed,
9695
layout = 'mothership-view',
9796
initialScrollBlocked = false,
9897
animateInput = false,
@@ -106,11 +105,24 @@ export function MothershipChat({
106105
})
107106
const hasMessages = messages.length > 0
108107
const initialScrollDoneRef = useRef(false)
108+
const userInputRef = useRef<UserInputHandle>(null)
109109
const handleSendQueuedHead = useCallback(() => {
110110
const topMessage = messageQueue[0]
111111
if (!topMessage) return
112112
void onSendQueuedMessage(topMessage.id)
113113
}, [messageQueue, onSendQueuedMessage])
114+
const handleEditQueued = useCallback(
115+
(id: string) => {
116+
const msg = onEditQueuedMessage(id)
117+
if (msg) userInputRef.current?.loadQueuedMessage(msg)
118+
},
119+
[onEditQueuedMessage]
120+
)
121+
const handleEditQueuedTail = useCallback(() => {
122+
const tail = messageQueue[messageQueue.length - 1]
123+
if (!tail) return
124+
handleEditQueued(tail.id)
125+
}, [messageQueue, handleEditQueued])
114126

115127
useLayoutEffect(() => {
116128
if (!hasMessages) {
@@ -205,19 +217,19 @@ export function MothershipChat({
205217
messageQueue={messageQueue}
206218
onRemove={onRemoveQueuedMessage}
207219
onSendNow={onSendQueuedMessage}
208-
onEdit={onEditQueuedMessage}
220+
onEdit={handleEditQueued}
209221
/>
210222
<UserInput
223+
ref={userInputRef}
211224
onSubmit={onSubmit}
212225
isSending={isStreamActive}
213226
onStopGeneration={onStopGeneration}
214227
isInitialView={false}
215228
userId={userId}
216229
onContextAdd={onContextAdd}
217230
onContextRemove={onContextRemove}
218-
editValue={editValue}
219-
onEditValueConsumed={onEditValueConsumed}
220231
onSendQueuedHead={handleSendQueuedHead}
232+
onEditQueuedTail={handleEditQueuedTail}
221233
/>
222234
</div>
223235
</div>

apps/sim/app/workspace/[workspaceId]/home/components/queued-messages/queued-messages.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use client'
22

33
import { useState } from 'react'
4-
import { ArrowUp, ChevronDown, ChevronRight, Pencil, Trash2 } from 'lucide-react'
4+
import { ArrowUp, ChevronDown, ChevronRight, Paperclip, Pencil, Trash2 } from 'lucide-react'
55
import { Tooltip } from '@/components/emcn'
6+
import { UserMessageContent } from '@/app/workspace/[workspaceId]/home/components/user-message-content'
67
import type { QueuedMessage } from '@/app/workspace/[workspaceId]/home/types'
78

89
interface QueuedMessagesProps {
@@ -39,16 +40,32 @@ export function QueuedMessages({ messageQueue, onRemove, onSendNow, onEdit }: Qu
3940
{messageQueue.map((msg) => (
4041
<div
4142
key={msg.id}
42-
className='flex items-center gap-2 px-3.5 py-1.5 transition-colors hover-hover:bg-[var(--surface-active)]'
43+
className='flex items-center gap-2 py-1.5 pr-2 pl-3.5 transition-colors hover-hover:bg-[var(--surface-active)]'
4344
>
4445
<div className='flex h-[16px] w-[16px] shrink-0 items-center justify-center'>
4546
<div className='h-[10px] w-[10px] rounded-full border-[1.5px] border-[color-mix(in_srgb,var(--text-tertiary)_40%,transparent)]' />
4647
</div>
4748

48-
<div className='min-w-0 flex-1'>
49-
<p className='truncate text-[var(--text-primary)] text-small'>{msg.content}</p>
49+
<div className='min-w-0 flex-1 overflow-hidden'>
50+
<UserMessageContent
51+
content={msg.content}
52+
contexts={msg.contexts}
53+
className='!truncate !whitespace-nowrap !text-small !leading-[20px]'
54+
/>
5055
</div>
5156

57+
{msg.fileAttachments && msg.fileAttachments.length > 0 && (
58+
<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'>
59+
<Paperclip className='h-[12px] w-[12px] shrink-0 text-[var(--text-icon)]' />
60+
<span className='truncate'>{msg.fileAttachments[0].filename}</span>
61+
{msg.fileAttachments.length > 1 && (
62+
<span className='shrink-0 text-[var(--text-secondary)]'>
63+
+{msg.fileAttachments.length - 1}
64+
</span>
65+
)}
66+
</span>
67+
)}
68+
5269
<div className='flex shrink-0 items-center gap-0.5'>
5370
<Tooltip.Root>
5471
<Tooltip.Trigger asChild>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { UserInput } from './user-input'
1+
export { UserInput, type UserInputHandle } from './user-input'

apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
'use client'
22

33
import type React from 'react'
4-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
4+
import {
5+
forwardRef,
6+
useCallback,
7+
useEffect,
8+
useImperativeHandle,
9+
useLayoutEffect,
10+
useMemo,
11+
useRef,
12+
useState,
13+
} from 'react'
514
import { useParams } from 'next/navigation'
615
import { useSession } from '@/lib/auth/auth-client'
716
import { SIM_RESOURCE_DRAG_TYPE, SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/resource-types'
@@ -26,6 +35,7 @@ import {
2635
import type {
2736
FileAttachmentForApi,
2837
MothershipResource,
38+
QueuedMessage,
2939
} from '@/app/workspace/[workspaceId]/home/types'
3040
import {
3141
useContextManagement,
@@ -91,8 +101,6 @@ function getCaretAnchor(
91101

92102
interface UserInputProps {
93103
defaultValue?: string
94-
editValue?: string
95-
onEditValueConsumed?: () => void
96104
onSubmit: (
97105
text: string,
98106
fileAttachments?: FileAttachmentForApi[],
@@ -105,21 +113,28 @@ interface UserInputProps {
105113
onContextAdd?: (context: ChatContext) => void
106114
onContextRemove?: (context: ChatContext) => void
107115
onSendQueuedHead?: () => void
116+
onEditQueuedTail?: () => void
117+
}
118+
119+
export interface UserInputHandle {
120+
loadQueuedMessage: (msg: QueuedMessage) => void
108121
}
109122

110-
export function UserInput({
111-
defaultValue = '',
112-
editValue,
113-
onEditValueConsumed,
114-
onSubmit,
115-
isSending,
116-
onStopGeneration,
117-
isInitialView = true,
118-
userId,
119-
onContextAdd,
120-
onContextRemove,
121-
onSendQueuedHead,
122-
}: UserInputProps) {
123+
export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function UserInput(
124+
{
125+
defaultValue = '',
126+
onSubmit,
127+
isSending,
128+
onStopGeneration,
129+
isInitialView = true,
130+
userId,
131+
onContextAdd,
132+
onContextRemove,
133+
onSendQueuedHead,
134+
onEditQueuedTail,
135+
},
136+
ref
137+
) {
123138
const { workspaceId } = useParams<{ workspaceId: string }>()
124139
const { navigateToSettings } = useSettingsNavigation()
125140
const { data: workflowsById = {} } = useWorkflowMap(workspaceId)
@@ -136,18 +151,6 @@ export function UserInput({
136151
setPrevDefaultValue(defaultValue)
137152
}
138153

139-
const [prevEditValue, setPrevEditValue] = useState(editValue)
140-
if (editValue && editValue !== prevEditValue) {
141-
setPrevEditValue(editValue)
142-
setValue(editValue)
143-
} else if (!editValue && prevEditValue) {
144-
setPrevEditValue(editValue)
145-
}
146-
147-
useEffect(() => {
148-
if (editValue) onEditValueConsumed?.()
149-
}, [editValue, onEditValueConsumed])
150-
151154
const files = useFileAttachments({
152155
userId: userId || session?.user?.id,
153156
workspaceId,
@@ -158,6 +161,27 @@ export function UserInput({
158161

159162
const contextManagement = useContextManagement({ message: value })
160163

164+
useImperativeHandle(
165+
ref,
166+
() => ({
167+
loadQueuedMessage: (msg: QueuedMessage) => {
168+
setValue(msg.content)
169+
const restored: AttachedFile[] = (msg.fileAttachments ?? []).map((a) => ({
170+
id: a.id,
171+
name: a.filename,
172+
size: a.size,
173+
type: a.media_type,
174+
path: a.path ?? '',
175+
key: a.key,
176+
uploading: false,
177+
}))
178+
files.restoreAttachedFiles(restored)
179+
contextManagement.setSelectedContexts(msg.contexts ?? [])
180+
},
181+
}),
182+
[files.restoreAttachedFiles, contextManagement.setSelectedContexts]
183+
)
184+
161185
const { addContext } = contextManagement
162186

163187
const handleContextAdd = useCallback(
@@ -269,6 +293,8 @@ export function UserInput({
269293
contextRef.current = contextManagement
270294
const onSendQueuedHeadRef = useRef(onSendQueuedHead)
271295
onSendQueuedHeadRef.current = onSendQueuedHead
296+
const onEditQueuedTailRef = useRef(onEditQueuedTail)
297+
onEditQueuedTailRef.current = onEditQueuedTail
272298
const isSendingRef = useRef(isSending)
273299
isSendingRef.current = isSending
274300

@@ -430,6 +456,7 @@ export function UserInput({
430456
filename: f.name,
431457
media_type: f.type,
432458
size: f.size,
459+
...(f.path ? { path: f.path } : {}),
433460
}))
434461

435462
onSubmit(
@@ -452,6 +479,16 @@ export function UserInput({
452479

453480
const handleKeyDown = useCallback(
454481
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
482+
if (e.key === 'ArrowUp' && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey) {
483+
const isEmpty =
484+
valueRef.current.length === 0 && filesRef.current.attachedFiles.length === 0
485+
if (isEmpty && onEditQueuedTailRef.current) {
486+
e.preventDefault()
487+
onEditQueuedTailRef.current()
488+
return
489+
}
490+
}
491+
455492
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
456493
e.preventDefault()
457494
const hasSubmitPayload =
@@ -763,4 +800,4 @@ export function UserInput({
763800
{files.isDragging && <DropOverlay />}
764801
</div>
765802
)
766-
}
803+
})

apps/sim/app/workspace/[workspaceId]/home/components/user-message-content/user-message-content.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useMemo } from 'react'
44
import { useParams } from 'next/navigation'
5+
import { cn } from '@/lib/core/utils/cn'
56
import { ContextMentionIcon } from '@/app/workspace/[workspaceId]/home/components/context-mention-icon'
67
import type { ChatMessageContext } from '@/app/workspace/[workspaceId]/home/types'
78
import { useWorkflows } from '@/hooks/queries/workflows'
@@ -12,6 +13,7 @@ const USER_MESSAGE_CLASSES =
1213
interface UserMessageContentProps {
1314
content: string
1415
contexts?: ChatMessageContext[]
16+
className?: string
1517
}
1618

1719
function escapeRegex(str: string): string {
@@ -64,17 +66,18 @@ function MentionHighlight({ context }: { context: ChatMessageContext }) {
6466
)
6567
}
6668

67-
export function UserMessageContent({ content, contexts }: UserMessageContentProps) {
69+
export function UserMessageContent({ content, contexts, className }: UserMessageContentProps) {
6870
const trimmed = content.trim()
71+
const classes = cn(USER_MESSAGE_CLASSES, className)
6972

7073
if (!contexts || contexts.length === 0) {
71-
return <p className={USER_MESSAGE_CLASSES}>{trimmed}</p>
74+
return <p className={classes}>{trimmed}</p>
7275
}
7376

7477
const ranges = computeMentionRanges(content, contexts)
7578

7679
if (ranges.length === 0) {
77-
return <p className={USER_MESSAGE_CLASSES}>{trimmed}</p>
80+
return <p className={classes}>{trimmed}</p>
7881
}
7982

8083
const elements: React.ReactNode[] = []
@@ -97,5 +100,5 @@ export function UserMessageContent({ content, contexts }: UserMessageContentProp
97100
elements.push(<span key={`tail-${lastIndex}`}>{tail}</span>)
98101
}
99102

100-
return <p className={USER_MESSAGE_CLASSES}>{elements}</p>
103+
return <p className={classes}>{elements}</p>
101104
}

apps/sim/app/workspace/[workspaceId]/home/home.tsx

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -159,26 +159,6 @@ export function Home({ chatId }: HomeProps = {}) {
159159
})
160160
)
161161

162-
const [editingInputValue, setEditingInputValue] = useState('')
163-
const [prevChatId, setPrevChatId] = useState(chatId)
164-
const clearEditingValue = useCallback(() => setEditingInputValue(''), [])
165-
166-
// Clear editing value when navigating to a different chat (guarded render-phase update)
167-
if (chatId !== prevChatId) {
168-
setPrevChatId(chatId)
169-
setEditingInputValue('')
170-
}
171-
172-
const handleEditQueuedMessage = useCallback(
173-
(id: string) => {
174-
const msg = editQueuedMessage(id)
175-
if (msg) {
176-
setEditingInputValue(msg.content)
177-
}
178-
},
179-
[editQueuedMessage]
180-
)
181-
182162
useEffect(() => {
183163
const url = new URL(window.location.href)
184164
if (activeResourceId) {
@@ -375,13 +355,11 @@ export function Home({ chatId }: HomeProps = {}) {
375355
messageQueue={messageQueue}
376356
onRemoveQueuedMessage={removeFromQueue}
377357
onSendQueuedMessage={sendNow}
378-
onEditQueuedMessage={handleEditQueuedMessage}
358+
onEditQueuedMessage={editQueuedMessage}
379359
userId={session?.user?.id}
380360
chatId={resolvedChatId}
381361
onContextAdd={handleContextAdd}
382362
onWorkspaceResourceSelect={handleWorkspaceResourceSelect}
383-
editValue={editingInputValue}
384-
onEditValueConsumed={clearEditingValue}
385363
animateInput={isInputEntering}
386364
onInputAnimationEnd={isInputEntering ? () => setIsInputEntering(false) : undefined}
387365
initialScrollBlocked={resources.length > 0 && isResourceCollapsed}

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface FileAttachmentForApi {
4646
filename: string
4747
media_type: string
4848
size: number
49+
path?: string
4950
}
5051

5152
export interface QueuedMessage {

0 commit comments

Comments
 (0)