Skip to content

Commit 8610d67

Browse files
committed
improvement(mothership): message queueing for home chat
1 parent d06aa1d commit 8610d67

File tree

7 files changed

+401
-43
lines changed

7 files changed

+401
-43
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { MessageContent } from './message-content'
22
export { MothershipView } from './mothership-view'
3+
export { QueuedMessages } from './queued-messages'
34
export { TemplatePrompts } from './template-prompts'
45
export { UserInput } from './user-input'
56
export { UserMessageContent } from './user-message-content'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { QueuedMessages } from './queued-messages'
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { ArrowUp, ChevronDown, ChevronRight, Pencil, Trash2 } from 'lucide-react'
5+
import { Tooltip } from '@/components/emcn'
6+
import type { QueuedMessage } from '@/app/workspace/[workspaceId]/home/types'
7+
8+
interface QueuedMessagesProps {
9+
messageQueue: QueuedMessage[]
10+
onRemove: (id: string) => void
11+
onSendNow: (id: string) => Promise<void>
12+
onEdit: (id: string) => void
13+
}
14+
15+
export function QueuedMessages({ messageQueue, onRemove, onSendNow, onEdit }: QueuedMessagesProps) {
16+
const [isExpanded, setIsExpanded] = useState(true)
17+
18+
if (messageQueue.length === 0) return null
19+
20+
return (
21+
<div className='-mb-[12px] mx-[14px] overflow-hidden rounded-t-[16px] border border-[var(--border-1)] border-b-0 bg-[var(--surface-2)] pb-[12px] dark:bg-[var(--surface-3)]'>
22+
<button
23+
type='button'
24+
onClick={() => setIsExpanded(!isExpanded)}
25+
className='flex w-full items-center gap-[6px] px-[14px] py-[8px] transition-colors hover:bg-black/[0.03] dark:hover:bg-white/[0.03]'
26+
>
27+
{isExpanded ? (
28+
<ChevronDown className='h-[14px] w-[14px] text-[var(--text-tertiary)]' />
29+
) : (
30+
<ChevronRight className='h-[14px] w-[14px] text-[var(--text-tertiary)]' />
31+
)}
32+
<span className='font-medium text-[13px] text-[var(--text-secondary)]'>
33+
{messageQueue.length} Queued
34+
</span>
35+
</button>
36+
37+
{isExpanded && (
38+
<div>
39+
{messageQueue.map((msg) => (
40+
<div
41+
key={msg.id}
42+
className='flex items-center gap-[8px] px-[14px] py-[6px] transition-colors hover:bg-black/[0.03] dark:hover:bg-white/[0.03]'
43+
>
44+
<div className='flex h-[16px] w-[16px] shrink-0 items-center justify-center'>
45+
<div className='h-[10px] w-[10px] rounded-full border-[1.5px] border-[var(--text-tertiary)]/40' />
46+
</div>
47+
48+
<div className='min-w-0 flex-1'>
49+
<p className='truncate text-[13px] text-[var(--text-primary)]'>{msg.content}</p>
50+
</div>
51+
52+
<div className='flex shrink-0 items-center gap-[2px]'>
53+
<Tooltip.Root>
54+
<Tooltip.Trigger asChild>
55+
<button
56+
type='button'
57+
onClick={(e) => {
58+
e.stopPropagation()
59+
onEdit(msg.id)
60+
}}
61+
className='rounded-[6px] p-[5px] text-[var(--text-tertiary)] transition-colors hover:bg-black/[0.06] hover:text-[var(--text-primary)] dark:hover:bg-white/[0.06]'
62+
>
63+
<Pencil className='h-[13px] w-[13px]' />
64+
</button>
65+
</Tooltip.Trigger>
66+
<Tooltip.Content side='top' sideOffset={4}>
67+
Edit queued message
68+
</Tooltip.Content>
69+
</Tooltip.Root>
70+
71+
<Tooltip.Root>
72+
<Tooltip.Trigger asChild>
73+
<button
74+
type='button'
75+
onClick={(e) => {
76+
e.stopPropagation()
77+
onSendNow(msg.id)
78+
}}
79+
className='rounded-[6px] p-[5px] text-[var(--text-tertiary)] transition-colors hover:bg-black/[0.06] hover:text-[var(--text-primary)] dark:hover:bg-white/[0.06]'
80+
>
81+
<ArrowUp className='h-[13px] w-[13px]' />
82+
</button>
83+
</Tooltip.Trigger>
84+
<Tooltip.Content side='top' sideOffset={4}>
85+
Send now
86+
</Tooltip.Content>
87+
</Tooltip.Root>
88+
89+
<Tooltip.Root>
90+
<Tooltip.Trigger asChild>
91+
<button
92+
type='button'
93+
onClick={(e) => {
94+
e.stopPropagation()
95+
onRemove(msg.id)
96+
}}
97+
className='rounded-[6px] p-[5px] text-[var(--text-tertiary)] transition-colors hover:bg-black/[0.06] hover:text-[var(--text-primary)] dark:hover:bg-white/[0.06]'
98+
>
99+
<Trash2 className='h-[13px] w-[13px]' />
100+
</button>
101+
</Tooltip.Trigger>
102+
<Tooltip.Content side='top' sideOffset={4}>
103+
Remove from queue
104+
</Tooltip.Content>
105+
</Tooltip.Root>
106+
</div>
107+
</div>
108+
))}
109+
</div>
110+
)}
111+
</div>
112+
)
113+
}

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

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,17 @@ function autoResizeTextarea(e: React.FormEvent<HTMLTextAreaElement>, maxHeight:
125125
function mapResourceToContext(resource: MothershipResource): ChatContext {
126126
switch (resource.type) {
127127
case 'workflow':
128-
return { kind: 'workflow', workflowId: resource.id, label: resource.title }
128+
return {
129+
kind: 'workflow',
130+
workflowId: resource.id,
131+
label: resource.title,
132+
}
129133
case 'knowledgebase':
130-
return { kind: 'knowledge', knowledgeId: resource.id, label: resource.title }
134+
return {
135+
kind: 'knowledge',
136+
knowledgeId: resource.id,
137+
label: resource.title,
138+
}
131139
case 'table':
132140
return { kind: 'table', tableId: resource.id, label: resource.title }
133141
case 'file':
@@ -147,6 +155,8 @@ export interface FileAttachmentForApi {
147155

148156
interface UserInputProps {
149157
defaultValue?: string
158+
editValue?: string
159+
onEditValueConsumed?: () => void
150160
onSubmit: (
151161
text: string,
152162
fileAttachments?: FileAttachmentForApi[],
@@ -161,6 +171,8 @@ interface UserInputProps {
161171

162172
export function UserInput({
163173
defaultValue = '',
174+
editValue,
175+
onEditValueConsumed,
164176
onSubmit,
165177
isSending,
166178
onStopGeneration,
@@ -176,9 +188,22 @@ export function UserInput({
176188
const [plusMenuActiveIndex, setPlusMenuActiveIndex] = useState(0)
177189
const overlayRef = useRef<HTMLDivElement>(null)
178190

179-
useEffect(() => {
180-
if (defaultValue) setValue(defaultValue)
181-
}, [defaultValue])
191+
const [prevDefaultValue, setPrevDefaultValue] = useState(defaultValue)
192+
if (defaultValue && defaultValue !== prevDefaultValue) {
193+
setPrevDefaultValue(defaultValue)
194+
setValue(defaultValue)
195+
} else if (!defaultValue && prevDefaultValue) {
196+
setPrevDefaultValue(defaultValue)
197+
}
198+
199+
const [prevEditValue, setPrevEditValue] = useState(editValue)
200+
if (editValue && editValue !== prevEditValue) {
201+
setPrevEditValue(editValue)
202+
setValue(editValue)
203+
onEditValueConsumed?.()
204+
} else if (!editValue && prevEditValue) {
205+
setPrevEditValue(editValue)
206+
}
182207

183208
const animatedPlaceholder = useAnimatedPlaceholder(isInitialView)
184209
const placeholder = isInitialView ? animatedPlaceholder : 'Send message to Sim'
@@ -393,9 +418,7 @@ export function UserInput({
393418
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
394419
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
395420
e.preventDefault()
396-
if (!isSending) {
397-
handleSubmit()
398-
}
421+
handleSubmit()
399422
return
400423
}
401424

@@ -637,7 +660,9 @@ export function UserInput({
637660
<span
638661
key={`mention-${i}-${range.start}-${range.end}`}
639662
className='rounded-[5px] bg-[var(--surface-5)] py-[2px]'
640-
style={{ boxShadow: '-2px 0 0 var(--surface-5), 2px 0 0 var(--surface-5)' }}
663+
style={{
664+
boxShadow: '-2px 0 0 var(--surface-5), 2px 0 0 var(--surface-5)',
665+
}}
641666
>
642667
<span className='relative'>
643668
<span className='invisible'>{range.token.charAt(0)}</span>
@@ -662,7 +687,7 @@ export function UserInput({
662687
<div
663688
onClick={handleContainerClick}
664689
className={cn(
665-
'relative mx-auto w-full max-w-[42rem] cursor-text rounded-[20px] border border-[var(--border-1)] bg-[var(--white)] px-[10px] py-[8px] dark:bg-[var(--surface-4)]',
690+
'relative z-10 mx-auto w-full max-w-[42rem] cursor-text rounded-[20px] border border-[var(--border-1)] bg-[var(--white)] px-[10px] py-[8px] dark:bg-[var(--surface-4)]',
666691
isInitialView && 'shadow-sm'
667692
)}
668693
onDragEnter={files.handleDragEnter}
@@ -818,7 +843,11 @@ export function UserInput({
818843
)}
819844
onMouseEnter={() => setPlusMenuActiveIndex(index)}
820845
onClick={() => {
821-
handleResourceSelect({ type, id: item.id, title: item.name })
846+
handleResourceSelect({
847+
type,
848+
id: item.id,
849+
title: item.name,
850+
})
822851
setPlusMenuOpen(false)
823852
setPlusMenuSearch('')
824853
setPlusMenuActiveIndex(0)

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { ChatContext } from '@/stores/panel'
1919
import {
2020
MessageContent,
2121
MothershipView,
22+
QueuedMessages,
2223
TemplatePrompts,
2324
UserInput,
2425
UserMessageContent,
@@ -183,8 +184,25 @@ export function Home({ chatId }: HomeProps = {}) {
183184
addResource,
184185
removeResource,
185186
reorderResources,
187+
messageQueue,
188+
removeFromQueue,
189+
sendNow,
190+
editQueuedMessage,
186191
} = useChat(workspaceId, chatId, { onResourceEvent: handleResourceEvent })
187192

193+
const [editingInputValue, setEditingInputValue] = useState('')
194+
const clearEditingValue = useCallback(() => setEditingInputValue(''), [])
195+
196+
const handleEditQueuedMessage = useCallback(
197+
(id: string) => {
198+
const msg = editQueuedMessage(id)
199+
if (msg) {
200+
setEditingInputValue(msg.content)
201+
}
202+
},
203+
[editQueuedMessage]
204+
)
205+
188206
useEffect(() => {
189207
wasSendingRef.current = false
190208
if (resolvedChatId) markRead(resolvedChatId)
@@ -419,13 +437,21 @@ export function Home({ chatId }: HomeProps = {}) {
419437

420438
<div className='flex-shrink-0 px-[24px] pb-[16px]'>
421439
<div className='mx-auto max-w-[42rem]'>
440+
<QueuedMessages
441+
messageQueue={messageQueue}
442+
onRemove={removeFromQueue}
443+
onSendNow={sendNow}
444+
onEdit={handleEditQueuedMessage}
445+
/>
422446
<UserInput
423447
onSubmit={handleSubmit}
424448
isSending={isSending}
425449
onStopGeneration={stopGeneration}
426450
isInitialView={false}
427451
userId={session?.user?.id}
428452
onContextAdd={handleContextAdd}
453+
editValue={editingInputValue}
454+
onEditValueConsumed={clearEditingValue}
429455
/>
430456
</div>
431457
</div>

0 commit comments

Comments
 (0)