Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delay onboarding installation #162

Merged
merged 6 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion core/protocol/ideWebview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AiderState } from "../../extensions/vscode/src/integrations/aider/types/aiderTypes.js";
import { ToolType } from "../../extensions/vscode/src/util/integrationUtils.js";
import type { RangeInFileWithContents } from "../commands/util.js";
import type { ContextSubmenuItem } from "../index.js";
import { ToIdeFromWebviewOrCoreProtocol } from "./ide.js";
Expand Down Expand Up @@ -56,12 +57,13 @@ export type ToIdeFromWebviewProtocol = ToIdeFromWebviewOrCoreProtocol & {
completeWelcome: [undefined, void];
openInventory: [undefined, void];
getUrlTitle: [string, string];
pearAIinstallation: [{tools: ToolType[], installExtensions: boolean}, void];
};

export type ToWebviewFromIdeProtocol = ToWebviewFromIdeOrCoreProtocol & {
setInactive: [undefined, void];
setActiveFilePath: [string | undefined, void];
resetInteractiveContinueTutorial: [undefined, void];
restFirstLaunchInGUI: [undefined, void];
showInteractiveContinueTutorial: [undefined, void];
submitMessage: [{ message: any }, void]; // any -> JSONContent from TipTap
updateSubmenuItems: [
Expand Down
9 changes: 5 additions & 4 deletions extensions/vscode/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,12 @@ const commandsMap: (
await importUserSettingsFromVSCode();
},
"pearai.welcome.markNewOnboardingComplete": async () => {
// vscode.window.showInformationMessage("Marking onboarding complete.");
await extensionContext.globalState.update(FIRST_LAUNCH_KEY, true);
await vscode.commands.executeCommand('pearai.unlockOverlay');
await vscode.commands.executeCommand('pearai.hideOverlay');
},
"pearai.resetInteractiveContinueTutorial": async () => {
sidebar.webviewProtocol?.request("resetInteractiveContinueTutorial", undefined, [PEAR_CONTINUE_VIEW_ID]);
"pearai.restFirstLaunchInGUI": async () => {
sidebar.webviewProtocol?.request("restFirstLaunchInGUI", undefined, [PEAR_CONTINUE_VIEW_ID]);
},
"pearai.showInteractiveContinueTutorial": async () => {
sidebar.webviewProtocol?.request("showInteractiveContinueTutorial", undefined, [PEAR_CONTINUE_VIEW_ID]);
Expand Down Expand Up @@ -386,7 +387,7 @@ const commandsMap: (
await vscode.commands.executeCommand("pearai.showInteractiveContinueTutorial");
},
"pearai.developer.restFirstLaunch": async () => {
vscode.commands.executeCommand("pearai.resetInteractiveContinueTutorial");
vscode.commands.executeCommand("pearai.restFirstLaunchInGUI");
extensionContext.globalState.update(FIRST_LAUNCH_KEY, false);
vscode.window.showInformationMessage("Successfully reset PearAI first launch flag, RELOAD WINDOW TO SEE WELCOME PAGE", 'Reload Window')
.then(selection => {
Expand Down
31 changes: 21 additions & 10 deletions extensions/vscode/src/extension/VsCodeMessenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { getExtensionUri } from "../util/vscode";
import { VsCodeWebviewProtocol } from "../webviewProtocol";
import { attemptInstallExtension, attemptUninstallExtension, isVSCodeExtensionInstalled } from "../activation/activate";
import { checkAiderInstallation } from "../integrations/aider/aiderUtil";
import { TOOL_COMMANDS, ToolType } from "../util/integrationUtils";

/**
* A shared messenger class between Core and Webview
Expand Down Expand Up @@ -122,13 +123,6 @@ export class VsCodeMessenger {
});
this.onWebview("pearWelcomeOpenFolder", (msg) => {
vscode.commands.executeCommand("workbench.action.files.openFolder");
// force close overlay if a folder is already open
if (vscode.workspace.workspaceFolders?.length) {
vscode.commands.executeCommand("pearai.unlockOverlay");
vscode.commands.executeCommand("pearai.hideOverlay");
// force reload to update overlay with new global state
vscode.commands.executeCommand("workbench.action.reloadWindow");
}
});
this.onWebview("pearInstallCommandLine", (msg) => {
vscode.commands.executeCommand("workbench.action.installCommandLine");
Expand Down Expand Up @@ -168,11 +162,28 @@ export class VsCodeMessenger {
this.onWebview("openInventory", (msg) => {
vscode.commands.executeCommand("pearai.toggleInventoryHome");
});
this.onWebview("completeWelcome", (msg) => {
this.onWebview("pearAIinstallation", (msg) => {
const { tools, installExtensions } = msg.data;
if (installExtensions) {
vscode.commands.executeCommand("pearai.welcome.importUserSettingsFromVSCode");
}
Comment on lines +165 to +169

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its importing extensions in pearAIinstallation. naming can be improved here. but its not a big deal.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very true


tools.forEach((tool: ToolType) => {
const toolCommand = TOOL_COMMANDS[tool];
if (toolCommand) {
if (toolCommand.args) {
vscode.commands.executeCommand(toolCommand.command, toolCommand.args);
} else {
vscode.commands.executeCommand(toolCommand.command);
}
} else {
console.warn(`Unknown tool: ${tool}`);
}
});
});
this.onWebview("closePearAIOverlay", (msg) => {
vscode.commands.executeCommand("pearai.unlockOverlay");
vscode.commands.executeCommand("pearai.hideOverlay");
// force reload to update overlay with new global state
vscode.commands.executeCommand("workbench.action.reloadWindow");
});
this.onWebview("highlightElement", (msg) => {
vscode.commands.executeCommand("pearai.highlightElement", msg);
Expand Down
23 changes: 23 additions & 0 deletions extensions/vscode/src/util/integrationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
/* eslint-disable @typescript-eslint/naming-convention */
import * as vscode from "vscode";
import { ContinueGUIWebviewViewProvider } from "../ContinueGUIWebviewViewProvider";
import { ToWebviewProtocol } from "core/protocol";

export enum InstallableTool {
AIDER = "aider",
SUPERMAVEN = "supermaven"
}

export interface ToolCommand {
command: string;
args?: any;
}

export type ToolType = typeof InstallableTool[keyof typeof InstallableTool];

export const TOOL_COMMANDS: Record<ToolType, ToolCommand> = {
[InstallableTool.AIDER]: {
command: "pearai.installAider"
},
[InstallableTool.SUPERMAVEN]: {
command: "workbench.extensions.installExtension",
args: "supermaven.supermaven"
}
};


export function getIntegrationTab(webviewName: string) {
const tabs = vscode.window.tabGroups.all.flatMap((tabGroup) => tabGroup.tabs);
Expand Down
4 changes: 3 additions & 1 deletion gui/src/pages/gui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,10 @@ function GUI() {
[loadMostRecentChat],
);

useWebviewListener("resetInteractiveContinueTutorial", async () => {
useWebviewListener("restFirstLaunchInGUI", async () => {
setLocalStorage("showTutorialCard", true);
localStorage.removeItem("onboardingSelectedTools");
localStorage.removeItem("importUserSettingsFromVSCode");
dispatch(setShowInteractiveContinueTutorial(true));
});

Expand Down
13 changes: 9 additions & 4 deletions gui/src/pages/welcome/FinalStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,28 @@ import { IdeMessengerContext } from "@/context/IdeMessenger";
import { FolderOpen } from "lucide-react";
import { useNavigate } from "react-router-dom";

export default function FinalStep({ onBack }: { onBack: () => void }) {
export default function FinalStep({ onNext }: { onNext: () => void }) {

const navigate = useNavigate();
const selectedTools = JSON.parse(localStorage.getItem('onboardingSelectedTools'));
const installExtensions = localStorage.getItem('importUserSettingsFromVSCode') === 'true';

const handleOpenFolder = () => {
ideMessenger.post("pearWelcomeOpenFolder", undefined);
ideMessenger.post("pearAIinstallation", {tools: selectedTools, installExtensions: installExtensions})
ideMessenger.post("markNewOnboardingComplete", undefined);
onNext() // navigates to inventory page
};

const handleClose = () => {
ideMessenger.post("completeWelcome", undefined);
ideMessenger.post("pearAIinstallation", {tools: selectedTools, installExtensions: installExtensions});
ideMessenger.post("markNewOnboardingComplete", undefined);
onNext() // navigates to inventory page
};

useEffect(() => {
// unlock overlay when we get to last page
ideMessenger.post("unlockOverlay", undefined);
ideMessenger.post("markNewOnboardingComplete", undefined);
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
handleOpenFolder();
Expand All @@ -34,7 +40,6 @@ export default function FinalStep({ onBack }: { onBack: () => void }) {
const ideMessenger = useContext(IdeMessengerContext);
return (
<div className="flex w-full overflow-hidden text-foreground">

<div className="w-[35%] min-w-[320px] max-w-[420px] flex flex-col h-screen">
<div className="flex-1 overflow-y-auto">
<div className="p-6 space-y-6 pt-8">
Expand Down
22 changes: 10 additions & 12 deletions gui/src/pages/welcome/SetupPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ export default function SetupPage({ onNext }: { onNext: () => void }) {

const allSetupSteps = [
{
icon: <User className="h-6 w-6" />,
title: "Sign in",
description: "Have PearAI work for free out of the box by signing in.",
component: <SignIn onNext={handleNextClick} />,
icon: <Move className="h-5 w-5" />,
title: "Import VSCode Extensions",
description:
"Automatically import your extensions from VSCode to feel at home.",
component: <ImportExtensions onNext={handleNextClick} />,
},
{
icon: <Terminal className="h-6 w-6" />,
Expand All @@ -63,15 +64,12 @@ export default function SetupPage({ onNext }: { onNext: () => void }) {
description: "Install recommended tools to enhance your PearAI experience.",
component: <InstallTools onNext={handleNextClick} />,
},
// devnote: keep import extensions as last step.
// because if user has lots of extensions, it can take a while to activate them all
// which makes extension host unresponsive and it makes the UI feel more laggy.

{
icon: <Move className="h-5 w-5" />,
title: "Import VSCode Extensions",
description:
"Automatically import your extensions from VSCode to feel at home.",
component: <ImportExtensions onNext={handleNextClick} />,
icon: <User className="h-6 w-6" />,
title: "Sign in",
description: "Have PearAI work for free out of the box by signing in.",
component: <SignIn onNext={handleNextClick} />,
},
];

Expand Down
12 changes: 9 additions & 3 deletions gui/src/pages/welcome/setup/ImportExtensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ export default function ImportExtensions({
}: {
onNext: () => void;
}) {
const ideMessenger = useContext(IdeMessengerContext);
const [isImporting, setIsImporting] = useState(false);

const handleImport = () => {
localStorage.setItem('importUserSettingsFromVSCode', 'true');
setIsImporting(true);
ideMessenger.post("importUserSettingsFromVSCode", undefined);
onNext();
};

const handleSkip = () => {
localStorage.setItem('importUserSettingsFromVSCode', 'false');
onNext()
}

useEffect(() => {
setIsImporting(localStorage.getItem('importUserSettingsFromVSCode') === 'true')
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === 'Enter' && !isImporting) {
handleImport();
Expand Down Expand Up @@ -51,7 +57,7 @@ export default function ImportExtensions({

{!isImporting ?
<div className="absolute bottom-8 right-8 flex items-center gap-4">
<div onClick={onNext} className="flex items-center gap-2 cursor-pointer">
<div onClick={handleSkip} className="flex items-center gap-2 cursor-pointer">
<span className="text-center w-full">Skip</span>
</div>
<Button
Expand Down
72 changes: 16 additions & 56 deletions gui/src/pages/welcome/setup/InstallTools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ interface Tool {
name: string;
description: string;
icon: JSX.Element | string;
installCommand: () => Promise<void>;
preInstalled: boolean;
}

Expand All @@ -22,37 +21,25 @@ export default function InstallTools({
onNext: () => void;
}) {

const handleVSCExtensionInstall = async (extensionId: string) => {
ideMessenger.post("installVscodeExtension", { extensionId });
};

const handleAiderInstall = async () => {
ideMessenger.post("installAider", undefined);
};

const tools: Tool[] = [
{
id: "aider",
name: "PearAI Creator",
description: "PearAI Creator is a no-code tool powered by aider* that let's you build complete features with just a prompt.",
icon: "inventory-creator.svg",
installCommand: handleAiderInstall,
preInstalled: false
},
{
id: "supermaven",
name: "PearAI Predict",
description: "PearAI Predict is our upcoming code autocomplete tool. While it’s under development, we recommend using Supermaven* as a standalone extension within PearAI for code autocompletion. Selecting this option will install Supermaven.",
icon: "inventory-autocomplete.svg",
installCommand: () => handleVSCExtensionInstall("supermaven.supermaven"),
preInstalled: false
}
];

const ideMessenger = useContext(IdeMessengerContext);
const [isInstallingAll, setIsInstallingAll] = useState(false);
const [attemptedInstalls, setAttemptedInstalls] = useState<string[]>(() => {
const saved = localStorage.getItem('onboardingAttemptedInstalls');
const saved = localStorage.getItem('onboardingSelectedTools');
return saved ? JSON.parse(saved) : [];
});

Expand All @@ -64,46 +51,17 @@ export default function InstallTools({
return initialState;
});



const handleInstallAll = async () => {
setIsInstallingAll(true);

const toolsToInstall = tools.filter(tool => !attemptedInstalls.includes(tool.id));
toolsToInstall.forEach(tool => tool.installCommand());

// Save to attempted installations
const newAttemptedInstalls = [...new Set([...attemptedInstalls, ...toolsToInstall.map(t => t.id)])];
localStorage.setItem('onboardingAttemptedInstalls', JSON.stringify(newAttemptedInstalls));
setAttemptedInstalls(newAttemptedInstalls);

setTimeout(() => {
setIsInstallingAll(false);
onNext();
}, 3000);
};

const handleCheckboxChange = (toolId: string) => {
setCheckedTools(prev => ({ ...prev, [toolId]: !prev[toolId] }));
};

const handleInstallChecked = async () => {
setIsInstallingAll(true);

const selectedTools = tools.filter(tool =>
checkedTools[tool.id] && !attemptedInstalls.includes(tool.id)
checkedTools[tool.id]
);
selectedTools.forEach(tool => tool.installCommand());

// Save to attempted installations
const newAttemptedInstalls = [...new Set([...attemptedInstalls, ...selectedTools.map(t => t.id)])];
localStorage.setItem('onboardingAttemptedInstalls', JSON.stringify(newAttemptedInstalls));
setAttemptedInstalls(newAttemptedInstalls);

setTimeout(() => {
setIsInstallingAll(false);
onNext();
}, 3000);
localStorage.setItem('onboardingSelectedTools', JSON.stringify(selectedTools.map(t => t.id)));
onNext()
};

const areAllToolsSelected = () => {
Expand All @@ -119,19 +77,22 @@ export default function InstallTools({
};

const getButtonText = () => {
if (areAllToolsAttempted()) {
return "All Tools Setup Initiated";
if (areAllToolsAttempted() || !areAnyToolsSelected()) {
return "Next"
}
if (areAllToolsSelected() && attemptedInstalls?.length > 0) {
return "Install Selected Tool";
}
if (!areAnyToolsSelected()) {
return "None Selected";
if (attemptedInstalls?.length > 0) {
return "Next";
}
return areAllToolsSelected() ? "Install All Tools" : "Install Selected Tools";
};

useEffect(() => {
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === 'Enter' && !isInstallingAll) {
handleInstallAll();
if (event.key === 'Enter') {
handleInstallChecked();
} else if ((event.metaKey || event.ctrlKey) && event.key === 'ArrowRight') {
event.preventDefault();
onNext();
Expand All @@ -140,7 +101,7 @@ export default function InstallTools({

window.addEventListener('keydown', handleKeyPress);
return () => window.removeEventListener('keydown', handleKeyPress);
}, [isInstallingAll]);
}, []);

return (
<div className="step-content flex w-full h-screen items-center justify-center bg-background text-foreground">
Expand Down Expand Up @@ -195,9 +156,8 @@ export default function InstallTools({
Skip
</div>
<Button
className="w-[250px] text-button-foreground bg-button hover:bg-button-hover p-4 lg:py-6 lg:px-2 text-sm md:text-base cursor-pointer"
onClick={handleInstallChecked}
disabled={isInstallingAll || !areAnyToolsSelected() || areAllToolsAttempted()}
className="w-[250px] text-button-foreground bg-button hover:bg-button-hover p-4 lg:py-6 lg:px-2 text-sm md:text-base cursor-pointer"
onClick={handleInstallChecked}
>
{getButtonText()}
</Button>
Expand Down
Loading