Skip to content

Commit

Permalink
Merge pull request #3796 from continuedev/dallin/free-trial-over
Browse files Browse the repository at this point in the history
Free Trial Over Dialog and Onboarding Card
  • Loading branch information
sestinj authored Jan 23, 2025
2 parents 23f05b2 + e2a9b99 commit d4b72ca
Show file tree
Hide file tree
Showing 21 changed files with 205 additions and 107 deletions.
8 changes: 8 additions & 0 deletions core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
import { usePlatform } from "./control-plane/flags";
import type { FromCoreProtocol, ToCoreProtocol } from "./protocol";
import type { IMessenger, Message } from "./protocol/messenger";
import { controlPlaneEnv } from "./control-plane/env";

export class Core {
// implements IMessenger<ToCoreProtocol, FromCoreProtocol>
Expand Down Expand Up @@ -294,6 +295,13 @@ export class Core {
addContextProvider(msg.data);
});

on("controlPlane/openUrl", async (msg) => {
await this.messenger.request(
"openUrl",
`${controlPlaneEnv.APP_URL}${msg.data.path}`,
);
});

// Context providers
on("context/addDocs", async (msg) => {
void this.docsService.indexAndAdd(msg.data);
Expand Down
1 change: 1 addition & 0 deletions core/protocol/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,5 @@ export type ToCoreFromIdeOrWebviewProtocol = {
{ contextItems: ContextItem[] },
];
"clipboardCache/add": [{ content: string }, void];
"controlPlane/openUrl": [{ path: string }, void];
};
1 change: 1 addition & 0 deletions core/protocol/passThrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const WEBVIEW_TO_CORE_PASS_THROUGH: (keyof ToCoreFromWebviewProtocol)[] =
"profiles/switch",
"didChangeSelectedProfile",
"tools/call",
"controlPlane/openUrl",
];

// Message types to pass through from core to webview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class MessageTypes {
"profiles/switch",
"didChangeSelectedProfile",
"tools/call",
"controlPlane/openUrl"
)
}
}
3 changes: 2 additions & 1 deletion gui/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useAppSelector } from "../redux/hooks";
import { selectDefaultModel } from "../redux/slices/configSlice";
import { FREE_TRIAL_LIMIT_REQUESTS } from "../util/freeTrial";
import { getLocalStorage } from "../util/localStorage";
import FreeTrialProgressBar from "./loaders/FreeTrialProgressBar";

function Footer() {
Expand All @@ -10,7 +11,7 @@ function Footer() {
return (
<footer className="flex flex-col border-0 border-t border-solid border-t-zinc-700 px-2 py-2">
<FreeTrialProgressBar
completed={parseInt(localStorage.getItem("ftc") || "0")}
completed={getLocalStorage("ftc") ?? 0}
total={FREE_TRIAL_LIMIT_REQUESTS}
/>
</footer>
Expand Down
9 changes: 2 additions & 7 deletions gui/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
newSession,
} from "../redux/slices/sessionSlice";
import { getFontSize, isMetaEquivalentKeyPressed } from "../util";
import { getLocalStorage, setLocalStorage } from "../util/localStorage";
import { ROUTES } from "../util/navigation";
import TextDialog from "./dialogs";
import Footer from "./Footer";
Expand All @@ -23,6 +22,7 @@ import AccountDialog from "./AccountDialog";
import { AuthProvider } from "../context/Auth";
import { exitEditMode } from "../redux/thunks";
import { loadLastSession, saveCurrentSession } from "../redux/thunks/session";
import { incrementFreeTrialCount } from "../util/freeTrial";

const LayoutTopDiv = styled(CustomScrollbarDiv)`
height: 100%;
Expand Down Expand Up @@ -150,12 +150,7 @@ const Layout = () => {
useWebviewListener(
"incrementFtc",
async () => {
const u = getLocalStorage("ftc");
if (u) {
setLocalStorage("ftc", u + 1);
} else {
setLocalStorage("ftc", 1);
}
incrementFreeTrialCount();
},
[],
);
Expand Down
14 changes: 10 additions & 4 deletions gui/src/components/OnboardingCard/OnboardingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export interface OnboardingCardState {
activeTab?: TabTitle;
}

export function OnboardingCard() {
interface OnboardingCardProps {
isDialog?: boolean;
}

export function OnboardingCard({ isDialog }: OnboardingCardProps) {
const onboardingCard = useOnboardingCard();

function renderTabContent() {
Expand Down Expand Up @@ -49,9 +53,11 @@ export function OnboardingCard() {
activeTab={onboardingCard.activeTab || "Best"}
onTabClick={onboardingCard.setActiveTab}
/>
<CloseButton onClick={onboardingCard.close}>
<XMarkIcon className="mt-1.5 hidden h-5 w-5 hover:brightness-125 sm:flex" />
</CloseButton>
{!isDialog && (
<CloseButton onClick={() => onboardingCard.close()}>
<XMarkIcon className="mt-1.5 hidden h-5 w-5 hover:brightness-125 sm:flex" />
</CloseButton>
)}
<div className="content py-4">{renderTabContent()}</div>
</StyledCard>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import { useSubmitOnboarding } from "../hooks";
import JetBrainsFetchGitHubTokenDialog from "./JetBrainsFetchGitHubTokenDialog";
import { setDefaultModel } from "../../../redux/slices/configSlice";

function QuickstartSubmitButton() {
interface QuickstartSubmitButtonProps {
isDialog?: boolean;
}

function QuickstartSubmitButton({ isDialog }: QuickstartSubmitButtonProps) {
const ideMessenger = useContext(IdeMessengerContext);
const dispatch = useDispatch();

const { submitOnboarding } = useSubmitOnboarding("Quickstart");
const { submitOnboarding } = useSubmitOnboarding("Quickstart", isDialog);

function onComplete() {
submitOnboarding();
Expand Down
22 changes: 11 additions & 11 deletions gui/src/components/OnboardingCard/hooks/useOnboardingCard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { TabTitle } from "../components/OnboardingCardTabs";
import { setOnboardingCard } from "../../../redux/slices/uiSlice";
import {
setDialogMessage,
setOnboardingCard,
setShowDialog,
} from "../../../redux/slices/uiSlice";
import { OnboardingCardState } from "..";
import { getLocalStorage, setLocalStorage } from "../../../util/localStorage";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
Expand All @@ -12,7 +16,7 @@ export interface UseOnboardingCard {
activeTab: OnboardingCardState["activeTab"];
setActiveTab: (tab: TabTitle) => void;
open: (tab: TabTitle) => void;
close: () => void;
close: (isDialog?: boolean) => void;
}

export function useOnboardingCard(): UseOnboardingCard {
Expand All @@ -38,20 +42,16 @@ export function useOnboardingCard(): UseOnboardingCard {

async function open(tab: TabTitle) {
navigate("/");

// Used to clear the chat panel before showing onboarding card
dispatch(
saveCurrentSession({
openNewSession: true,
}),
);

dispatch(setOnboardingCard({ show: true, activeTab: tab }));
}

function close() {
function close(isDialog = false) {
setLocalStorage("hasDismissedOnboardingCard", true);
dispatch(setOnboardingCard({ show: false }));
if (isDialog) {
dispatch(setDialogMessage(undefined));
dispatch(setShowDialog(false));
}
}

function setActiveTab(tab: TabTitle) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { OnboardingModes } from "core/protocol/core";
import { useTutorialCard } from "../../../hooks/useTutorialCard";
import { useOnboardingCard } from "./useOnboardingCard";

export function useSubmitOnboarding(mode: OnboardingModes) {
export function useSubmitOnboarding(mode: OnboardingModes, isDialog = false) {
const posthog = usePostHog();
const ideMessenger = useContext(IdeMessengerContext);
const { openTutorialCard } = useTutorialCard();
Expand All @@ -16,7 +16,7 @@ export function useSubmitOnboarding(mode: OnboardingModes) {
const onboardingStatus = getLocalStorage("onboardingStatus");

// Always close the onboarding card and update config.json
closeOnboardingCard();
closeOnboardingCard(isDialog);
ideMessenger.post("completeOnboarding", {
mode,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ export interface OnboardingCardState {
activeTab?: TabTitle;
}

export function PlatformOnboardingCard() {
interface OnboardingCardProps {
isDialog: boolean;
}

export function PlatformOnboardingCard({ isDialog }: OnboardingCardProps) {
const onboardingCard = useOnboardingCard();

if (getLocalStorage("onboardingStatus") === undefined) {
Expand All @@ -34,13 +38,18 @@ export function PlatformOnboardingCard() {

return (
<StyledCard className="xs:py-4 xs:px-4 relative px-2 py-3">
<CloseButton onClick={onboardingCard.close}>
<XMarkIcon className="mt-1.5 hidden h-5 w-5 hover:brightness-125 sm:flex" />
</CloseButton>
{!isDialog && (
<CloseButton onClick={() => onboardingCard.close()}>
<XMarkIcon className="mt-1.5 hidden h-5 w-5 hover:brightness-125 sm:flex" />
</CloseButton>
)}
<div className="content py-4">
<div className="flex h-full w-full items-center justify-center">
{currentTab === "main" ? (
<MainTab onRemainLocal={() => setCurrentTab("local")} />
<MainTab
onRemainLocal={() => setCurrentTab("local")}
isDialog={isDialog}
/>
) : (
<div className="mt-4">
<Alert type="info">
Expand All @@ -53,7 +62,7 @@ export function PlatformOnboardingCard() {
</a>
</Alert>

<OnboardingLocalTab />
<OnboardingLocalTab isDialog={isDialog} />
</div>
)}
</div>
Expand Down
26 changes: 0 additions & 26 deletions gui/src/components/OnboardingCard/platform/tabs/local.tsx

This file was deleted.

69 changes: 50 additions & 19 deletions gui/src/components/OnboardingCard/platform/tabs/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,78 @@ import { Button, ButtonSubtext } from "../../..";
import { useAuth } from "../../../../context/Auth";
import ContinueLogo from "../../../gui/ContinueLogo";
import { useOnboardingCard } from "../../hooks";
import { hasPassedFTL } from "../../../../util/freeTrial";
import { useContext } from "react";
import { IdeMessengerContext } from "../../../../context/IdeMessenger";

export default function MainTab({
onRemainLocal,
isDialog,
}: {
onRemainLocal: () => void;
isDialog: boolean;
}) {
const ideMessenger = useContext(IdeMessengerContext);
const onboardingCard = useOnboardingCard();
const auth = useAuth();

function onGetStarted() {
auth.login(true).then((success) => {
if (success) {
onboardingCard.close();
onboardingCard.close(isDialog);
}
});
}

function openPastFreeTrialOnboarding() {
ideMessenger.post("controlPlane/openUrl", {
path: "setup-models",
});
onboardingCard.close(isDialog);
}

const pastFreeTrialLimit = hasPassedFTL();

return (
<div className="xs:px-0 flex w-full max-w-full flex-col items-center justify-center px-4 text-center">
<div className="xs:flex hidden">
<ContinueLogo height={75} />
</div>

<p className="xs:w-3/4 w-full text-sm">
Log in to quickly build your first custom AI code assistant
</p>

<div className="mt-4 w-full">
<Button
onClick={onGetStarted}
className="grid w-full grid-flow-col items-center gap-2"
>
Get started
</Button>
<ButtonSubtext onClick={onRemainLocal}>
<div className="mt-4 flex cursor-pointer items-center justify-center gap-1">
<span>Or, remain local</span>
<ChevronRightIcon className="h-3 w-3" />
</div>
</ButtonSubtext>
</div>
{pastFreeTrialLimit ? (
<>
<p className="xs:w-3/4 w-full text-sm">
You've reached the free trial limit. Visit the Continue Platform to
select a Coding Assistant.
</p>
<Button
onClick={openPastFreeTrialOnboarding}
className="mt-4 grid w-full grid-flow-col items-center gap-2"
>
Go to Continue Platform
</Button>
</>
) : (
<>
<p className="xs:w-3/4 w-full text-sm">
Log in to quickly build your first custom AI code assistant
</p>

<Button
onClick={onGetStarted}
className="mt-4 grid w-full grid-flow-col items-center gap-2"
>
Get started
</Button>
</>
)}

<ButtonSubtext onClick={onRemainLocal}>
<div className="mt-4 flex cursor-pointer items-center justify-center gap-1">
<span>Or, remain local</span>
<ChevronRightIcon className="h-3 w-3" />
</div>
</ButtonSubtext>
</div>
);
}
8 changes: 6 additions & 2 deletions gui/src/components/OnboardingCard/tabs/OnboardingBestTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import BestExperienceConfigForm from "../components/BestExperienceConfigForm";
import ProviderAlert from "../components/ProviderAlert";
import { useSubmitOnboarding } from "../hooks";

function OnboardingBestTab() {
const { submitOnboarding } = useSubmitOnboarding("Best");
interface OnboardingBestTabProps {
isDialog?: boolean;
}

function OnboardingBestTab({ isDialog }: OnboardingBestTabProps) {
const { submitOnboarding } = useSubmitOnboarding("Best", isDialog);

return (
<div className="flex flex-col gap-8">
Expand Down
Loading

0 comments on commit d4b72ca

Please sign in to comment.