mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 06:58:07 -05:00
feat(copilot): show inline prompt to increase usage limit or upgrade plan (#2465)
* Add limit v1 * fix ui for copilot upgrade limit inline * open settings modal * Upgrade plan button * Remove comments * Ishosted check * Fix hardcoded bumps --------- Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com> Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
This commit is contained in:
committed by
GitHub
parent
6247f421bc
commit
0ebb45b2db
@@ -2,3 +2,4 @@ export * from './file-display'
|
||||
export { default as CopilotMarkdownRenderer } from './markdown-renderer'
|
||||
export * from './smooth-streaming'
|
||||
export * from './thinking-block'
|
||||
export * from './usage-limit-actions'
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn'
|
||||
import { canEditUsageLimit } from '@/lib/billing/subscriptions/utils'
|
||||
import { isHosted } from '@/lib/core/config/feature-flags'
|
||||
import { useSubscriptionData, useUpdateUsageLimit } from '@/hooks/queries/subscription'
|
||||
import { useCopilotStore } from '@/stores/panel/copilot/store'
|
||||
|
||||
const LIMIT_INCREMENTS = [0, 50, 100] as const
|
||||
|
||||
function roundUpToNearest50(value: number): number {
|
||||
return Math.ceil(value / 50) * 50
|
||||
}
|
||||
|
||||
export function UsageLimitActions() {
|
||||
const { data: subscriptionData } = useSubscriptionData()
|
||||
const updateUsageLimitMutation = useUpdateUsageLimit()
|
||||
|
||||
const subscription = subscriptionData?.data
|
||||
const canEdit = subscription ? canEditUsageLimit(subscription) : false
|
||||
|
||||
const [selectedAmount, setSelectedAmount] = useState<number | null>(null)
|
||||
const [isHidden, setIsHidden] = useState(false)
|
||||
|
||||
const currentLimit = subscription?.usage_limit ?? 0
|
||||
const baseLimit = roundUpToNearest50(currentLimit) || 50
|
||||
const limitOptions = LIMIT_INCREMENTS.map((increment) => baseLimit + increment)
|
||||
|
||||
const handleUpdateLimit = async (newLimit: number) => {
|
||||
setSelectedAmount(newLimit)
|
||||
try {
|
||||
await updateUsageLimitMutation.mutateAsync({ limit: newLimit })
|
||||
|
||||
setIsHidden(true)
|
||||
|
||||
const { messages, sendMessage } = useCopilotStore.getState()
|
||||
const lastUserMessage = [...messages].reverse().find((m) => m.role === 'user')
|
||||
|
||||
if (lastUserMessage) {
|
||||
const filteredMessages = messages.filter(
|
||||
(m) => !(m.role === 'assistant' && m.errorType === 'usage_limit')
|
||||
)
|
||||
useCopilotStore.setState({ messages: filteredMessages })
|
||||
|
||||
await sendMessage(lastUserMessage.content, {
|
||||
fileAttachments: lastUserMessage.fileAttachments,
|
||||
contexts: lastUserMessage.contexts,
|
||||
messageId: lastUserMessage.id,
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
setIsHidden(false)
|
||||
} finally {
|
||||
setSelectedAmount(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNavigateToUpgrade = () => {
|
||||
if (isHosted) {
|
||||
window.dispatchEvent(new CustomEvent('open-settings', { detail: { tab: 'subscription' } }))
|
||||
} else {
|
||||
window.open('https://www.sim.ai', '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
if (isHidden) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isHosted || !canEdit) {
|
||||
return (
|
||||
<Button onClick={handleNavigateToUpgrade} variant='default'>
|
||||
Upgrade
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{limitOptions.map((limit) => {
|
||||
const isLoading = updateUsageLimitMutation.isPending && selectedAmount === limit
|
||||
const isDisabled = updateUsageLimitMutation.isPending
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={limit}
|
||||
onClick={() => handleUpdateLimit(limit)}
|
||||
disabled={isDisabled}
|
||||
variant='default'
|
||||
>
|
||||
{isLoading ? <Loader2 className='mr-1 h-3 w-3 animate-spin' /> : null}${limit}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
SmoothStreamingText,
|
||||
StreamingIndicator,
|
||||
ThinkingBlock,
|
||||
UsageLimitActions,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components'
|
||||
import CopilotMarkdownRenderer from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer'
|
||||
import {
|
||||
@@ -458,6 +459,12 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
|
||||
<StreamingIndicator />
|
||||
)}
|
||||
|
||||
{message.errorType === 'usage_limit' && (
|
||||
<div className='mt-3 flex gap-1.5'>
|
||||
<UsageLimitActions />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action buttons for completed messages */}
|
||||
{!isStreaming && cleanTextContent && (
|
||||
<div className='flex items-center gap-[8px] pt-[8px]'>
|
||||
|
||||
@@ -533,7 +533,11 @@ function createStreamingMessage(): CopilotMessage {
|
||||
}
|
||||
}
|
||||
|
||||
function createErrorMessage(messageId: string, content: string): CopilotMessage {
|
||||
function createErrorMessage(
|
||||
messageId: string,
|
||||
content: string,
|
||||
errorType?: 'usage_limit' | 'unauthorized' | 'forbidden' | 'rate_limit' | 'upgrade_required'
|
||||
): CopilotMessage {
|
||||
return {
|
||||
id: messageId,
|
||||
role: 'assistant',
|
||||
@@ -546,6 +550,7 @@ function createErrorMessage(messageId: string, content: string): CopilotMessage
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
errorType,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2066,23 +2071,35 @@ export const useCopilotStore = create<CopilotStore>()(
|
||||
|
||||
// Check for specific status codes and provide custom messages
|
||||
let errorContent = result.error || 'Failed to send message'
|
||||
let errorType:
|
||||
| 'usage_limit'
|
||||
| 'unauthorized'
|
||||
| 'forbidden'
|
||||
| 'rate_limit'
|
||||
| 'upgrade_required'
|
||||
| undefined
|
||||
if (result.status === 401) {
|
||||
errorContent =
|
||||
'_Unauthorized request. You need a valid API key to use the copilot. You can get one by going to [sim.ai](https://sim.ai) settings and generating one there._'
|
||||
errorType = 'unauthorized'
|
||||
} else if (result.status === 402) {
|
||||
errorContent =
|
||||
'_Usage limit exceeded. To continue using this service, upgrade your plan or top up on credits._'
|
||||
'_Usage limit exceeded. To continue using this service, upgrade your plan or increase your usage limit to:_'
|
||||
errorType = 'usage_limit'
|
||||
} else if (result.status === 403) {
|
||||
errorContent =
|
||||
'_Provider config not allowed for non-enterprise users. Please remove the provider config and try again_'
|
||||
errorType = 'forbidden'
|
||||
} else if (result.status === 426) {
|
||||
errorContent =
|
||||
'_Please upgrade to the latest version of the Sim platform to continue using the copilot._'
|
||||
errorType = 'upgrade_required'
|
||||
} else if (result.status === 429) {
|
||||
errorContent = '_Provider rate limit exceeded. Please try again later._'
|
||||
errorType = 'rate_limit'
|
||||
}
|
||||
|
||||
const errorMessage = createErrorMessage(streamingMessage.id, errorContent)
|
||||
const errorMessage = createErrorMessage(streamingMessage.id, errorContent, errorType)
|
||||
set((state) => ({
|
||||
messages: state.messages.map((m) => (m.id === streamingMessage.id ? errorMessage : m)),
|
||||
error: errorContent,
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface CopilotMessage {
|
||||
>
|
||||
fileAttachments?: MessageFileAttachment[]
|
||||
contexts?: ChatContext[]
|
||||
errorType?: 'usage_limit' | 'unauthorized' | 'forbidden' | 'rate_limit' | 'upgrade_required'
|
||||
}
|
||||
|
||||
// Contexts attached to a user message
|
||||
|
||||
Reference in New Issue
Block a user