Skip to content

Commit 2f90e41

Browse files
authored
feat(mothership): restore attachment previews on draft and add video support (#4435)
* feat(mothership): restore attachment previews on draft and add video support * fix(mothership): icon fallback behind video preview
1 parent 578fc50 commit 2f90e41

7 files changed

Lines changed: 66 additions & 13 deletions

File tree

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,36 @@ export function ChatMessageAttachments(props: {
3030
)}
3131
>
3232
{attachments.map((att) => {
33-
const isImage = att.media_type.startsWith('image/')
34-
return isImage && att.previewUrl ? (
33+
if (!att.previewUrl) {
34+
return (
35+
<FileAttachmentPill key={att.id} mediaType={att.media_type} filename={att.filename} />
36+
)
37+
}
38+
const isVideo = att.media_type.startsWith('video/')
39+
if (isVideo) {
40+
const Icon = getDocumentIcon(att.media_type, att.filename)
41+
return (
42+
<div
43+
key={att.id}
44+
className='relative h-[56px] w-[56px] overflow-hidden rounded-[8px] bg-[var(--surface-5)]'
45+
>
46+
<div className='absolute inset-0 flex items-center justify-center text-[var(--text-icon)]'>
47+
<Icon className='h-[18px] w-[18px]' />
48+
</div>
49+
<video
50+
src={att.previewUrl}
51+
muted
52+
playsInline
53+
preload='metadata'
54+
className='relative h-full w-full object-cover'
55+
/>
56+
</div>
57+
)
58+
}
59+
return (
3560
<div key={att.id} className='h-[56px] w-[56px] overflow-hidden rounded-[8px]'>
3661
<img src={att.previewUrl} alt={att.filename} className='h-full w-full object-cover' />
3762
</div>
38-
) : (
39-
<FileAttachmentPill key={att.id} mediaType={att.media_type} filename={att.filename} />
4063
)
4164
})}
4265
</div>

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,32 @@ export const AttachedFilesList = React.memo(function AttachedFilesList({
2222
return (
2323
<div className='mb-1.5 flex flex-wrap gap-1.5'>
2424
{attachedFiles.map((file) => {
25-
const isImage = file.type.startsWith('image/')
25+
const isVideo = file.type.startsWith('video/')
26+
const hasPreview = Boolean(file.previewUrl)
2627
return (
2728
<Tooltip.Root key={file.id}>
2829
<Tooltip.Trigger asChild>
2930
<div
3031
className='group relative h-[56px] w-[56px] flex-shrink-0 cursor-pointer overflow-hidden rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-5)] hover:bg-[var(--surface-4)]'
3132
onClick={() => onFileClick(file)}
3233
>
33-
{isImage && file.previewUrl ? (
34+
{hasPreview && isVideo ? (
35+
<>
36+
<div className='absolute inset-0 flex items-center justify-center text-[var(--text-icon)]'>
37+
{(() => {
38+
const Icon = getDocumentIcon(file.type, file.name)
39+
return <Icon className='h-[18px] w-[18px]' />
40+
})()}
41+
</div>
42+
<video
43+
src={file.previewUrl}
44+
muted
45+
playsInline
46+
preload='metadata'
47+
className='relative h-full w-full object-cover'
48+
/>
49+
</>
50+
) : hasPreview ? (
3451
<img
3552
src={file.previewUrl}
3653
alt={file.name}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Paperclip } from 'lucide-react'
1616
import { useParams } from 'next/navigation'
1717
import { Button, Tooltip } from '@/components/emcn'
1818
import { useSession } from '@/lib/auth/auth-client'
19+
import { getMothershipAttachmentPreviewUrl } from '@/lib/copilot/chat/attachment-preview'
1920
import { SIM_RESOURCE_DRAG_TYPE, SIM_RESOURCES_DRAG_TYPE } from '@/lib/copilot/resource-types'
2021
import { cn } from '@/lib/core/utils/cn'
2122
import { CHAT_ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation'
@@ -211,6 +212,7 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
211212
path: a.path ?? '',
212213
key: a.key,
213214
uploading: false,
215+
previewUrl: getMothershipAttachmentPreviewUrl(a),
214216
}))
215217
}
216218
if (typeof draft.text === 'string' && draft.text.length > 0) {
@@ -385,6 +387,7 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
385387
path: a.path ?? '',
386388
key: a.key,
387389
uploading: false,
390+
previewUrl: getMothershipAttachmentPreviewUrl(a),
388391
}))
389392
files.restoreAttachedFiles(restored)
390393
contextManagement.setSelectedContexts(msg.contexts ?? [])

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { sleep } from '@sim/utils/helpers'
55
import { generateId } from '@sim/utils/id'
66
import { useQueryClient } from '@tanstack/react-query'
77
import { usePathname, useRouter } from 'next/navigation'
8+
import { getMothershipAttachmentPreviewUrl } from '@/lib/copilot/chat/attachment-preview'
89
import { toDisplayMessage } from '@/lib/copilot/chat/display-message'
910
import { getLiveAssistantMessageId } from '@/lib/copilot/chat/effective-transcript'
1011
import type {
@@ -3303,9 +3304,7 @@ export function useChat(
33033304
filename: f.filename,
33043305
media_type: f.media_type,
33053306
size: f.size,
3306-
previewUrl: f.media_type.startsWith('image/')
3307-
? `/api/files/serve/${encodeURIComponent(f.key)}?context=mothership`
3308-
: undefined,
3307+
previewUrl: getMothershipAttachmentPreviewUrl(f),
33093308
}))
33103309

33113310
const optimisticUserMessage: ChatMessage = {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ export function useFileAttachments(props: UseFileAttachmentsProps) {
126126
type: resolveFileType(file),
127127
path: '',
128128
uploading: true,
129-
previewUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,
129+
previewUrl:
130+
file.type.startsWith('image/') || file.type.startsWith('video/')
131+
? URL.createObjectURL(file)
132+
: undefined,
130133
}))
131134

132135
setAttachedFiles((prev) => [...prev, ...placeholders])
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function getMothershipAttachmentPreviewUrl(file: {
2+
key: string
3+
media_type: string
4+
}): string | undefined {
5+
if (!file.media_type.startsWith('image/') && !file.media_type.startsWith('video/')) {
6+
return undefined
7+
}
8+
return `/api/files/serve/${encodeURIComponent(file.key)}?context=mothership`
9+
}

apps/sim/lib/copilot/chat/display-message.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
type ToolCallInfo,
1616
ToolCallStatus,
1717
} from '@/app/workspace/[workspaceId]/home/types'
18+
import { getMothershipAttachmentPreviewUrl } from './attachment-preview'
1819
import type { PersistedContentBlock, PersistedMessage } from './persisted-message'
1920
import { withBlockTiming } from './persisted-message'
2021

@@ -91,9 +92,7 @@ function toDisplayAttachment(f: PersistedMessage['fileAttachments']): ChatMessag
9192
filename: a.filename,
9293
media_type: a.media_type,
9394
size: a.size,
94-
previewUrl: a.media_type.startsWith('image/')
95-
? `/api/files/serve/${encodeURIComponent(a.key)}?context=mothership`
96-
: undefined,
95+
previewUrl: getMothershipAttachmentPreviewUrl(a),
9796
}))
9897
}
9998

0 commit comments

Comments
 (0)