Merge branch 'pricingBrowser' of https://github.com/getcursor/cursor into pricingBrowser

pricingBrowser
Sanger2000 2023-03-29 19:30:24 -07:00
commit 9d5b1488f9
4 changed files with 257 additions and 22 deletions

View File

@ -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>

View File

@ -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('')

View File

@ -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;
}

View File

@ -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...'
}
}