mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -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 { default as CopilotMarkdownRenderer } from './markdown-renderer'
|
||||||
export * from './smooth-streaming'
|
export * from './smooth-streaming'
|
||||||
export * from './thinking-block'
|
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,
|
SmoothStreamingText,
|
||||||
StreamingIndicator,
|
StreamingIndicator,
|
||||||
ThinkingBlock,
|
ThinkingBlock,
|
||||||
|
UsageLimitActions,
|
||||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components'
|
} 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 CopilotMarkdownRenderer from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer'
|
||||||
import {
|
import {
|
||||||
@@ -458,6 +459,12 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
|
|||||||
<StreamingIndicator />
|
<StreamingIndicator />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{message.errorType === 'usage_limit' && (
|
||||||
|
<div className='mt-3 flex gap-1.5'>
|
||||||
|
<UsageLimitActions />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Action buttons for completed messages */}
|
{/* Action buttons for completed messages */}
|
||||||
{!isStreaming && cleanTextContent && (
|
{!isStreaming && cleanTextContent && (
|
||||||
<div className='flex items-center gap-[8px] pt-[8px]'>
|
<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 {
|
return {
|
||||||
id: messageId,
|
id: messageId,
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
@@ -546,6 +550,7 @@ function createErrorMessage(messageId: string, content: string): CopilotMessage
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
errorType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2066,23 +2071,35 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
|
|
||||||
// Check for specific status codes and provide custom messages
|
// Check for specific status codes and provide custom messages
|
||||||
let errorContent = result.error || 'Failed to send message'
|
let errorContent = result.error || 'Failed to send message'
|
||||||
|
let errorType:
|
||||||
|
| 'usage_limit'
|
||||||
|
| 'unauthorized'
|
||||||
|
| 'forbidden'
|
||||||
|
| 'rate_limit'
|
||||||
|
| 'upgrade_required'
|
||||||
|
| undefined
|
||||||
if (result.status === 401) {
|
if (result.status === 401) {
|
||||||
errorContent =
|
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._'
|
'_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) {
|
} else if (result.status === 402) {
|
||||||
errorContent =
|
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) {
|
} else if (result.status === 403) {
|
||||||
errorContent =
|
errorContent =
|
||||||
'_Provider config not allowed for non-enterprise users. Please remove the provider config and try again_'
|
'_Provider config not allowed for non-enterprise users. Please remove the provider config and try again_'
|
||||||
|
errorType = 'forbidden'
|
||||||
} else if (result.status === 426) {
|
} else if (result.status === 426) {
|
||||||
errorContent =
|
errorContent =
|
||||||
'_Please upgrade to the latest version of the Sim platform to continue using the copilot._'
|
'_Please upgrade to the latest version of the Sim platform to continue using the copilot._'
|
||||||
|
errorType = 'upgrade_required'
|
||||||
} else if (result.status === 429) {
|
} else if (result.status === 429) {
|
||||||
errorContent = '_Provider rate limit exceeded. Please try again later._'
|
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) => ({
|
set((state) => ({
|
||||||
messages: state.messages.map((m) => (m.id === streamingMessage.id ? errorMessage : m)),
|
messages: state.messages.map((m) => (m.id === streamingMessage.id ? errorMessage : m)),
|
||||||
error: errorContent,
|
error: errorContent,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export interface CopilotMessage {
|
|||||||
>
|
>
|
||||||
fileAttachments?: MessageFileAttachment[]
|
fileAttachments?: MessageFileAttachment[]
|
||||||
contexts?: ChatContext[]
|
contexts?: ChatContext[]
|
||||||
|
errorType?: 'usage_limit' | 'unauthorized' | 'forbidden' | 'rate_limit' | 'upgrade_required'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contexts attached to a user message
|
// Contexts attached to a user message
|
||||||
|
|||||||
Reference in New Issue
Block a user