Skip to content

Commit c96731e

Browse files
committed
Copilot context window
1 parent 4937d72 commit c96731e

File tree

5 files changed

+88
-4
lines changed

5 files changed

+88
-4
lines changed

apps/sim/app/api/copilot/chat/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,8 @@ export async function POST(req: NextRequest) {
463463
logger.debug(`[${tracker.requestId}] Sent initial chatId event to client`)
464464
}
465465

466+
// Note: context_usage events are forwarded from sim-agent (which has accurate token counts)
467+
466468
// Start title generation in parallel if needed
467469
if (actualChatId && !currentChat?.title && conversationHistory.length === 0) {
468470
generateChatTitle(message)
@@ -594,6 +596,7 @@ export async function POST(req: NextRequest) {
594596
lastSafeDoneResponseId = responseIdFromDone
595597
}
596598
}
599+
// Note: context_usage events are forwarded from sim-agent
597600
break
598601

599602
case 'error':
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use client'
2+
3+
import { memo } from 'react'
4+
import { cn } from '@/lib/utils'
5+
6+
interface ContextUsagePillProps {
7+
percentage: number
8+
className?: string
9+
}
10+
11+
export const ContextUsagePill = memo(({ percentage, className }: ContextUsagePillProps) => {
12+
// Don't render if invalid (but DO render if 0 or very small)
13+
if (percentage === null || percentage === undefined || Number.isNaN(percentage)) return null
14+
15+
// Determine color based on percentage (similar to Cursor IDE)
16+
const getColorClass = () => {
17+
if (percentage >= 90) return 'bg-red-500/10 text-red-600 dark:text-red-400'
18+
if (percentage >= 75) return 'bg-orange-500/10 text-orange-600 dark:text-orange-400'
19+
if (percentage >= 50) return 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400'
20+
return 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
21+
}
22+
23+
// Format: show 1 decimal for <1%, 0 decimals for >=1%
24+
const formattedPercentage = percentage < 1 ? percentage.toFixed(1) : percentage.toFixed(0)
25+
26+
return (
27+
<div
28+
className={cn(
29+
'inline-flex items-center justify-center rounded-full px-2 py-0.5 font-medium text-[11px] tabular-nums transition-colors',
30+
getColorClass(),
31+
className
32+
)}
33+
title={`Context: ${percentage.toFixed(2)}%`}
34+
>
35+
{formattedPercentage}%
36+
</div>
37+
)
38+
})
39+
40+
ContextUsagePill.displayName = 'ContextUsagePill'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/user-input.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { cn } from '@/lib/utils'
5555
import { useCopilotStore } from '@/stores/copilot/store'
5656
import type { ChatContext } from '@/stores/copilot/types'
5757
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
58+
import { ContextUsagePill } from '../context-usage-pill/context-usage-pill'
5859

5960
const logger = createLogger('CopilotUserInput')
6061

@@ -182,7 +183,8 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
182183
const [isLoadingLogs, setIsLoadingLogs] = useState(false)
183184

184185
const { data: session } = useSession()
185-
const { currentChat, workflowId, enabledModels, setEnabledModels } = useCopilotStore()
186+
const { currentChat, workflowId, enabledModels, setEnabledModels, contextUsage } =
187+
useCopilotStore()
186188
const params = useParams()
187189
const workspaceId = params.workspaceId as string
188190
// Track per-chat preference for auto-adding workflow context
@@ -2050,7 +2052,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
20502052
<div className={cn('relative flex-none pb-4', className)}>
20512053
<div
20522054
className={cn(
2053-
'rounded-[8px] border border-[#E5E5E5] bg-[#FFFFFF] p-2 shadow-xs transition-all duration-200 dark:border-[#414141] dark:bg-[var(--surface-elevated)]',
2055+
'relative rounded-[8px] border border-[#E5E5E5] bg-[#FFFFFF] p-2 shadow-xs transition-all duration-200 dark:border-[#414141] dark:bg-[var(--surface-elevated)]',
20542056
isDragging &&
20552057
'border-[var(--brand-primary-hover-hex)] bg-purple-50/50 dark:border-[var(--brand-primary-hover-hex)] dark:bg-purple-950/20'
20562058
)}
@@ -2059,6 +2061,12 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
20592061
onDragOver={handleDragOver}
20602062
onDrop={handleDrop}
20612063
>
2064+
{/* Context Usage Pill - Top Right */}
2065+
{contextUsage && contextUsage.percentage > 0 && (
2066+
<div className='absolute top-2 right-2 z-10'>
2067+
<ContextUsagePill percentage={contextUsage.percentage} />
2068+
</div>
2069+
)}
20622070
{/* Attached Files Display with Thumbnails */}
20632071
{attachedFiles.length > 0 && (
20642072
<div className='mb-2 flex flex-wrap gap-1.5'>
@@ -3364,7 +3372,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
33643372
</div>
33653373

33663374
{/* Right side: Attach Button + Send Button */}
3367-
<div className='flex items-center gap-1'>
3375+
<div className='flex items-center gap-1.5'>
33683376
{/* Attach Button */}
33693377
<Button
33703378
variant='ghost'

apps/sim/stores/copilot/store.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,25 @@ const sseHandlers: Record<string, SSEHandler> = {
11661166
context.currentTextBlock = null
11671167
updateStreamingMessage(set, context)
11681168
},
1169+
context_usage: (data, _context, _get, set) => {
1170+
try {
1171+
const usageData = data?.data
1172+
if (usageData) {
1173+
set({
1174+
contextUsage: {
1175+
usage: usageData.usage || 0,
1176+
percentage: usageData.percentage || 0,
1177+
model: usageData.model || '',
1178+
contextWindow: usageData.context_window || usageData.contextWindow || 0,
1179+
when: usageData.when || 'start',
1180+
estimatedTokens: usageData.estimated_tokens || usageData.estimatedTokens,
1181+
},
1182+
})
1183+
}
1184+
} catch (err) {
1185+
logger.warn('Failed to handle context_usage event:', err)
1186+
}
1187+
},
11691188
default: () => {},
11701189
}
11711190

@@ -1304,6 +1323,7 @@ const initialState = {
13041323
showPlanTodos: false,
13051324
toolCallsById: {} as Record<string, CopilotToolCall>,
13061325
suppressAutoSelect: false,
1326+
contextUsage: null,
13071327
}
13081328

13091329
export const useCopilotStore = create<CopilotStore>()(
@@ -1314,7 +1334,7 @@ export const useCopilotStore = create<CopilotStore>()(
13141334
setMode: (mode) => set({ mode }),
13151335

13161336
// Clear messages
1317-
clearMessages: () => set({ messages: [] }),
1337+
clearMessages: () => set({ messages: [], contextUsage: null }),
13181338

13191339
// Workflow selection
13201340
setWorkflowId: async (workflowId: string | null) => {
@@ -1374,6 +1394,7 @@ export const useCopilotStore = create<CopilotStore>()(
13741394
planTodos: [],
13751395
showPlanTodos: false,
13761396
suppressAutoSelect: false,
1397+
contextUsage: null,
13771398
})
13781399

13791400
// Background-save the previous chat's latest messages before switching (optimistic)
@@ -1442,6 +1463,7 @@ export const useCopilotStore = create<CopilotStore>()(
14421463
planTodos: [],
14431464
showPlanTodos: false,
14441465
suppressAutoSelect: true,
1466+
contextUsage: null,
14451467
})
14461468
},
14471469

@@ -2041,6 +2063,7 @@ export const useCopilotStore = create<CopilotStore>()(
20412063
for await (const data of parseSSEStream(reader, decoder)) {
20422064
const { abortController } = get()
20432065
if (abortController?.signal.aborted) break
2066+
20442067
const handler = sseHandlers[data.type] || sseHandlers.default
20452068
await handler(data, context, get, set)
20462069
if (context.streamComplete) break

apps/sim/stores/copilot/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ export interface CopilotState {
124124
currentUserMessageId?: string | null
125125

126126
// Per-message metadata captured at send-time for reliable stats
127+
128+
// Context usage tracking for percentage pill
129+
contextUsage: {
130+
usage: number
131+
percentage: number
132+
model: string
133+
contextWindow: number
134+
when: 'start' | 'end'
135+
estimatedTokens?: number
136+
} | null
127137
}
128138

129139
export interface CopilotActions {

0 commit comments

Comments
 (0)