Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Include rid
  • Loading branch information
Sg312 committed Mar 17, 2026
commit c2ac24f945f919479535c96c515de663cccc23c4
1 change: 1 addition & 0 deletions apps/sim/app/api/mothership/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ export async function POST(req: NextRequest) {
role: 'assistant' as const,
content: result.content,
timestamp: new Date().toISOString(),
...(result.requestId ? { requestId: result.requestId } : {}),
}
if (result.toolCalls.length > 0) {
assistantMessage.toolCalls = result.toolCalls
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { MessageActions } from './message-actions'
export { MessageContent } from './message-content'
export { MothershipView } from './mothership-view'
export { QueuedMessages } from './queued-messages'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MessageActions } from './message-actions'
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
'use client'

import { useCallback, useState } from 'react'
import { Check, Copy, Ellipsis, Hash } from 'lucide-react'
import {
Popover,
PopoverContent,
PopoverItem,
PopoverScrollArea,
PopoverTrigger,
} from '@/components/emcn'

interface MessageActionsProps {
content: string
requestId?: string
}

export function MessageActions({ content, requestId }: MessageActionsProps) {
const [open, setOpen] = useState(false)
const [copied, setCopied] = useState<'message' | 'request' | null>(null)

const copyToClipboard = useCallback(async (text: string, type: 'message' | 'request') => {
try {
await navigator.clipboard.writeText(text)
setCopied(type)
setTimeout(() => setCopied(null), 1500)
} catch {
// Silently fail
}
setOpen(false)
}, [])

return (
<Popover variant='default' size='sm' open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type='button'
className='rounded-md p-1 text-[var(--text-icon)] opacity-0 transition-opacity hover:bg-[var(--surface-3)] group-hover/msg:opacity-100 data-[state=open]:opacity-100'
onClick={(e) => e.stopPropagation()}
>
<Ellipsis className='h-[14px] w-[14px]' strokeWidth={2} />
</button>
</PopoverTrigger>
<PopoverContent
side='bottom'
align='end'
sideOffset={4}
maxHeight={120}
style={{ width: '160px', minWidth: '160px' }}
>
<PopoverScrollArea>
<PopoverItem onClick={() => copyToClipboard(content, 'message')} disabled={!content}>
{copied === 'message' ? (
<Check className='h-[13px] w-[13px]' />
) : (
<Copy className='h-[13px] w-[13px]' />
)}
<span>Copy Message</span>
</PopoverItem>
<PopoverItem
onClick={() => requestId && copyToClipboard(requestId, 'request')}
disabled={!requestId}
>
{copied === 'request' ? (
<Check className='h-[13px] w-[13px]' />
) : (
<Hash className='h-[13px] w-[13px]' />
)}
<span>Copy Request ID</span>
</PopoverItem>
</PopoverScrollArea>
</PopoverContent>
</Popover>
)
}
8 changes: 7 additions & 1 deletion apps/sim/app/workspace/[workspaceId]/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useChatHistory, useMarkTaskRead } from '@/hooks/queries/tasks'
import type { ChatContext } from '@/stores/panel'
import { useSidebarStore } from '@/stores/sidebar/store'
import {
MessageActions,
MessageContent,
MothershipView,
QueuedMessages,
Expand Down Expand Up @@ -414,7 +415,12 @@ export function Home({ chatId }: HomeProps = {}) {
const isLastMessage = index === messages.length - 1

return (
<div key={msg.id} className='pb-4'>
<div key={msg.id} className='group/msg relative pb-4'>
{!isThisStreaming && (msg.content || msg.contentBlocks?.length) && (
<div className='-top-1 absolute right-0 z-10'>
<MessageActions content={msg.content} requestId={msg.requestId} />
</div>
)}
<MessageContent
blocks={msg.contentBlocks || []}
fallbackContent={msg.content}
Expand Down
20 changes: 18 additions & 2 deletions apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ export function useChat(
let activeSubagent: string | undefined
let runningText = ''
let lastContentSource: 'main' | 'subagent' | null = null
let streamRequestId: string | undefined

streamingContentRef.current = ''
streamingBlocksRef.current = []
Expand All @@ -526,14 +527,21 @@ export function useChat(
const flush = () => {
if (isStale()) return
streamingBlocksRef.current = [...blocks]
const snapshot = { content: runningText, contentBlocks: [...blocks] }
const snapshot: Partial<ChatMessage> = {
content: runningText,
contentBlocks: [...blocks],
}
if (streamRequestId) snapshot.requestId = streamRequestId
setMessages((prev) => {
if (expectedGen !== undefined && streamGenRef.current !== expectedGen) return prev
const idx = prev.findIndex((m) => m.id === assistantId)
if (idx >= 0) {
return prev.map((m) => (m.id === assistantId ? { ...m, ...snapshot } : m))
}
return [...prev, { id: assistantId, role: 'assistant' as const, ...snapshot }]
return [
...prev,
{ id: assistantId, role: 'assistant' as const, content: '', ...snapshot },
]
})
}

Expand Down Expand Up @@ -597,6 +605,14 @@ export function useChat(
}
break
}
case 'request_id': {
const rid = typeof parsed.data === 'string' ? parsed.data : undefined
if (rid) {
streamRequestId = rid
flush()
}
break
}
case 'content': {
const chunk = typeof parsed.data === 'string' ? parsed.data : (parsed.content ?? '')
if (chunk) {
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/home/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface QueuedMessage {
*/
export type SSEEventType =
| 'chat_id'
| 'request_id'
| 'title_updated'
| 'content'
| 'reasoning' // openai reasoning - render as thinking text
Expand Down Expand Up @@ -199,6 +200,7 @@ export interface ChatMessage {
contentBlocks?: ContentBlock[]
attachments?: ChatMessageAttachment[]
contexts?: ChatMessageContext[]
requestId?: string
}

export const SUBAGENT_LABELS: Record<SubagentName, string> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { type FC, memo, useCallback, useMemo, useRef, useState } from 'react'
import { RotateCcw } from 'lucide-react'
import { Button } from '@/components/emcn'
import { MessageActions } from '@/app/workspace/[workspaceId]/home/components/message-actions'
Comment thread
icecrasher321 marked this conversation as resolved.
Outdated
import {
OptionsSelector,
parseSpecialTags,
Expand Down Expand Up @@ -409,9 +410,14 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
if (isAssistant) {
return (
<div
className={`w-full max-w-full flex-none overflow-hidden [max-width:var(--panel-max-width)] ${isDimmed ? 'opacity-40' : 'opacity-100'}`}
className={`group/msg relative w-full max-w-full flex-none overflow-hidden [max-width:var(--panel-max-width)] ${isDimmed ? 'opacity-40' : 'opacity-100'}`}
style={{ '--panel-max-width': `${panelWidth - 16}px` } as React.CSSProperties}
>
{!isStreaming && message.content && (
Comment thread
icecrasher321 marked this conversation as resolved.
Outdated
<div className='-top-1 absolute right-0 z-10'>
<MessageActions content={message.content} requestId={message.requestId} />
</div>
Comment thread
icecrasher321 marked this conversation as resolved.
)}
<div className='max-w-full space-y-[4px] px-[2px] pb-[4px]'>
{/* Content blocks in chronological order */}
{memoizedContentBlocks || (isStreaming && <div className='min-h-0' />)}
Expand Down
1 change: 1 addition & 0 deletions apps/sim/lib/copilot/orchestrator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export async function orchestrateCopilotStream(
contentBlocks: context.contentBlocks,
toolCalls: buildToolCallSummaries(context),
chatId: context.chatId,
requestId: context.requestId,
errors: context.errors.length ? context.errors : undefined,
usage: context.usage,
cost: context.cost,
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/lib/copilot/orchestrator/sse/handlers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ export const sseHandlers: Record<string, SSEHandler> = {
execContext.chatId = chatId
}
},
request_id: (event, context) => {
const rid = typeof event.data === 'string' ? event.data : undefined
if (rid) {
context.requestId = rid
}
},
title_updated: () => {},
tool_result: (event, context) => {
const data = getEventData(event)
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/lib/copilot/orchestrator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export interface ContentBlock {

export interface StreamingContext {
chatId?: string
requestId?: string
Comment thread
icecrasher321 marked this conversation as resolved.
messageId: string
accumulatedContent: string
contentBlocks: ContentBlock[]
Expand Down Expand Up @@ -154,6 +155,7 @@ export interface OrchestratorResult {
contentBlocks: ContentBlock[]
toolCalls: ToolCallSummary[]
chatId?: string
requestId?: string
error?: string
errors?: string[]
usage?: { prompt: number; completion: number }
Expand Down
1 change: 1 addition & 0 deletions apps/sim/stores/panel/copilot/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface CopilotMessage {
role: 'user' | 'assistant' | 'system'
content: string
timestamp: string
requestId?: string
citations?: { id: number; title: string; url: string; similarity?: number }[]
toolCalls?: CopilotToolCall[]
contentBlocks?: ClientContentBlock[]
Expand Down