Enable opt-in telemetry

This commit is contained in:
Will Chen
2025-04-21 12:49:54 -07:00
parent 497a3b7dac
commit 16d5320485
18 changed files with 299 additions and 81 deletions

View File

@@ -21,13 +21,14 @@ import {
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { NodeSystemInfo } from "@/ipc/ipc_types";
import { usePostHog } from "posthog-js/react";
type NodeInstallStep =
| "install"
| "waiting-for-continue"
| "continue-processing";
export function SetupBanner() {
const { capture } = usePostHog();
const navigate = useNavigate();
const { isAnyProviderSetup, loading } = useSettings();
const [nodeSystemInfo, setNodeSystemInfo] = useState<NodeSystemInfo | null>(
@@ -53,6 +54,7 @@ export function SetupBanner() {
}, [checkNode]);
const handleAiSetupClick = () => {
capture("setup-flow:ai-provider-setup-click");
navigate({
to: providerSettingsRoute.id,
params: { provider: "google" },
@@ -60,11 +62,13 @@ export function SetupBanner() {
};
const handleNodeInstallClick = useCallback(async () => {
capture("setup-flow:start-node-install-click");
setNodeInstallStep("waiting-for-continue");
IpcClient.getInstance().openExternalUrl(nodeSystemInfo!.nodeDownloadUrl);
}, [nodeSystemInfo, setNodeInstallStep]);
const finishNodeInstall = useCallback(async () => {
capture("setup-flow:continue-node-install-click");
setNodeInstallStep("continue-processing");
await IpcClient.getInstance().reloadEnvPath();
await checkNode();

View File

@@ -0,0 +1,69 @@
import { IpcClient } from "@/ipc/ipc_client";
import React, { useState } from "react";
import { Button } from "./ui/button";
import { atom, useAtom } from "jotai";
import { useSettings } from "@/hooks/useSettings";
const hideBannerAtom = atom(false);
export function PrivacyBanner() {
const [hideBanner, setHideBanner] = useAtom(hideBannerAtom);
const { settings, updateSettings } = useSettings();
// TODO: Implement state management for banner visibility and user choice
// TODO: Implement functionality for Accept, Reject, Ask me later buttons
// TODO: Add state to hide/show banner based on user choice
if (hideBanner) {
return null;
}
if (settings?.telemetryConsent !== "unset") {
return null;
}
return (
<div className="fixed bg-(--background)/90 bottom-4 right-4 backdrop-blur-md border border-gray-200 dark:border-gray-700 p-4 rounded-lg shadow-lg z-50 max-w-md">
<div className="flex flex-col gap-3">
<div>
<h4 className="text-base font-semibold text-gray-800 dark:text-gray-200">
Share anonymous data?
</h4>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
Help improve Dyad with anonymous usage data.
<em className="block italic mt-0.5">
Note: this does not log your code or messages.
</em>
<a
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://dyad.sh/docs/telemetry"
);
}}
className="cursor-pointer text-sm text-blue-600 dark:text-blue-400 hover:underline"
>
Learn more
</a>
</p>
</div>
<div className="flex gap-2 justify-end">
<Button
variant="default"
onClick={() => {
updateSettings({ telemetryConsent: "opted_in" });
}}
>
Accept
</Button>
<Button
variant="secondary"
onClick={() => {
updateSettings({ telemetryConsent: "opted_out" });
}}
>
Reject
</Button>
<Button variant="ghost" onClick={() => setHideBanner(true)}>
Later
</Button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,25 @@
import { useSettings } from "@/hooks/useSettings";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { showInfo } from "@/lib/toast";
export function TelemetrySwitch() {
const { settings, updateSettings } = useSettings();
return (
<div className="flex items-center space-x-2">
<Switch
id="telemetry-switch"
checked={settings?.telemetryConsent === "opted_in"}
onCheckedChange={() => {
updateSettings({
telemetryConsent:
settings?.telemetryConsent === "opted_in"
? "opted_out"
: "opted_in",
});
}}
/>
<Label htmlFor="telemetry-switch">Telemetry</Label>
</div>
);
}

View File

@@ -17,6 +17,7 @@ import {
} from "@/components/ui/sidebar";
import { ChatList } from "./ChatList";
import { AppList } from "./AppList";
import { usePostHog } from "posthog-js/react";
// Menu items.
const items = [
@@ -123,6 +124,8 @@ function AppIcons({
}: {
onHoverChange: (state: HoverState) => void;
}) {
const { capture } = usePostHog();
const routerState = useRouterState();
const pathname = routerState.location.pathname;

View File

@@ -36,8 +36,9 @@ import type { Message } from "@/ipc/ipc_types";
import { isPreviewOpenAtom } from "@/atoms/viewAtoms";
import { useRunApp } from "@/hooks/useRunApp";
import { AutoApproveSwitch } from "../AutoApproveSwitch";
import { usePostHog } from "posthog-js/react";
export function ChatInput({ chatId }: { chatId?: number }) {
const { capture } = usePostHog();
const [inputValue, setInputValue] = useAtom(chatInputValueAtom);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const { settings, updateSettings, isAnyProviderSetup } = useSettings();
@@ -104,6 +105,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
const currentInput = inputValue;
setInputValue("");
await streamMessage({ prompt: currentInput, chatId });
capture("chat:submit");
};
const handleCancel = () => {
@@ -124,6 +126,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
`Approving proposal for chatId: ${chatId}, messageId: ${messageId}`
);
setIsApproving(true);
capture("chat:approve");
try {
const result = await IpcClient.getInstance().approveProposal({
chatId,
@@ -157,6 +160,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
`Rejecting proposal for chatId: ${chatId}, messageId: ${messageId}`
);
setIsRejecting(true);
capture("chat:reject");
try {
const result = await IpcClient.getInstance().rejectProposal({
chatId,

View File

@@ -6,6 +6,7 @@ import { useSettings } from "@/hooks/useSettings";
import { homeChatInputValueAtom } from "@/atoms/chatAtoms"; // Use a different atom for home input
import { useAtom } from "jotai";
import { useStreamChat } from "@/hooks/useStreamChat";
import { usePostHog } from "posthog-js/react";
export function HomeChatInput({ onSubmit }: { onSubmit: () => void }) {
const [inputValue, setInputValue] = useAtom(homeChatInputValueAtom);
@@ -14,7 +15,6 @@ export function HomeChatInput({ onSubmit }: { onSubmit: () => void }) {
const { streamMessage, isStreaming, setIsStreaming } = useStreamChat({
hasChatId: false,
}); // eslint-disable-line @typescript-eslint/no-unused-vars
const adjustHeight = () => {
const textarea = textareaRef.current;
if (textarea) {