Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
18 changes: 14 additions & 4 deletions src/routes/Settings/SettingsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,25 @@ const AGENT_ITEM: SidebarItem = {
testId: "settings-nav-agent",
};

const VSCODE_ITEM: SidebarItem = {
to: "/settings/vscode-extension",
label: "VS Code Extension",
icon: "Download",
testId: "settings-nav-vscode-extension",
};

export function SettingsLayout() {
const router = useRouter();
const componentSearchEnabled = useFlagValue("component-search-v2");
const aiAssistantEnabled = useFlagValue("ai-assistant");
const tangentShellEnabled = useTangentEnabled();
const sidebarItems =
componentSearchEnabled || aiAssistantEnabled || tangentShellEnabled
? [...SIDEBAR_ITEMS, AGENT_ITEM]
: SIDEBAR_ITEMS;
const sidebarItems = [
...SIDEBAR_ITEMS,
...(componentSearchEnabled || aiAssistantEnabled || tangentShellEnabled
? [AGENT_ITEM]
: []),
...(tangentShellEnabled ? [VSCODE_ITEM] : []),
];

const handleGoBack = () => {
router.history.back();
Expand Down
129 changes: 129 additions & 0 deletions src/routes/Settings/sections/VsCodeExtensionSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { useEffect, useState } from "react";

import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Heading, Paragraph, Text } from "@/components/ui/typography";
import { CopyValueButton } from "@/routes/v2/shared/components/CopyValueButton";

interface ExtensionManifest {
version: string;
filename: string;
url: string;
}

function isExtensionManifest(value: unknown): value is ExtensionManifest {
return (
typeof value === "object" &&
value !== null &&
"version" in value &&
typeof value.version === "string" &&
"filename" in value &&
typeof value.filename === "string" &&
"url" in value &&
typeof value.url === "string"
);
}

const STABLE_VSIX_PATH = "/extension/tangent-local-agent.vsix";
const DEFAULT_VSIX_FILENAME = "tangent-local-agent.vsix";

function CommandBlock({ command }: { command: string }) {
return (
<InlineStack
gap="2"
blockAlign="center"
wrap="nowrap"
className="w-full min-w-0 rounded-md border border-border bg-muted px-3 py-2"
>
<Text
size="sm"
font="mono"
className="min-w-0 flex-1 overflow-x-auto whitespace-pre"
>
{command}
</Text>
<CopyValueButton valueFn={() => command} />
</InlineStack>
);
}

export function VsCodeExtensionSettings() {
const [manifest, setManifest] = useState<ExtensionManifest | null>(null);
const search = window.location.search;

useEffect(() => {
let cancelled = false;
fetch(`/extension/latest.json${search}`)
.then((response) => (response.ok ? response.json() : null))
.then((data) => {
if (!cancelled) {
setManifest(isExtensionManifest(data) ? data : null);
}
})
.catch(() => {
if (!cancelled) setManifest(null);
});
return () => {
cancelled = true;
};
}, [search]);

const origin = window.location.origin;
const downloadUrl = manifest
? `${origin}${manifest.url}${search}`
: `${STABLE_VSIX_PATH}${search}`;
const vsixFilename = manifest?.filename ?? DEFAULT_VSIX_FILENAME;

return (
<BlockStack gap="6">
<BlockStack gap="3">
<Heading level={2} weight="semibold">
VS Code Extension: Manual install and upgrade
</Heading>
<Paragraph size="sm" tone="subdued">
Download the package and install it from the editor. Re-running either
path always pulls the latest version.
</Paragraph>
<InlineStack gap="2" blockAlign="center">
<Button asChild>
<a href={downloadUrl} download>
<Icon name="Download" size="sm" />
Download .vsix{manifest ? ` (v${manifest.version})` : ""}
</a>
</Button>
</InlineStack>
<Tabs defaultValue="cursor">
<TabsList>
<TabsTrigger value="cursor">Cursor</TabsTrigger>
<TabsTrigger value="vscode">Visual Studio Code</TabsTrigger>
</TabsList>
<TabsContent value="cursor">
<CommandBlock
command={`cursor --install-extension ~/Downloads/${vsixFilename}`}
/>
</TabsContent>
<TabsContent value="vscode">
<CommandBlock
command={`code --install-extension ~/Downloads/${vsixFilename}`}
/>
</TabsContent>
</Tabs>
<ol className="list-decimal space-y-1 pl-5">
<li>
<Text size="sm">Open the Extensions view, then its “…” menu.</Text>
</li>
<li>
<Text size="sm">
Choose “Install from VSIX…” and pick the downloaded file.
</Text>
</li>
<li>
<Text size="sm">Reload or restart the editor to activate it.</Text>
</li>
</ol>
</BlockStack>
</BlockStack>
);
}
1 change: 1 addition & 0 deletions src/routes/appRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const APP_ROUTES = {
SETTINGS_PREFERENCES: `${SETTINGS_PATH}/preferences`,
SETTINGS_BETA_FEATURES: `${SETTINGS_PATH}/beta-features`,
SETTINGS_AGENT: `${SETTINGS_PATH}/agent`,
SETTINGS_VSCODE: `${SETTINGS_PATH}/vscode-extension`,
SETTINGS_SECRETS: `${SETTINGS_PATH}/secrets`,
SETTINGS_SECRETS_ADD: `${SETTINGS_PATH}/secrets/add`,
SETTINGS_SECRETS_REPLACE: `${SETTINGS_PATH}/secrets/$secretId/replace`,
Expand Down
13 changes: 13 additions & 0 deletions src/routes/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { BackendSettings } from "./Settings/sections/BackendSettings";
import { BetaFeaturesSettings } from "./Settings/sections/BetaFeaturesSettings";
import { PreferencesSettings } from "./Settings/sections/PreferencesSettings";
import { SecretsSettings } from "./Settings/sections/SecretsSettings";
import { VsCodeExtensionSettings } from "./Settings/sections/VsCodeExtensionSettings";
import { SettingsLayout } from "./Settings/SettingsLayout";
import { CompareView } from "./v2/pages/CompareView/CompareView";
import { PipelineFoldersPage } from "./v2/pages/PipelineFolders/PipelineFoldersPage";
Expand Down Expand Up @@ -258,6 +259,17 @@ const settingsAgentRoute = createRoute({
},
});

const settingsVsCodeRoute = createRoute({
getParentRoute: () => settingsLayoutRoute,
path: "/vscode-extension",
component: VsCodeExtensionSettings,
beforeLoad: () => {
if (!isTangentEnabled()) {
throw redirect({ to: APP_ROUTES.SETTINGS_BACKEND });
}
},
});

const settingsSecretsRoute = createRoute({
getParentRoute: () => settingsLayoutRoute,
path: "/secrets",
Expand Down Expand Up @@ -326,6 +338,7 @@ const settingsRouteTree = settingsLayoutRoute.addChildren([
settingsPreferencesRoute,
settingsBetaFeaturesRoute,
settingsAgentRoute,
settingsVsCodeRoute,
secretsRouteTree,
]);

Expand Down
Loading
Loading