Enable opt-in telemetry
This commit is contained in:
@@ -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();
|
||||
|
||||
69
src/components/TelemetryBanner.tsx
Normal file
69
src/components/TelemetryBanner.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
25
src/components/TelemetrySwitch.tsx
Normal file
25
src/components/TelemetrySwitch.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user