mirror of https://github.com/getcursor/cursor
Merge branch 'pricingBrowser' of https://github.com/getcursor/cursor into pricingBrowser
commit
9d5b1488f9
|
@ -4,8 +4,8 @@ import {getShowErrors, getError} from '../features/selectors';
|
|||
import { faClose } from '@fortawesome/pro-regular-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import Modal from 'react-modal'
|
||||
import { NoAuthRateLimitError, NotLoggedInError, OpenAIError } from '../utils';
|
||||
import { CursorLogin, OpenAIPanel } from './settingsPane';
|
||||
import { NoAuthGlobalOldRateLimitError, NotLoggedInError, OpenAIError } from '../utils';
|
||||
import { CursorLogin, OpenAILoginPanel } from './settingsPane';
|
||||
|
||||
const customStyles = {
|
||||
overlay: {
|
||||
|
@ -24,10 +24,33 @@ const customStyles = {
|
|||
height: 'auto',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
maxWidth: '700px',
|
||||
maxWidth: '600px',
|
||||
},
|
||||
}
|
||||
|
||||
const loginStyles = {
|
||||
overlay: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
zIndex: 10000,
|
||||
},
|
||||
content: {
|
||||
padding: 'none',
|
||||
top: '150px',
|
||||
bottom: 'none',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
maxWidth: '450px',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function ErrorPopup() {
|
||||
const showError = useAppSelector(getShowErrors)
|
||||
const error = useAppSelector(getError)
|
||||
|
@ -69,12 +92,11 @@ export function ErrorPopup() {
|
|||
onRequestClose={() => {
|
||||
dispatch(closeError())
|
||||
}}
|
||||
style={customStyles}
|
||||
style={loginStyles}
|
||||
>
|
||||
<div className="errorPopup">
|
||||
<div className="errorPopup__title">
|
||||
<div className="errorPopup__title_text">
|
||||
{error.title}
|
||||
</div>
|
||||
<div
|
||||
className="errorPopup__title_close"
|
||||
|
@ -83,22 +105,67 @@ export function ErrorPopup() {
|
|||
<FontAwesomeIcon icon={faClose} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="errorPopup__body">
|
||||
{error.message}
|
||||
<br />
|
||||
Try logging in here:
|
||||
<CursorLogin showSettings={false}/>
|
||||
<br/>
|
||||
Or try using an OpenAI key:
|
||||
<OpenAIPanel />
|
||||
<div className="signup__body">
|
||||
<div className="signup__title">Cursor</div>
|
||||
<div className="signup__module">
|
||||
<div className="signup__subtitle">To avoid abuse on our backend, we ask that you login in to use the AI features</div>
|
||||
<div className="signup__signup_button">Log in</div>
|
||||
<div className="signup__signup_button">Sign up</div>
|
||||
</div>
|
||||
<div className="signup__module signup__last_module">
|
||||
<div className="signup__subtitle">Or enter your OpenAI API key</div>
|
||||
<OpenAILoginPanel onSubmit={
|
||||
() => {
|
||||
dispatch(closeError())
|
||||
}
|
||||
} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
} else if (error instanceof NoAuthGlobalOldRateLimitError) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true || showError}
|
||||
onRequestClose={() => {
|
||||
dispatch(closeError())
|
||||
}}
|
||||
style={loginStyles}
|
||||
>
|
||||
<div className="errorPopup">
|
||||
<div className="errorPopup__title">
|
||||
<div className="errorPopup__title_text">
|
||||
</div>
|
||||
<div
|
||||
className="errorPopup__title_close"
|
||||
onClick={() => dispatch(closeError())}
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="signup__body">
|
||||
<div className="signup__title">Free tier limit exceeded</div>
|
||||
<div className="signup__module">
|
||||
<div className="signup__subtitle">If you've enjoyed using Cursor, please consider subscribing to one of our paid plans</div>
|
||||
<div className="signup__signup_button">Upgrade</div>
|
||||
</div>
|
||||
<div className="signup__module signup__last_module">
|
||||
<div className="signup__subtitle">Or enter your OpenAI API key to continue using the AI features at-cost</div>
|
||||
<OpenAILoginPanel onSubmit={
|
||||
() => {
|
||||
dispatch(closeError())
|
||||
}
|
||||
} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showError}
|
||||
isOpen={true || showError}
|
||||
onRequestClose={() => {
|
||||
dispatch(closeError())
|
||||
}}
|
||||
|
@ -107,7 +174,7 @@ export function ErrorPopup() {
|
|||
<div className="errorPopup">
|
||||
<div className="errorPopup__title">
|
||||
<div className="errorPopup__title_text">
|
||||
{error.title}
|
||||
{/* {error.title} */}
|
||||
</div>
|
||||
<div
|
||||
className="errorPopup__title_close"
|
||||
|
@ -117,7 +184,7 @@ export function ErrorPopup() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="errorPopup__body">
|
||||
{error.message}
|
||||
{/* {error.message} */}
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -182,6 +182,117 @@ export function SettingsPopup() {
|
|||
)
|
||||
}
|
||||
|
||||
export function OpenAILoginPanel({onSubmit}: {onSubmit: () => void}) {
|
||||
const settings = useAppSelector(ssel.getSettings)
|
||||
const [localAPIKey, setLocalAPIKey] = useState('')
|
||||
const [models, setAvailableModels] = useState<string[]>([])
|
||||
const [keyError, showKeyError] = useState(false)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
// When the global openai key changes, we change this one
|
||||
useEffect(() => {
|
||||
if (settings.openAIKey && settings.openAIKey != localAPIKey) {
|
||||
setLocalAPIKey(settings.openAIKey)
|
||||
ssel.getModels(settings.openAIKey).then(({models, isValidKey}) => {
|
||||
if (models) {
|
||||
setAvailableModels(models)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [settings.openAIKey])
|
||||
|
||||
useEffect(() => {
|
||||
showKeyError(false)
|
||||
}, [localAPIKey]);
|
||||
|
||||
const handleNewAPIKey = useCallback(async () => {
|
||||
const {models, isValidKey} = await ssel.getModels(localAPIKey)
|
||||
console.log({models, isValidKey})
|
||||
if (!isValidKey) {
|
||||
// Error, and we let them know
|
||||
showKeyError(true)
|
||||
setAvailableModels([])
|
||||
} else {
|
||||
setAvailableModels(models)
|
||||
dispatch(
|
||||
changeSettings({
|
||||
openAIKey: localAPIKey,
|
||||
})
|
||||
)
|
||||
onSubmit()
|
||||
}
|
||||
}, [localAPIKey])
|
||||
|
||||
return (
|
||||
<div className="settings__item">
|
||||
|
||||
<div className="flex">
|
||||
<input
|
||||
className={`settings__item_textarea
|
||||
${keyError ? 'input-error' : ''}`}
|
||||
placeholder="Enter your OpenAI API Key"
|
||||
onChange={(e) => {
|
||||
setLocalAPIKey(e.target.value)
|
||||
}}
|
||||
value={localAPIKey || ""}
|
||||
spellCheck="false"
|
||||
/>
|
||||
<button className='settings__button'
|
||||
onClick={() => {
|
||||
handleNewAPIKey()
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
{keyError && (
|
||||
<div className="error-message">
|
||||
Invalid API Key. Please try again.
|
||||
</div>
|
||||
)}
|
||||
{settings.openAIKey &&
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<Switch
|
||||
checked={settings.useOpenAIKey}
|
||||
onChange={(value) => dispatch(changeSettings({useOpenAIKey: value}))}
|
||||
className={`${settings.useOpenAIKey ? 'bg-green-500' : 'bg-red-500'}
|
||||
mt-2 relative inline-flex h-[38px] w-[74px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`}
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${settings.useOpenAIKey ? 'translate-x-9' : 'translate-x-0'}
|
||||
pointer-events-none inline-block h-[34px] w-[34px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`}
|
||||
/>
|
||||
</Switch>
|
||||
{settings.useOpenAIKey ? (
|
||||
|
||||
<span className="ml-2">Enabled</span>
|
||||
) : (
|
||||
<span className="ml-2">Disabled</span>
|
||||
)}
|
||||
</div>
|
||||
{settings.useOpenAIKey &&
|
||||
<Dropdown
|
||||
options={models}
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
changeSettings({
|
||||
openAIModel: e.value,
|
||||
})
|
||||
)
|
||||
}}
|
||||
value={settings.openAIModel}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function OpenAIPanel() {
|
||||
const settings = useAppSelector(ssel.getSettings)
|
||||
const [localAPIKey, setLocalAPIKey] = useState('')
|
||||
|
|
|
@ -1673,7 +1673,7 @@ cm-diff-cancel > div {
|
|||
}
|
||||
|
||||
.input-error {
|
||||
border: 1px solid red !important;
|
||||
border: 1px solid rgba(255, 0, 0, 0.5) !important;
|
||||
/* background-color: #ffe6e6; */
|
||||
}
|
||||
|
||||
|
@ -2505,3 +2505,55 @@ free servers .disabled_command .shortcut__block {
|
|||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.signup__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.signup__title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 24px;
|
||||
font-weight: 600;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.signup__subtitle {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.signup__signup_button {
|
||||
padding: 8px 16px;
|
||||
width: 100%;
|
||||
background-color: var(--vscode-blue);
|
||||
border-radius: 5px;
|
||||
margin: 8px 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.signup__signup_button:hover {
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.signup__module {
|
||||
margin-bottom: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.signup__last_module {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 8px;
|
||||
color: rgb(251 142 142 / 90%);
|
||||
font-weight: 600;
|
||||
}
|
15
src/utils.ts
15
src/utils.ts
|
@ -8,45 +8,50 @@ export class ExpectedBackendError extends Error {
|
|||
|
||||
export class NoAuthRateLimitError extends ExpectedBackendError {
|
||||
constructor(
|
||||
message = 'You have reached the rate limit for unauthenticated requests. Please authenticate to continue.'
|
||||
message = 'You\'ve reached the rate limit for unauthenticated requests. Please log in to continue.',
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'NoAuthRateLimitError'
|
||||
this.title = 'Please log in to continue...'
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthRateLimitError extends ExpectedBackendError {
|
||||
constructor(
|
||||
message = 'You have reached the rate limit for authenticated requests. Please wait before making more requests.'
|
||||
message = 'It seems like you\'re making an unusual number of AI requests. Please try again later. If you think this is a mistake, please contact admin@cursor.so'
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'AuthRateLimitError'
|
||||
this.title = 'You\'re going a bit fast...'
|
||||
} }
|
||||
|
||||
export class NoAuthLocalRateLimitError extends ExpectedBackendError {
|
||||
constructor(
|
||||
message = 'You have reached the rate limit for unauthenticated local requests. Please authenticate to continue.'
|
||||
message = 'To protect our backend, we ask that free users limit their usage to 30 prompts per hour. To raise this limit, feel free to upgrade to pro.'
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'NoAuthLocalRateLimitError'
|
||||
this.title = 'You\'re going a bit fast...'
|
||||
}
|
||||
}
|
||||
|
||||
export class NoAuthGlobalOldRateLimitError extends ExpectedBackendError {
|
||||
constructor(
|
||||
message = 'You have reached the rate limit for unauthenticated global requests. Please wait before making more requests.'
|
||||
message = 'If you\'ve enjoyed using Cursor, please consider subscribing to one of our paid plans. Otherwise, you can enter your Open AI key (gear icon) to continue using the AI features at-cost.'
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'NoAuthGlobalOldRateLimitError'
|
||||
this.title = 'Free tier limit exceeded'
|
||||
}
|
||||
}
|
||||
|
||||
export class NoAuthGlobalNewRateLimitError extends ExpectedBackendError {
|
||||
constructor(
|
||||
message = 'You have reached the rate limit for unauthenticated global requests. Please wait before making more requests.'
|
||||
message = 'We\'re currently experiencing a high volume of requests. Please try again in a few minutes. For support, please contact admin@cursor.so.'
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'NoAuthGlobalNewRateLimitError'
|
||||
this.title = 'Our servers are overloaded...'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue