import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { BookOpenIcon, BugIcon, UploadIcon, ChevronLeftIcon, CheckIcon, XIcon, FileIcon, SparklesIcon, } from "lucide-react"; import { IpcClient } from "@/ipc/ipc_client"; import { useState, useEffect } from "react"; import { useAtomValue } from "jotai"; import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { ChatLogsData } from "@/ipc/ipc_types"; import { showError } from "@/lib/toast"; import { HelpBotDialog } from "./HelpBotDialog"; import { useSettings } from "@/hooks/useSettings"; import { BugScreenshotDialog } from "./BugScreenshotDialog"; import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo"; interface HelpDialogProps { isOpen: boolean; onClose: () => void; } export function HelpDialog({ isOpen, onClose }: HelpDialogProps) { const [isLoading, setIsLoading] = useState(false); const [isUploading, setIsUploading] = useState(false); const [reviewMode, setReviewMode] = useState(false); const [chatLogsData, setChatLogsData] = useState(null); const [uploadComplete, setUploadComplete] = useState(false); const [sessionId, setSessionId] = useState(""); const [isHelpBotOpen, setIsHelpBotOpen] = useState(false); const [isBugScreenshotOpen, setIsBugScreenshotOpen] = useState(false); const selectedChatId = useAtomValue(selectedChatIdAtom); const { settings } = useSettings(); const { userBudget } = useUserBudgetInfo(); const isDyadProUser = settings?.providerSettings?.["auto"]?.apiKey?.value; // Function to reset all dialog state const resetDialogState = () => { setIsLoading(false); setIsUploading(false); setReviewMode(false); setChatLogsData(null); setUploadComplete(false); setSessionId(""); }; // Reset state when dialog closes or reopens useEffect(() => { if (!isOpen) { resetDialogState(); } }, [isOpen]); // Wrap the original onClose to also reset state const handleClose = () => { onClose(); }; const handleReportBug = async () => { setIsLoading(true); try { // Get system debug info const debugInfo = await IpcClient.getInstance().getSystemDebugInfo(); // Create a formatted issue body with the debug info const issueBody = ` ## Bug Description (required) ## Steps to Reproduce (required) ## Expected Behavior (required) ## Actual Behavior (required) ## Screenshot (Optional) ## System Information - Dyad Version: ${debugInfo.dyadVersion} - Platform: ${debugInfo.platform} - Architecture: ${debugInfo.architecture} - Node Version: ${debugInfo.nodeVersion || "n/a"} - PNPM Version: ${debugInfo.pnpmVersion || "n/a"} - Node Path: ${debugInfo.nodePath || "n/a"} - Pro User ID: ${userBudget?.redactedUserId || "n/a"} - Telemetry ID: ${debugInfo.telemetryId || "n/a"} - Model: ${debugInfo.selectedLanguageModel || "n/a"} ## Logs \`\`\` ${debugInfo.logs.slice(-3_500) || "No logs available"} \`\`\` `; // Create the GitHub issue URL with the pre-filled body const encodedBody = encodeURIComponent(issueBody); const encodedTitle = encodeURIComponent("[bug] "); const labels = ["bug"]; if (isDyadProUser) { labels.push("pro"); } const githubIssueUrl = `https://github.com/dyad-sh/dyad/issues/new?title=${encodedTitle}&labels=${labels}&body=${encodedBody}`; // Open the pre-filled GitHub issue page IpcClient.getInstance().openExternalUrl(githubIssueUrl); } catch (error) { console.error("Failed to prepare bug report:", error); // Fallback to opening the regular GitHub issue page IpcClient.getInstance().openExternalUrl( "https://github.com/dyad-sh/dyad/issues/new", ); } finally { setIsLoading(false); } }; const handleUploadChatSession = async () => { if (!selectedChatId) { alert("Please select a chat first"); return; } setIsUploading(true); try { // Get chat logs (includes debug info, chat data, and codebase) const chatLogs = await IpcClient.getInstance().getChatLogs(selectedChatId); // Store data for review and switch to review mode setChatLogsData(chatLogs); setReviewMode(true); } catch (error) { console.error("Failed to upload chat session:", error); alert( "Failed to upload chat session. Please try again or report manually.", ); } finally { setIsUploading(false); } }; const handleSubmitChatLogs = async () => { if (!chatLogsData) return; setIsUploading(true); try { // Prepare data for upload const chatLogsJson = { systemInfo: chatLogsData.debugInfo, chat: chatLogsData.chat, codebaseSnippet: chatLogsData.codebase, }; // Get signed URL const response = await fetch( "https://upload-logs.dyad.sh/generate-upload-url", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ extension: "json", contentType: "application/json", }), }, ); if (!response.ok) { showError(`Failed to get upload URL: ${response.statusText}`); throw new Error(`Failed to get upload URL: ${response.statusText}`); } const { uploadUrl, filename } = await response.json(); await IpcClient.getInstance().uploadToSignedUrl( uploadUrl, "application/json", chatLogsJson, ); // Extract session ID (filename without extension) const sessionId = filename.replace(".json", ""); setSessionId(sessionId); setUploadComplete(true); setReviewMode(false); } catch (error) { console.error("Failed to upload chat logs:", error); alert("Failed to upload chat logs. Please try again."); } finally { setIsUploading(false); } }; const handleCancelReview = () => { setReviewMode(false); setChatLogsData(null); }; const handleOpenGitHubIssue = () => { // Create a GitHub issue with the session ID const issueBody = ` Session ID: ${sessionId} Pro User ID: ${userBudget?.redactedUserId || "n/a"} ## Issue Description (required) ## Expected Behavior (required) ## Actual Behavior (required) `; const encodedBody = encodeURIComponent(issueBody); const encodedTitle = encodeURIComponent("[session report] "); const labels = ["support"]; if (isDyadProUser) { labels.push("pro"); } const githubIssueUrl = `https://github.com/dyad-sh/dyad/issues/new?title=${encodedTitle}&labels=${labels}&body=${encodedBody}`; IpcClient.getInstance().openExternalUrl(githubIssueUrl); handleClose(); }; if (uploadComplete) { return ( Upload Complete

Chat Logs Uploaded Successfully

{ try { await navigator.clipboard.writeText(sessionId); } catch (err) { console.error("Failed to copy session ID:", err); } }} /> {sessionId}

You must open a GitHub issue for us to investigate. Without a linked issue, your report will not be reviewed.

); } if (reviewMode && chatLogsData) { return ( OK to upload chat session? Please review the information that will be submitted. Your chat messages, system information, and a snapshot of your codebase will be included.

Chat Messages

{chatLogsData.chat.messages.map((msg) => (
{msg.role === "user" ? "You" : "Assistant"}:{" "} {msg.content}
))}

Codebase Snapshot

{chatLogsData.codebase}

Logs

{chatLogsData.debugInfo.logs}

System Information

Dyad Version: {chatLogsData.debugInfo.dyadVersion}

Platform: {chatLogsData.debugInfo.platform}

Architecture: {chatLogsData.debugInfo.architecture}

Node Version:{" "} {chatLogsData.debugInfo.nodeVersion || "Not available"}

); } return ( Need help with Dyad? If you need help or want to report an issue, here are some options:
{isDyadProUser ? (

Opens an in-app help chat assistant that searches through Dyad's docs.

) : (

Get help with common questions and issues.

)}

We'll auto-fill your report with system info and logs. You can review it for any sensitive info before submitting.

Share chat logs and code for troubleshooting. Data is used only to resolve your issue and auto-deleted after a limited time.

setIsHelpBotOpen(false)} /> setIsBugScreenshotOpen(false)} handleReportBug={handleReportBug} isLoading={isLoading} />
); }