Skip to content

Commit

Permalink
Merge branch 'pricingBrowser' of https://github.com/getcursor/cursor
Browse files Browse the repository at this point in the history
…into pricingBrowser
  • Loading branch information
Sanger2000 committed Mar 30, 2023
2 parents d5e1798 + b85efe2 commit 9d5b148
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 22 deletions.
99 changes: 83 additions & 16 deletions src/components/errors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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)
Expand Down Expand Up @@ -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"
Expand All @@ -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())
}}
Expand All @@ -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"
Expand All @@ -117,7 +184,7 @@ export function ErrorPopup() {
</div>
</div>
<div className="errorPopup__body">
{error.message}
{/* {error.message} */}
<br />
</div>
</div>
Expand Down
111 changes: 111 additions & 0 deletions src/components/settingsPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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('')
Expand Down
54 changes: 53 additions & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; */
}

Expand Down Expand Up @@ -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 changes: 10 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 [email protected]'
) {
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 [email protected].'
) {
super(message)
this.name = 'NoAuthGlobalNewRateLimitError'
this.title = 'Our servers are overloaded...'
}
}

Expand Down

0 comments on commit 9d5b148

Please sign in to comment.