Unverified 提交 5d2e87fc authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

IPC Contracts (#2276)

<!-- CURSOR_SUMMARY --> > [!NOTE] > Modernizes IPC usage app-wide to new typed, namespaced contracts. > > - Refactors calls from `IpcClient` to `ipc.*` namespaces (system, app, chat, github, git, version, languageModel, neon, visualEditing, context, misc, capacitor, template, events, upgrade, agent, proposal) > - Updates type imports from `"@/ipc/ipc_types"` to `"@/ipc/types"` (e.g., `ListedApp`, security types) and adapts optional fields > - Aligns method signatures to object params and new return shapes (e.g., `getAppVersion` → `{ version }`); replaces various URL opens/restarts/screenshots with `ipc.system.*` > - Moves custom theme and theme generation ops to `ipc.template.*`; adds maxOutputTokens in custom model dialogs; adjusts provider/model management APIs > - Switches GitHub/Vercel/Neon/Supabase/Capacitor connectors and branching flows to new IPC endpoints; updates event subscriptions for GitHub device flow > - Normalizes logging/preview interactions and visual editing apply/analyze via `ipc.misc`/`ipc.visualEditing` > > Potential follow-ups: verify all parameter objects and event handlers, and update any remaining legacy imports. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7c574fe53296ba5c9a16d6b69d1008d06490534e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adopted the new typed IPC contracts across the app, replacing IpcClient with namespaced ipc methods. Restored app output streaming via events, added maxOutputTokens for custom model flows, migrated custom theme operations to ipc.template, and fixed chat stream cancellation lifecycle. - **Refactors** - Replaced IpcClient usage with ipc.* namespaces (system, app, chat, github, git, version, languageModel, neon, visualEditing, context, prompt, mcp, misc, capacitor, template, events, upgrade, agent, proposal). - Moved type imports from "@/ipc/ipc_types" to "@/ipc/types"; adjusted renamed types and paths (e.g., ListedApp under "@/ipc/types/app", security types under "@/ipc/types/security"). - Updated method signatures to use object params and new return shapes (e.g., getAppVersion returns { version }). - Subscribed to ipc.events.misc.onAppOutput in useRunApp to process app output, restore preview URL updates, and handle HMR after API changes. - Added missing maxOutputTokens in create/edit custom model dialogs; small UI and hooks changes to align with new contracts. - Fixed DeepLinkData typing by re-exporting a discriminated union for correct type narrowing. - Migrated custom themes and theme generation to ipc.template.*; added CustomTheme and theme generation types. - Moved methods to correct namespaces (e.g., takeScreenshot/restartDyad → system; checkProblems → misc). - Emit chat:stream:end on stream cancellation for consistent renderer cleanup. - **Migration** - Replace any remaining IpcClient references with ipc.*. - Update type imports to "@/ipc/types" and adjust for changed/optional fields (e.g., App lists use ListedApp; Collaborator.permissions and VercelProject.framework may be optional). - Verify callers for new parameter objects and result shapes; replace runApp/restartApp callbacks with ipc.events.misc.onAppOutput subscriptions. <sup>Written for commit 7c574fe53296ba5c9a16d6b69d1008d06490534e. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2276"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 e9a079fb
import { describe, it, expect } from "vitest";
import { createProblemFixPrompt } from "../shared/problem_prompt";
import type { ProblemReport } from "../ipc/ipc_types";
import type { ProblemReport } from "@/ipc/types";
const snippet = `SNIPPET`;
......
......@@ -12,9 +12,9 @@ import { useDeepLink } from "@/contexts/DeepLinkContext";
import { useEffect, useState } from "react";
import { DyadProSuccessDialog } from "@/components/DyadProSuccessDialog";
import { useTheme } from "@/contexts/ThemeContext";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo";
import { UserBudgetInfo } from "@/ipc/ipc_types";
import type { UserBudgetInfo } from "@/ipc/types";
import {
Tooltip,
TooltipContent,
......@@ -35,7 +35,7 @@ export const TitleBar = () => {
// Check if we're running on Windows
const checkPlatform = async () => {
try {
const platform = await IpcClient.getInstance().getSystemPlatform();
const platform = await ipc.system.getSystemPlatform();
setShowWindowControls(platform !== "darwin");
} catch (error) {
console.error("Failed to get platform info:", error);
......@@ -115,18 +115,17 @@ export const TitleBar = () => {
function WindowsControls() {
const { isDarkMode } = useTheme();
const ipcClient = IpcClient.getInstance();
const minimizeWindow = () => {
ipcClient.minimizeWindow();
ipc.system.minimizeWindow();
};
const maximizeWindow = () => {
ipcClient.maximizeWindow();
ipc.system.maximizeWindow();
};
const closeWindow = () => {
ipcClient.closeWindow();
ipc.system.closeWindow();
};
return (
......@@ -225,7 +224,11 @@ export function DyadProButton({
);
}
export function AICreditStatus({ userBudget }: { userBudget: UserBudgetInfo }) {
export function AICreditStatus({
userBudget,
}: {
userBudget: NonNullable<UserBudgetInfo>;
}) {
const remaining = Math.round(
userBudget.totalCredits - userBudget.usedCredits,
);
......
import { atom } from "jotai";
import type { App, Version, ConsoleEntry } from "@/ipc/ipc_types";
import type { App, Version, ConsoleEntry } from "@/ipc/types";
import type { ListedApp } from "@/ipc/types/app";
import type { UserSettings } from "@/lib/schemas";
export const currentAppAtom = atom<App | null>(null);
export const selectedAppIdAtom = atom<number | null>(null);
export const appsListAtom = atom<App[]>([]);
export const appsListAtom = atom<ListedApp[]>([]);
export const versionsListAtom = atom<Version[]>([]);
export const previewModeAtom = atom<
"preview" | "code" | "problems" | "configure" | "publish" | "security"
......
import type { FileAttachment, Message, AgentTodo } from "@/ipc/ipc_types";
import type { FileAttachment, Message, AgentTodo } from "@/ipc/types";
import { atom } from "jotai";
// Per-chat atoms implemented with maps keyed by chatId
......
import { atom } from "jotai";
import { type LocalModel } from "@/ipc/ipc_types";
import { type LocalModel } from "@/ipc/types";
export const localModelsAtom = atom<LocalModel[]>([]);
export const localModelsLoadingAtom = atom<boolean>(false);
......
import { ComponentSelection, VisualEditingChange } from "@/ipc/ipc_types";
import { ComponentSelection, VisualEditingChange } from "@/ipc/types";
import { atom } from "jotai";
export const selectedComponentsPreviewAtom = atom<ComponentSelection[]>([]);
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { getAppPort } from "../../shared/ports";
import { v4 as uuidv4 } from "uuid";
......@@ -11,13 +11,13 @@ export async function neonTemplateHook({
appName: string;
}) {
console.log("Creating Neon project");
const neonProject = await IpcClient.getInstance().createNeonProject({
const neonProject = await ipc.neon.createProject({
name: appName,
appId: appId,
});
console.log("Neon project created", neonProject);
await IpcClient.getInstance().setAppEnvVars({
await ipc.misc.setAppEnvVars({
appId: appId,
envVars: [
{
......
......@@ -5,15 +5,12 @@ import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Loader2, Upload, X, Sparkles, Lock } from "lucide-react";
import { useGenerateThemePrompt } from "@/hooks/useCustomThemes";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { showError } from "@/lib/toast";
import { toast } from "sonner";
import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo";
import { AiAccessBanner } from "./ProBanner";
import type {
ThemeGenerationMode,
ThemeGenerationModel,
} from "@/ipc/ipc_types";
import type { ThemeGenerationMode, ThemeGenerationModel } from "@/ipc/types";
// Image upload constants
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB per image (raw file size)
......@@ -79,7 +76,7 @@ export function AIGeneratorTab({
const paths = images.map((img) => img.path);
if (paths.length > 0) {
try {
await IpcClient.getInstance().cleanupThemeImages({ paths });
await ipc.template.cleanupThemeImages({ paths });
} catch {
if (showErrors) {
showError("Failed to cleanup temporary image files");
......@@ -165,7 +162,7 @@ export function AIGeneratorTab({
});
// Save to temp file via IPC
const result = await IpcClient.getInstance().saveThemeImage({
const result = await ipc.template.saveThemeImage({
data: base64Data,
filename: file.name,
});
......
......@@ -2,9 +2,8 @@ import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Terminal } from "lucide-react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc, type AppUpgrade } from "@/ipc/types";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { AppUpgrade } from "@/ipc/ipc_types";
import { queryKeys } from "@/lib/queryKeys";
export function AppUpgrades({ appId }: { appId: number | null }) {
......@@ -20,7 +19,7 @@ export function AppUpgrades({ appId }: { appId: number | null }) {
if (!appId) {
return Promise.resolve([]);
}
return IpcClient.getInstance().getAppUpgrades({ appId });
return ipc.upgrade.getAppUpgrades({ appId });
},
enabled: !!appId,
});
......@@ -35,7 +34,7 @@ export function AppUpgrades({ appId }: { appId: number | null }) {
if (!appId) {
throw new Error("appId is not set");
}
return IpcClient.getInstance().executeAppUpgrade({
return ipc.upgrade.executeAppUpgrade({
appId,
upgradeId,
});
......@@ -132,7 +131,7 @@ export function AppUpgrades({ appId }: { appId: number | null }) {
<a
onClick={(e) => {
e.stopPropagation();
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
upgrade.manualUpgradeUrl ?? "https://dyad.sh/docs",
);
}}
......
......@@ -2,7 +2,7 @@ import { useSettings } from "@/hooks/useSettings";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { toast } from "sonner";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function AutoUpdateSwitch() {
const { settings, updateSettings } = useSettings();
......@@ -24,7 +24,7 @@ export function AutoUpdateSwitch() {
action: {
label: "Restart Dyad",
onClick: () => {
IpcClient.getInstance().restartDyad();
ipc.system.restartDyad();
},
},
});
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { Dialog, DialogTitle } from "@radix-ui/react-dialog";
import { DialogContent, DialogHeader } from "./ui/dialog";
import { Button } from "./ui/button";
......@@ -26,7 +26,7 @@ export function BugScreenshotDialog({
onClose();
setTimeout(async () => {
try {
await IpcClient.getInstance().takeScreenshot();
await ipc.system.takeScreenshot();
setIsScreenshotSuccessOpen(true);
} catch (error) {
setScreenshotError(
......
import { useState } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { showSuccess } from "@/lib/toast";
import {
Smartphone,
......@@ -44,7 +44,7 @@ export function CapacitorControls({ appId }: CapacitorControlsProps) {
// Check if Capacitor is installed
const { data: isCapacitor, isLoading } = useQuery({
queryKey: queryKeys.appUpgrades.isCapacitor({ appId }),
queryFn: () => IpcClient.getInstance().isCapacitor({ appId }),
queryFn: () => ipc.capacitor.isCapacitor({ appId }),
enabled: appId !== undefined && appId !== null,
});
......@@ -59,10 +59,10 @@ export function CapacitorControls({ appId }: CapacitorControlsProps) {
mutationFn: async () => {
setIosStatus("syncing");
// First sync
await IpcClient.getInstance().syncCapacitor({ appId });
await ipc.capacitor.syncCapacitor({ appId });
setIosStatus("opening");
// Then open iOS
await IpcClient.getInstance().openIos({ appId });
await ipc.capacitor.openIos({ appId });
},
onSuccess: () => {
setIosStatus("idle");
......@@ -79,10 +79,10 @@ export function CapacitorControls({ appId }: CapacitorControlsProps) {
mutationFn: async () => {
setAndroidStatus("syncing");
// First sync
await IpcClient.getInstance().syncCapacitor({ appId });
await ipc.capacitor.syncCapacitor({ appId });
setAndroidStatus("opening");
// Then open Android
await IpcClient.getInstance().openAndroid({ appId });
await ipc.capacitor.openAndroid({ appId });
},
onSuccess: () => {
setAndroidStatus("idle");
......@@ -136,7 +136,7 @@ export function CapacitorControls({ appId }: CapacitorControlsProps) {
size="sm"
onClick={() => {
// TODO: Add actual help link
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://dyad.sh/docs/guides/mobile-app#troubleshooting",
);
}}
......
......@@ -7,7 +7,7 @@ import { useAtom } from "jotai";
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { dropdownOpenAtom } from "@/atoms/uiAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { showError, showSuccess } from "@/lib/toast";
import { useSettings } from "@/hooks/useSettings";
import { getEffectiveDefaultChatMode } from "@/lib/schemas";
......@@ -88,7 +88,7 @@ export function ChatList({ show }: { show?: boolean }) {
if (selectedAppId) {
try {
// Create a new chat with an empty title for now
const chatId = await IpcClient.getInstance().createChat(selectedAppId);
const chatId = await ipc.chat.createChat(selectedAppId);
// Set the default chat mode for the new chat
if (settings) {
......@@ -117,7 +117,7 @@ export function ChatList({ show }: { show?: boolean }) {
const handleDeleteChat = async (chatId: number) => {
try {
await IpcClient.getInstance().deleteChat(chatId);
await ipc.chat.deleteChat(chatId);
showSuccess("Chat deleted successfully");
// If the deleted chat was selected, navigate to home
......
......@@ -5,7 +5,7 @@ import {
chatStreamCountByIdAtom,
isStreamingByIdAtom,
} from "../atoms/chatAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { ChatHeader } from "./chat/ChatHeader";
import { MessagesList } from "./chat/MessagesList";
......@@ -123,7 +123,7 @@ export function ChatPanel({
// no-op when no chat
return;
}
const chat = await IpcClient.getInstance().getChat(chatId);
const chat = await ipc.chat.getChat(chatId);
setMessagesById((prev) => {
const next = new Map(prev);
next.set(chatId, chat.messages);
......
......@@ -10,7 +10,7 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useMutation } from "@tanstack/react-query";
import { showError, showSuccess } from "@/lib/toast";
......@@ -33,8 +33,6 @@ export function CreateCustomModelDialog({
const [maxOutputTokens, setMaxOutputTokens] = useState<string>("");
const [contextWindow, setContextWindow] = useState<string>("");
const ipcClient = IpcClient.getInstance();
const mutation = useMutation({
mutationFn: async () => {
const params = {
......@@ -56,7 +54,14 @@ export function CreateCustomModelDialog({
if (contextWindow && isNaN(params.contextWindow ?? NaN))
throw new Error("Context Window must be a valid number");
await ipcClient.createCustomLanguageModel(params);
await ipc.languageModel.createCustomModel({
providerId: params.providerId,
displayName: params.displayName,
apiName: params.apiName,
description: params.description,
maxOutputTokens: params.maxOutputTokens,
contextWindow: params.contextWindow,
});
},
onSuccess: () => {
showSuccess("Custom model created successfully!");
......
......@@ -11,7 +11,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Loader2 } from "lucide-react";
import { useCustomLanguageModelProvider } from "@/hooks/useCustomLanguageModelProvider";
import type { LanguageModelProvider } from "@/ipc/ipc_types";
import type { LanguageModelProvider } from "@/ipc/types";
interface CreateCustomProviderDialogProps {
isOpen: boolean;
......
......@@ -10,7 +10,7 @@ import {
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useSettings } from "@/hooks/useSettings";
import { useMutation } from "@tanstack/react-query";
import { showError, showSuccess } from "@/lib/toast";
......@@ -47,8 +47,6 @@ export function EditCustomModelDialog({
const [contextWindow, setContextWindow] = useState<string>("");
const { settings, updateSettings } = useSettings();
const ipcClient = IpcClient.getInstance();
useEffect(() => {
if (model) {
setApiName(model.apiName);
......@@ -83,13 +81,20 @@ export function EditCustomModelDialog({
throw new Error("Context Window must be a valid number");
// First delete the old model
await ipcClient.deleteCustomModel({
await ipc.languageModel.deleteModel({
providerId,
modelApiName: model.apiName,
});
// Then create the new model
await ipcClient.createCustomLanguageModel(newParams);
await ipc.languageModel.createCustomModel({
providerId: newParams.providerId,
displayName: newParams.displayName,
apiName: newParams.apiName,
description: newParams.description,
maxOutputTokens: newParams.maxOutputTokens,
contextWindow: newParams.contextWindow,
});
},
onSuccess: async () => {
if (
......
......@@ -19,7 +19,7 @@ import {
import { Save, Edit2, Loader2 } from "lucide-react";
import { showError } from "@/lib/toast";
import { toast } from "sonner";
import type { CustomTheme } from "@/ipc/ipc_types";
import type { CustomTheme } from "@/ipc/types";
interface EditThemeDialogProps {
theme: CustomTheme;
......
......@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
import { LightbulbIcon } from "lucide-react";
import { ErrorComponentProps } from "@tanstack/react-router";
import { usePostHog } from "posthog-js/react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function ErrorBoundary({ error }: ErrorComponentProps) {
const [isLoading, setIsLoading] = useState(false);
......@@ -18,7 +18,7 @@ export function ErrorBoundary({ error }: ErrorComponentProps) {
setIsLoading(true);
try {
// Get system debug info
const debugInfo = await IpcClient.getInstance().getSystemDebugInfo();
const debugInfo = await ipc.system.getSystemDebugInfo();
// Create a formatted issue body with the debug info and error information
const issueBody = `
......@@ -62,13 +62,11 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
const githubIssueUrl = `https://github.com/dyad-sh/dyad/issues/new?title=${encodedTitle}&labels=bug,filed-from-app,client-error&body=${encodedBody}`;
// Open the pre-filled GitHub issue page
await IpcClient.getInstance().openExternalUrl(githubIssueUrl);
await ipc.system.openExternalUrl(githubIssueUrl);
} catch (err) {
console.error("Failed to prepare bug report:", err);
// Fallback to opening the regular GitHub issue page
IpcClient.getInstance().openExternalUrl(
"https://github.com/dyad-sh/dyad/issues/new",
);
ipc.system.openExternalUrl("https://github.com/dyad-sh/dyad/issues/new");
} finally {
setIsLoading(false);
}
......
......@@ -8,7 +8,7 @@ import {
ChevronRight,
GitMerge,
} from "lucide-react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc, type GithubSyncOptions } from "@/ipc/types";
import { useSettings } from "@/hooks/useSettings";
import { useLoadApp } from "@/hooks/useLoadApp";
import {
......@@ -29,7 +29,6 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { GithubBranchManager } from "@/components/GithubBranchManager";
import type { GithubSyncOptions } from "@/ipc/ipc_types";
type SyncResult =
| { error: Error; handled?: boolean }
......@@ -96,7 +95,7 @@ function ConnectedGitHubConnector({
setIsDisconnecting(true);
setDisconnectError(null);
try {
await IpcClient.getInstance().disconnectGithubRepo(appId);
await ipc.github.disconnect({ appId });
refreshApp();
} catch (err: any) {
setDisconnectError(err.message || "Failed to disconnect repository.");
......@@ -118,7 +117,8 @@ function ConnectedGitHubConnector({
setConflicts([]); // Clear conflicts when starting a new sync
try {
await IpcClient.getInstance().syncGithubRepo(appId, {
await ipc.github.push({
appId,
force,
forceWithLease,
});
......@@ -130,8 +130,7 @@ function ConnectedGitHubConnector({
} catch (err: any) {
if (err?.name === "GitConflictError") {
try {
const mergeConflicts =
await IpcClient.getInstance().getGithubMergeConflicts(appId);
const mergeConflicts = await ipc.github.getConflicts({ appId });
if (mergeConflicts.length > 0) {
setConflicts(mergeConflicts);
setSyncError(
......@@ -157,7 +156,7 @@ function ConnectedGitHubConnector({
let inferredRebaseInProgress = false;
if (!errorCode) {
try {
const state = await IpcClient.getInstance().getGithubState(appId);
const state = await ipc.github.getGitState({ appId });
inferredRebaseInProgress = state.rebaseInProgress;
} catch {
// ignore state inference errors
......@@ -192,7 +191,7 @@ function ConnectedGitHubConnector({
setRebaseStatusMessage(null);
setSyncSuccess(false);
try {
await IpcClient.getInstance().abortGithubRebase(appId);
await ipc.github.rebaseAbort({ appId });
setRebaseInProgress(false);
setRebaseStatusMessage("Rebase aborted. You can try syncing again.");
} catch (err: any) {
......@@ -209,7 +208,7 @@ function ConnectedGitHubConnector({
setRebaseStatusMessage(null);
setSyncSuccess(false);
try {
await IpcClient.getInstance().continueGithubRebase(appId);
await ipc.github.rebaseContinue({ appId });
setRebaseInProgress(false);
setRebaseStatusMessage("Rebase continued. You can sync when ready.");
} catch (err: any) {
......@@ -236,7 +235,7 @@ function ConnectedGitHubConnector({
setIsSyncing(true);
try {
// First, perform the rebase
await IpcClient.getInstance().rebaseGithubRepo(appId);
await ipc.github.rebase({ appId });
setRebaseStatusMessage(null);
const syncResult = await handleSyncToGithub();
if (syncResult?.error) {
......@@ -317,7 +316,7 @@ function ConnectedGitHubConnector({
<a
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
`https://github.com/${app.githubOrg}/${app.githubRepo}`,
);
}}
......@@ -379,7 +378,7 @@ function ConnectedGitHubConnector({
<a
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.dyad.sh/docs/integrations/github#troubleshooting",
);
}}
......@@ -591,15 +590,14 @@ export function UnconnectedGitHubConnector({
setGithubStatusMessage("Requesting device code from GitHub...");
// Send IPC message to main process to start the flow
IpcClient.getInstance().startGithubDeviceFlow(appId);
ipc.github.startFlow({ appId });
};
useEffect(() => {
const cleanupFunctions: (() => void)[] = [];
// Listener for updates (user code, verification uri, status messages)
const removeUpdateListener =
IpcClient.getInstance().onGithubDeviceFlowUpdate((data) => {
const removeUpdateListener = ipc.events.github.onFlowUpdate((data) => {
console.log("Received github:flow-update", data);
if (data.userCode) {
setGithubUserCode(data.userCode);
......@@ -623,8 +621,7 @@ export function UnconnectedGitHubConnector({
cleanupFunctions.push(removeUpdateListener);
// Listener for success
const removeSuccessListener =
IpcClient.getInstance().onGithubDeviceFlowSuccess((data) => {
const removeSuccessListener = ipc.events.github.onFlowSuccess((data) => {
console.log("Received github:flow-success", data);
setGithubStatusMessage("Successfully connected to GitHub!");
setGithubUserCode(null); // Clear user-facing info
......@@ -637,16 +634,14 @@ export function UnconnectedGitHubConnector({
cleanupFunctions.push(removeSuccessListener);
// Listener for errors
const removeErrorListener = IpcClient.getInstance().onGithubDeviceFlowError(
(data) => {
const removeErrorListener = ipc.events.github.onFlowError((data) => {
console.log("Received github:flow-error", data);
setGithubError(data.error || "An unknown error occurred.");
setGithubStatusMessage(null);
setGithubUserCode(null);
setGithubVerificationUri(null);
setIsConnectingToGithub(false);
},
);
});
cleanupFunctions.push(removeErrorListener);
// Cleanup function to remove all listeners when component unmounts or appId changes
......@@ -671,7 +666,7 @@ export function UnconnectedGitHubConnector({
const loadAvailableRepos = async () => {
setIsLoadingRepos(true);
try {
const repos = await IpcClient.getInstance().listGithubRepos();
const repos = await ipc.github.listRepos();
setAvailableRepos(repos);
} catch (error) {
console.error("Failed to load GitHub repos:", error);
......@@ -695,10 +690,7 @@ export function UnconnectedGitHubConnector({
setCustomBranchName(""); // Clear custom branch name
try {
const [owner, repo] = selectedRepo.split("/");
const branches = await IpcClient.getInstance().getGithubRepoBranches(
owner,
repo,
);
const branches = await ipc.github.getRepoBranches({ owner, repo });
setAvailableBranches(branches);
// Default to main if available, otherwise first branch
const defaultBranch =
......@@ -721,10 +713,10 @@ export function UnconnectedGitHubConnector({
if (!name) return;
setIsCheckingRepo(true);
try {
const result = await IpcClient.getInstance().checkGithubRepoAvailable(
githubOrg,
name,
);
const result = await ipc.github.isRepoAvailable({
org: githubOrg,
repo: name,
});
setRepoAvailable(result.available);
if (!result.available) {
setRepoCheckError(
......@@ -762,22 +754,22 @@ export function UnconnectedGitHubConnector({
try {
if (repoSetupMode === "create") {
await IpcClient.getInstance().createGithubRepo(
githubOrg,
repoName,
await ipc.github.createRepo({
org: githubOrg,
repo: repoName,
appId,
selectedBranch,
);
branch: selectedBranch,
});
} else {
const [owner, repo] = selectedRepo.split("/");
const branchToUse =
branchInputMode === "custom" ? customBranchName : selectedBranch;
await IpcClient.getInstance().connectToExistingGithubRepo(
await ipc.github.connectExistingRepo({
owner,
repo,
branchToUse,
branch: branchToUse,
appId,
);
});
}
setCreateRepoSuccess(true);
......@@ -846,9 +838,7 @@ export function UnconnectedGitHubConnector({
href={githubVerificationUri} // Make it a direct link
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
githubVerificationUri,
);
ipc.system.openExternalUrl(githubVerificationUri);
}}
target="_blank"
rel="noopener noreferrer"
......
......@@ -8,7 +8,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import {
ChevronsDownUp,
ChevronsUpDown,
......@@ -110,10 +110,8 @@ export function GithubBranchManager({
setIsLoading(true);
try {
const [localResult, remoteBranches] = await Promise.all([
IpcClient.getInstance().listLocalGithubBranches(appId),
IpcClient.getInstance()
.listRemoteGithubBranches(appId)
.catch(() => []),
ipc.github.listLocalBranches({ appId }),
ipc.github.listRemoteBranches({ appId }).catch(() => []),
]);
// Merge local and remote branches, removing duplicates
......@@ -137,11 +135,11 @@ export function GithubBranchManager({
setIsCreating(true);
const branchName = newBranchName.trim();
try {
await IpcClient.getInstance().createGithubBranch(
await ipc.github.createBranch({
appId,
branchName,
sourceBranch || undefined,
);
branch: branchName,
from: sourceBranch || undefined,
});
showSuccess(`Branch '${branchName}' created`);
setNewBranchName("");
setSourceBranch(""); // Reset source branch selection
......@@ -162,7 +160,7 @@ export function GithubBranchManager({
setIsSwitching(true);
try {
const switchBranch = async () =>
await IpcClient.getInstance().switchGithubBranch(appId, branch);
await ipc.github.switchBranch({ appId, branch });
try {
await switchBranch();
......@@ -181,7 +179,7 @@ export function GithubBranchManager({
| undefined;
if (!errorCode) {
try {
const state = await IpcClient.getInstance().getGithubState(appId);
const state = await ipc.github.getGitState({ appId });
if (state.rebaseInProgress) inferredCode = "REBASE_IN_PROGRESS";
else if (state.mergeInProgress) inferredCode = "MERGE_IN_PROGRESS";
} catch {
......@@ -197,8 +195,7 @@ export function GithubBranchManager({
// Check if there are unresolved conflicts
let hasConflicts = false;
try {
const conflicts =
await IpcClient.getInstance().getGithubMergeConflicts(appId);
const conflicts = await ipc.github.getConflicts({ appId });
hasConflicts = conflicts.length > 0;
} catch {
// If we can't get conflicts, assume there might be conflicts to be safe
......@@ -219,8 +216,7 @@ export function GithubBranchManager({
// Check if there are unresolved conflicts
let hasConflicts = false;
try {
const conflicts =
await IpcClient.getInstance().getGithubMergeConflicts(appId);
const conflicts = await ipc.github.getConflicts({ appId });
hasConflicts = conflicts.length > 0;
} catch {
// If we can't get conflicts, assume there might be conflicts to be safe
......@@ -255,14 +251,14 @@ export function GithubBranchManager({
try {
// Abort the operation - both methods throw on error
if (operationType === "rebase") {
await IpcClient.getInstance().abortGithubRebase(appId);
await ipc.github.rebaseAbort({ appId });
} else {
await IpcClient.getInstance().abortGithubMerge(appId);
await ipc.github.mergeAbort({ appId });
}
// Now switch to the target branch
try {
await IpcClient.getInstance().switchGithubBranch(appId, targetBranch);
await ipc.github.switchBranch({ appId, branch: targetBranch });
showSuccess(
`Aborted ongoing ${operationType} and switched to branch '${targetBranch}'`,
);
......@@ -291,7 +287,7 @@ export function GithubBranchManager({
setIsDeleting(true);
try {
await IpcClient.getInstance().deleteGithubBranch(appId, branchToDelete);
await ipc.github.deleteBranch({ appId, branch: branchToDelete });
showSuccess(`Branch '${branchToDelete}' deleted`);
setBranchToDelete(null);
await loadBranches();
......@@ -307,11 +303,11 @@ export function GithubBranchManager({
setIsRenaming(true);
try {
const trimmedNewName = renameBranchName.trim();
await IpcClient.getInstance().renameGithubBranch(
await ipc.github.renameBranch({
appId,
branchToRename,
trimmedNewName,
);
oldBranch: branchToRename,
newBranch: trimmedNewName,
});
showSuccess(`Renamed '${branchToRename}' to '${trimmedNewName}'`);
setBranchToRename(null);
setRenameBranchName("");
......@@ -328,7 +324,7 @@ export function GithubBranchManager({
setIsMerging(true);
setConflicts([]); // Clear conflicts when starting a new merge operation
try {
await IpcClient.getInstance().mergeGithubBranch(appId, branchToMerge);
await ipc.github.mergeBranch({ appId, branch: branchToMerge });
showSuccess(`Merged '${branchToMerge}' into '${currentBranch}'`);
setConflicts([]); // Clear conflicts on successful merge
setBranchToMerge(null);
......@@ -343,8 +339,7 @@ export function GithubBranchManager({
showInfo("Merge conflict detected. Please resolve them in the editor.");
// Show conflicts dialog
try {
const conflicts =
await IpcClient.getInstance().getGithubMergeConflicts(appId);
const conflicts = await ipc.github.getConflicts({ appId });
if (conflicts.length > 0) {
setConflicts(conflicts);
......
......@@ -9,7 +9,7 @@ import {
CardTitle,
} from "@/components/ui/card";
import { SimpleAvatar } from "@/components/ui/SimpleAvatar";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import {
Trash2,
UserPlus,
......@@ -32,7 +32,7 @@ import {
interface Collaborator {
login: string;
avatar_url: string;
permissions: {
permissions?: {
admin: boolean;
push: boolean;
pull: boolean;
......@@ -56,7 +56,7 @@ export function GithubCollaboratorManager({ appId }: CollaboratorManagerProps) {
const loadCollaborators = useCallback(async () => {
setIsLoading(true);
try {
const collabs = await IpcClient.getInstance().listCollaborators(appId);
const collabs = await ipc.github.listCollaborators({ appId });
setCollaborators(collabs);
} catch (error: any) {
console.error("Failed to load collaborators:", error);
......@@ -78,7 +78,7 @@ export function GithubCollaboratorManager({ appId }: CollaboratorManagerProps) {
setIsInviting(true);
try {
await IpcClient.getInstance().inviteCollaborator(appId, trimmedUsername);
await ipc.github.inviteCollaborator({ appId, username: trimmedUsername });
showSuccess(`Invited ${trimmedUsername} to the project.`);
setInviteUsername("");
// Reload list (though they might be pending)
......@@ -94,10 +94,10 @@ export function GithubCollaboratorManager({ appId }: CollaboratorManagerProps) {
if (!collaboratorToDelete) return;
try {
await IpcClient.getInstance().removeCollaborator(
await ipc.github.removeCollaborator({
appId,
collaboratorToDelete,
);
username: collaboratorToDelete,
});
showSuccess(`Removed ${collaboratorToDelete} from the project.`);
loadCollaborators();
} catch (error: any) {
......@@ -193,9 +193,9 @@ export function GithubCollaboratorManager({ appId }: CollaboratorManagerProps) {
<div>
<p className="text-sm font-medium">{collab.login}</p>
<p className="text-xs text-gray-500">
{collab.permissions.admin
{collab.permissions?.admin
? "Admin"
: collab.permissions.push
: collab.permissions?.push
? "Editor"
: "Viewer"}
</p>
......
......@@ -6,7 +6,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { v4 as uuidv4 } from "uuid";
import { LoadingBlock, VanillaMarkdownParser } from "@/components/LoadingBlock";
......@@ -75,10 +75,12 @@ export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) {
setInput("");
setStreaming(true);
IpcClient.getInstance().startHelpChat(sessionId, trimmed, {
onChunk: (delta) => {
ipc.helpStream.start(
{ sessionId, message: trimmed },
{
onChunk: (data) => {
// Buffer assistant content; UI will flush on interval for smoothness
assistantBufferRef.current += delta;
assistantBufferRef.current += data.delta;
},
onEnd: () => {
// Final flush then stop streaming
......@@ -100,8 +102,8 @@ export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) {
flushTimerRef.current = null;
}
},
onError: (errorMessage: string) => {
setError(errorMessage);
onError: (data) => {
setError(data.error);
setStreaming(false);
// Clear the flush timer
......@@ -127,7 +129,8 @@ export function HelpBotDialog({ isOpen, onClose }: HelpBotDialogProps) {
return next;
});
},
});
},
);
// Start smooth flush interval
if (flushTimerRef.current) {
......
......@@ -17,11 +17,11 @@ import {
FileIcon,
SparklesIcon,
} from "lucide-react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useState, useEffect } from "react";
import { useAtomValue } from "jotai";
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { ChatLogsData } from "@/ipc/ipc_types";
import { ChatLogsData } from "@/ipc/types";
import { showError } from "@/lib/toast";
import { HelpBotDialog } from "./HelpBotDialog";
import { useSettings } from "@/hooks/useSettings";
......@@ -73,7 +73,7 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
setIsLoading(true);
try {
// Get system debug info
const debugInfo = await IpcClient.getInstance().getSystemDebugInfo();
const debugInfo = await ipc.system.getSystemDebugInfo();
// Create a formatted issue body with the debug info
const issueBody = `
......@@ -112,13 +112,11 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
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);
ipc.system.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",
);
ipc.system.openExternalUrl("https://github.com/dyad-sh/dyad/issues/new");
} finally {
setIsLoading(false);
}
......@@ -133,8 +131,7 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
setIsUploading(true);
try {
// Get chat logs (includes debug info, chat data, and codebase)
const chatLogs =
await IpcClient.getInstance().getChatLogs(selectedChatId);
const chatLogs = await ipc.misc.getChatLogs(selectedChatId);
// Store data for review and switch to review mode
setChatLogsData(chatLogs);
......@@ -183,11 +180,11 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
const { uploadUrl, filename } = await response.json();
await IpcClient.getInstance().uploadToSignedUrl(
uploadUrl,
"application/json",
chatLogsJson,
);
await ipc.system.uploadToSignedUrl({
url: uploadUrl,
contentType: "application/json",
data: chatLogsJson,
});
// Extract session ID (filename without extension)
const sessionId = filename.replace(".json", "");
......@@ -233,7 +230,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}
}
const githubIssueUrl = `https://github.com/dyad-sh/dyad/issues/new?title=${encodedTitle}&labels=${labels}&body=${encodedBody}`;
IpcClient.getInstance().openExternalUrl(githubIssueUrl);
ipc.system.openExternalUrl(githubIssueUrl);
handleClose();
};
......@@ -403,9 +400,7 @@ Pro User ID: ${userBudget?.redactedUserId || "n/a"}
<Button
variant="outline"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://www.dyad.sh/docs",
);
ipc.system.openExternalUrl("https://www.dyad.sh/docs");
}}
className="w-full py-6 bg-(--background-lightest)"
>
......
......@@ -8,7 +8,7 @@ import {
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useMutation } from "@tanstack/react-query";
import { showError, showSuccess } from "@/lib/toast";
import { Folder, X, Loader2, Info } from "lucide-react";
......@@ -19,7 +19,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@radix-ui/react-label";
import { useNavigate } from "@tanstack/react-router";
import { useStreamChat } from "@/hooks/useStreamChat";
import type { GithubRepository } from "@/ipc/ipc_types";
import type { GithubRepository } from "@/ipc/types";
import {
Tooltip,
TooltipContent,
......@@ -90,7 +90,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
const fetchRepos = async () => {
setLoading(true);
try {
const fetchedRepos = await IpcClient.getInstance().listGithubRepos();
const fetchedRepos = await ipc.github.listRepos();
setRepos(fetchedRepos);
} catch (err: unknown) {
showError("Failed to fetch repositories.: " + (err as any).toString());
......@@ -105,7 +105,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
setGithubAppName(repoName);
setIsCheckingGithubName(true);
try {
const result = await IpcClient.getInstance().checkAppName({
const result = await ipc.import.checkAppName({
appName: repoName,
});
setGithubNameExists(result.exists);
......@@ -126,7 +126,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
const match = extractRepoNameFromUrl(url);
const repoName = match ? match[2] : "";
const appName = githubAppName.trim() || repoName;
const result = await IpcClient.getInstance().cloneRepoFromUrl({
const result = await ipc.github.cloneRepoFromUrl({
url,
installCommand: installCommand.trim() || undefined,
startCommand: startCommand.trim() || undefined,
......@@ -139,7 +139,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
}
setSelectedAppId(result.app.id);
showSuccess(`Successfully imported ${result.app.name}`);
const chatId = await IpcClient.getInstance().createChat(result.app.id);
const chatId = await ipc.chat.createChat(result.app.id);
navigate({ to: "/chat", search: { id: chatId } });
if (!result.hasAiRules) {
streamMessage({
......@@ -160,7 +160,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
try {
const appName = githubAppName.trim() || repo.name;
const result = await IpcClient.getInstance().cloneRepoFromUrl({
const result = await ipc.github.cloneRepoFromUrl({
url: `https://github.com/${repo.full_name}.git`,
installCommand: installCommand.trim() || undefined,
startCommand: startCommand.trim() || undefined,
......@@ -173,7 +173,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
}
setSelectedAppId(result.app.id);
showSuccess(`Successfully imported ${result.app.name}`);
const chatId = await IpcClient.getInstance().createChat(result.app.id);
const chatId = await ipc.chat.createChat(result.app.id);
navigate({ to: "/chat", search: { id: chatId } });
if (!result.hasAiRules) {
streamMessage({
......@@ -197,7 +197,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
if (newName.trim()) {
setIsCheckingGithubName(true);
try {
const result = await IpcClient.getInstance().checkAppName({
const result = await ipc.import.checkAppName({
appName: newName,
});
setGithubNameExists(result.exists);
......@@ -218,7 +218,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
}): Promise<void> => {
setIsCheckingName(true);
try {
const result = await IpcClient.getInstance().checkAppName({
const result = await ipc.import.checkAppName({
appName: name,
skipCopy,
});
......@@ -231,12 +231,12 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
};
const selectFolderMutation = useMutation({
mutationFn: async () => {
const result = await IpcClient.getInstance().selectAppFolder();
const result = await ipc.system.selectAppFolder();
if (!result.path || !result.name) {
// User cancelled the folder selection dialog
return null;
}
const aiRulesCheck = await IpcClient.getInstance().checkAiRules({
const aiRulesCheck = await ipc.import.checkAiRules({
path: result.path,
});
setHasAiRules(aiRulesCheck.exists);
......@@ -255,7 +255,7 @@ export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) {
const importAppMutation = useMutation({
mutationFn: async () => {
if (!selectedPath) throw new Error("No folder selected");
return IpcClient.getInstance().importApp({
return ipc.import.importApp({
path: selectedPath,
appName: customAppName,
installCommand: installCommand || undefined,
......
import { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
const customLink = ({
node: _node,
......@@ -15,7 +15,7 @@ const customLink = ({
const url = props.href;
if (url) {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(url);
ipc.system.openExternalUrl(url);
}
}}
/>
......
......@@ -21,7 +21,7 @@ import { useLocalModels } from "@/hooks/useLocalModels";
import { useLocalLMSModels } from "@/hooks/useLMStudioModels";
import { useLanguageModelsByProviders } from "@/hooks/useLanguageModelsByProviders";
import { LocalModel } from "@/ipc/ipc_types";
import { LocalModel } from "@/ipc/types";
import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders";
import { useSettings } from "@/hooks/useSettings";
import { PriceBadge } from "@/components/PriceBadge";
......
import { useEffect } from "react";
import { Button } from "@/components/ui/button";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { toast } from "sonner";
import { useSettings } from "@/hooks/useSettings";
......@@ -34,9 +34,7 @@ export function NeonConnector() {
<Button
variant="outline"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://console.neon.tech/",
);
ipc.system.openExternalUrl("https://console.neon.tech/");
}}
className="ml-2 px-2 py-1 h-8 mb-2"
style={{ display: "inline-flex", alignItems: "center" }}
......@@ -67,9 +65,9 @@ export function NeonConnector() {
<div
onClick={async () => {
if (settings?.isTestMode) {
await IpcClient.getInstance().fakeHandleNeonConnect();
await ipc.neon.fakeConnect();
} else {
await IpcClient.getInstance().openExternalUrl(
await ipc.system.openExternalUrl(
"https://oauth.dyad.sh/api/integrations/neon/login",
);
}
......
......@@ -3,7 +3,7 @@ import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { useSettings } from "@/hooks/useSettings";
import { showError, showSuccess } from "@/lib/toast";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { FolderOpen, RotateCcw, CheckCircle, AlertCircle } from "lucide-react";
export function NodePathSelector() {
......@@ -26,7 +26,7 @@ export function NodePathSelector() {
const fetchSystemPath = async () => {
try {
const debugInfo = await IpcClient.getInstance().getSystemDebugInfo();
const debugInfo = await ipc.system.getSystemDebugInfo();
setSystemPath(debugInfo.nodePath || "System PATH (not available)");
} catch (err) {
console.error("Failed to fetch system path:", err);
......@@ -43,7 +43,7 @@ export function NodePathSelector() {
if (!settings) return;
setIsCheckingNode(true);
try {
const status = await IpcClient.getInstance().getNodejsStatus();
const status = await ipc.system.getNodejsStatus();
setNodeStatus({
version: status.nodeVersion,
isValid: !!status.nodeVersion,
......@@ -59,12 +59,12 @@ export function NodePathSelector() {
setIsSelectingPath(true);
try {
// Call the IPC method to select folder
const result = await IpcClient.getInstance().selectNodeFolder();
const result = await ipc.system.selectNodeFolder();
if (result.path) {
// Save the custom path to settings
await updateSettings({ customNodePath: result.path });
// Update the environment PATH
await IpcClient.getInstance().reloadEnvPath();
await ipc.system.reloadEnvPath();
// Recheck Node.js status
await checkNodeStatus();
showSuccess("Node.js path updated successfully");
......@@ -84,7 +84,7 @@ export function NodePathSelector() {
// Clear the custom path
await updateSettings({ customNodePath: null });
// Reload environment to use system PATH
await IpcClient.getInstance().reloadEnvPath();
await ipc.system.reloadEnvPath();
// Recheck Node.js status
await fetchSystemPath();
await checkNodeStatus();
......
import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ExternalLink, Database, Loader2 } from "lucide-react";
......@@ -17,8 +17,7 @@ export const PortalMigrate = ({ appId }: PortalMigrateProps) => {
const migrateMutation = useMutation({
mutationFn: async () => {
const ipcClient = IpcClient.getInstance();
return ipcClient.portalMigrateCreate({ appId });
return ipc.misc.portalMigrateCreate({ appId });
},
onSuccess: (result) => {
setOutput(result.output);
......@@ -41,8 +40,7 @@ export const PortalMigrate = ({ appId }: PortalMigrateProps) => {
};
const openDocs = () => {
const ipcClient = IpcClient.getInstance();
ipcClient.openExternalUrl(
ipc.system.openExternalUrl(
"https://www.dyad.sh/docs/templates/portal#create-a-database-migration",
);
};
......
......@@ -4,7 +4,7 @@ import openAiLogo from "../../assets/ai-logos/openai-logo.svg";
import googleLogo from "../../assets/ai-logos/google-logo.svg";
// @ts-ignore
import anthropicLogo from "../../assets/ai-logos/anthropic-logo.svg";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useState } from "react";
import { ArrowUpRight, KeyRound, Wallet } from "lucide-react";
......@@ -48,9 +48,7 @@ export function ManageDyadProButton({ className }: { className?: string }) {
className,
)}
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://academy.dyad.sh/subscription",
);
ipc.system.openExternalUrl("https://academy.dyad.sh/subscription");
}}
>
<Wallet aria-hidden="true" className="w-5 h-5" />
......@@ -67,9 +65,7 @@ export function SetupDyadProButton() {
size="lg"
className="cursor-pointer w-full mt-4 bg-(--background-lighter) text-primary"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://academy.dyad.sh/settings",
);
ipc.system.openExternalUrl("https://academy.dyad.sh/settings");
}}
>
<KeyRound aria-hidden="true" />
......@@ -83,7 +79,7 @@ export function AiAccessBanner() {
<div
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-white via-indigo-50 to-sky-100 dark:from-indigo-700 dark:via-indigo-700 dark:to-indigo-900 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-black/5 dark:ring-white/10 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-ai-access",
);
}}
......@@ -149,7 +145,7 @@ export function SmartContextBanner() {
<div
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-emerald-50 via-emerald-100 to-emerald-200 dark:from-emerald-700 dark:via-emerald-700 dark:to-emerald-900 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-emerald-900/10 dark:ring-white/10 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-smart-context",
);
}}
......@@ -190,7 +186,7 @@ export function TurboBanner() {
<div
className="w-full py-2 sm:py-2.5 md:py-3 rounded-lg bg-gradient-to-br from-rose-50 via-rose-100 to-rose-200 dark:from-rose-800 dark:via-fuchsia-800 dark:to-rose-800 flex items-center justify-center relative overflow-hidden ring-1 ring-inset ring-rose-900/10 dark:ring-white/5 shadow-sm cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-[1px]"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=in-app-banner-turbo",
);
}}
......
......@@ -13,7 +13,7 @@ import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { Sparkles, Info } from "lucide-react";
import { useSettings } from "@/hooks/useSettings";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { hasDyadProKey, type UserSettings } from "@/lib/schemas";
export function ProModeSelector() {
......@@ -93,9 +93,7 @@ export function ProModeSelector() {
<a
className="inline-flex items-center justify-center gap-2 rounded-md border border-primary/30 bg-primary/10 px-3 py-2 text-sm font-medium text-primary shadow-sm transition-colors hover:bg-primary/20 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-pointer"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://dyad.sh/pro#ai",
);
ipc.system.openExternalUrl("https://dyad.sh/pro#ai");
}}
>
Unlock Pro modes
......
......@@ -6,7 +6,7 @@ import {
} from "@/components/ui/card";
import { useNavigate } from "@tanstack/react-router";
import { providerSettingsRoute } from "@/routes/settings/providers/$provider";
import type { LanguageModelProvider } from "@/ipc/ipc_types";
import type { LanguageModelProvider } from "@/ipc/types";
import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders";
import { useCustomLanguageModelProvider } from "@/hooks/useCustomLanguageModelProvider";
......
......@@ -8,7 +8,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { toast } from "sonner";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import type { ReleaseChannel } from "@/lib/schemas";
export function ReleaseChannelSelector() {
......@@ -27,7 +27,7 @@ export function ReleaseChannelSelector() {
action: {
label: "Download Stable",
onClick: () => {
IpcClient.getInstance().openExternalUrl("https://dyad.sh/download");
ipc.system.openExternalUrl("https://dyad.sh/download");
},
},
});
......@@ -38,7 +38,7 @@ export function ReleaseChannelSelector() {
action: {
label: "Restart Dyad",
onClick: () => {
IpcClient.getInstance().restartDyad();
ipc.system.restartDyad();
},
},
});
......
......@@ -8,7 +8,7 @@ import {
} from "@/components/ui/select";
import { useSettings } from "@/hooks/useSettings";
import { showError } from "@/lib/toast";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function RuntimeModeSelector() {
const { settings, updateSettings } = useSettings();
......@@ -59,7 +59,7 @@ export function RuntimeModeSelector() {
type="button"
className="underline font-medium cursor-pointer"
onClick={() =>
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.docker.com/products/docker-desktop/",
)
}
......
......@@ -15,7 +15,7 @@ import { providerSettingsRoute } from "@/routes/settings/providers/$provider";
import SetupProviderCard from "@/components/SetupProviderCard";
import { useState, useEffect, useCallback } from "react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc, NodeSystemInfo } from "@/ipc/types";
import {
Accordion,
AccordionContent,
......@@ -24,7 +24,6 @@ import {
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { NodeSystemInfo } from "@/ipc/ipc_types";
import { usePostHog } from "posthog-js/react";
import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders";
import { useScrollAndNavigateTo } from "@/hooks/useScrollAndNavigateTo";
......@@ -55,7 +54,7 @@ export function SetupBanner() {
const checkNode = useCallback(async () => {
try {
setNodeCheckError(false);
const status = await IpcClient.getInstance().getNodejsStatus();
const status = await ipc.system.getNodejsStatus();
setNodeSystemInfo(status);
} catch (error) {
console.error("Failed to check Node.js status:", error);
......@@ -71,10 +70,10 @@ export function SetupBanner() {
const handleManualNodeConfig = useCallback(async () => {
setIsSelectingPath(true);
try {
const result = await IpcClient.getInstance().selectNodeFolder();
const result = await ipc.system.selectNodeFolder();
if (result.path) {
await updateSettings({ customNodePath: result.path });
await IpcClient.getInstance().reloadEnvPath();
await ipc.system.reloadEnvPath();
await checkNode();
setNodeInstallStep("finished-checking");
setShowManualConfig(false);
......@@ -116,7 +115,7 @@ export function SetupBanner() {
};
const handleDyadProSetupClick = () => {
posthog.capture("setup-flow:ai-provider-setup:dyad:click");
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.dyad.sh/pro?utm_source=dyad-app&utm_medium=app&utm_campaign=setup-banner",
);
};
......@@ -129,13 +128,13 @@ export function SetupBanner() {
const handleNodeInstallClick = useCallback(async () => {
posthog.capture("setup-flow:start-node-install-click");
setNodeInstallStep("waiting-for-continue");
IpcClient.getInstance().openExternalUrl(nodeSystemInfo!.nodeDownloadUrl);
ipc.system.openExternalUrl(nodeSystemInfo!.nodeDownloadUrl);
}, [nodeSystemInfo, setNodeInstallStep]);
const finishNodeInstall = useCallback(async () => {
posthog.capture("setup-flow:continue-node-install-click");
setNodeInstallStep("continue-processing");
await IpcClient.getInstance().reloadEnvPath();
await ipc.system.reloadEnvPath();
await checkNode();
setNodeInstallStep("finished-checking");
}, [checkNode, setNodeInstallStep]);
......@@ -236,7 +235,7 @@ export function SetupBanner() {
<a
className="text-blue-500 dark:text-blue-400 hover:underline"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://nodejs.org/en/download",
);
}}
......@@ -392,9 +391,7 @@ function NodeJsHelpCallout() {
If you run into issues, read our{" "}
<a
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://www.dyad.sh/docs/help/nodejs",
);
ipc.system.openExternalUrl("https://www.dyad.sh/docs/help/nodejs");
}}
className="text-blue-600 dark:text-blue-400 hover:underline font-medium"
>
......
......@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc, type SupabaseProject } from "@/ipc/types";
import { toast } from "sonner";
import { useSettings } from "@/hooks/useSettings";
import { useSupabase } from "@/hooks/useSupabase";
......@@ -39,7 +39,6 @@ import connectSupabaseLight from "../../assets/supabase/connect-supabase-light.s
import { ExternalLink, Plus, RefreshCw, Trash2 } from "lucide-react";
import { useTheme } from "@/contexts/ThemeContext";
import type { SupabaseProject } from "@/ipc/ipc_types";
import { isSupabaseConnected } from "@/lib/schemas";
export function SupabaseConnector({ appId }: { appId: number }) {
......@@ -132,12 +131,12 @@ export function SupabaseConnector({ appId }: { appId: number }) {
const handleAddAccount = async () => {
if (settings?.isTestMode) {
await IpcClient.getInstance().fakeHandleSupabaseConnect({
await ipc.supabase.fakeConnectAndSetProject({
appId,
fakeProjectId: "fake-project-id",
});
} else {
await IpcClient.getInstance().openExternalUrl(
await ipc.system.openExternalUrl(
"https://supabase-oauth.dyad.sh/api/connect-supabase/login",
);
}
......@@ -173,7 +172,7 @@ export function SupabaseConnector({ appId }: { appId: number }) {
<Button
variant="outline"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
`https://supabase.com/dashboard/project/${app.supabaseProjectId}`,
);
}}
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import React from "react";
import { Button } from "./ui/button";
import { atom, useAtom } from "jotai";
......@@ -32,7 +32,7 @@ export function PrivacyBanner() {
</em>
<a
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://dyad.sh/docs/policies/privacy-policy",
);
}}
......
import React, { useState } from "react";
import { ArrowLeft } from "lucide-react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useSettings } from "@/hooks/useSettings";
import { CommunityCodeConsentDialog } from "./CommunityCodeConsentDialog";
import type { Template } from "@/shared/templates";
......@@ -59,7 +59,7 @@ export const TemplateCard: React.FC<TemplateCardProps> = ({
const handleGithubClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (template.githubUrl) {
IpcClient.getInstance().openExternalUrl(template.githubUrl);
ipc.system.openExternalUrl(template.githubUrl);
}
};
......
import { useState, useEffect, useCallback, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Globe } from "lucide-react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc, App } from "@/ipc/types";
import { useSettings } from "@/hooks/useSettings";
import { useLoadApp } from "@/hooks/useLoadApp";
import { useVercelDeployments } from "@/hooks/useVercelDeployments";
......@@ -15,7 +15,6 @@ import {
import {} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { App } from "@/ipc/ipc_types";
interface VercelConnectorProps {
appId: number | null;
......@@ -25,7 +24,7 @@ interface VercelConnectorProps {
interface VercelProject {
id: string;
name: string;
framework: string | null;
framework?: string | null;
}
interface ConnectedVercelConnectorProps {
......@@ -88,7 +87,7 @@ function ConnectedVercelConnector({
<a
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
`https://vercel.com/${app.vercelTeamSlug}/${app.vercelProjectName}`,
);
}}
......@@ -106,9 +105,7 @@ function ConnectedVercelConnector({
onClick={(e) => {
e.preventDefault();
if (app.vercelDeploymentUrl) {
IpcClient.getInstance().openExternalUrl(
app.vercelDeploymentUrl,
);
ipc.system.openExternalUrl(app.vercelDeploymentUrl);
}
}}
className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400 font-mono"
......@@ -193,9 +190,7 @@ function ConnectedVercelConnector({
<a
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
`https://${deployment.url}`,
);
ipc.system.openExternalUrl(`https://${deployment.url}`);
}}
className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400 text-sm"
target="_blank"
......@@ -277,7 +272,7 @@ function UnconnectedVercelConnector({
const loadAvailableProjects = async () => {
setIsLoadingProjects(true);
try {
const projects = await IpcClient.getInstance().listVercelProjects();
const projects = await ipc.vercel.listProjects();
setAvailableProjects(projects);
} catch (error) {
console.error("Failed to load Vercel projects:", error);
......@@ -295,7 +290,7 @@ function UnconnectedVercelConnector({
setTokenSuccess(false);
try {
await IpcClient.getInstance().saveVercelAccessToken({
await ipc.vercel.saveToken({
token: accessToken.trim(),
});
setTokenSuccess(true);
......@@ -314,7 +309,7 @@ function UnconnectedVercelConnector({
if (!name) return;
setIsCheckingProject(true);
try {
const result = await IpcClient.getInstance().isVercelProjectAvailable({
const result = await ipc.vercel.isProjectAvailable({
name,
});
setProjectAvailable(result.available);
......@@ -352,12 +347,12 @@ function UnconnectedVercelConnector({
try {
if (projectSetupMode === "create") {
await IpcClient.getInstance().createVercelProject({
await ipc.vercel.createProject({
name: projectName,
appId,
});
} else {
await IpcClient.getInstance().connectToExistingVercelProject({
await ipc.vercel.connectExistingProject({
projectId: selectedProject,
appId,
});
......@@ -398,9 +393,7 @@ function UnconnectedVercelConnector({
<div className="flex gap-2 mt-3">
<Button
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://vercel.com/signup",
);
ipc.system.openExternalUrl("https://vercel.com/signup");
}}
variant="outline"
className="flex-1"
......@@ -409,7 +402,7 @@ function UnconnectedVercelConnector({
</Button>
<Button
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://vercel.com/account/settings/tokens",
);
}}
......
......@@ -2,7 +2,7 @@ import { formatDistanceToNow } from "date-fns";
import { Star } from "lucide-react";
import { SidebarMenuItem } from "@/components/ui/sidebar";
import { Button } from "@/components/ui/button";
import { App } from "@/ipc/ipc_types";
import type { ListedApp } from "@/ipc/types/app";
import {
Tooltip,
TooltipContent,
......@@ -11,7 +11,7 @@ import {
} from "@/components/ui/tooltip";
type AppItemProps = {
app: App;
app: ListedApp;
handleAppClick: (id: number) => void;
selectedAppId: number | null;
handleToggleFavorite: (appId: number, e: React.MouseEvent) => void;
......
import { FileText, X, MessageSquare, Upload } from "lucide-react";
import type { FileAttachment } from "@/ipc/ipc_types";
import type { FileAttachment } from "@/ipc/types";
interface AttachmentsListProps {
attachments: FileAttachment[];
......
......@@ -39,7 +39,7 @@ import { useThemes } from "@/hooks/useThemes";
import { useAppTheme } from "@/hooks/useAppTheme";
import { useCustomThemes } from "@/hooks/useCustomThemes";
import { useSettings } from "@/hooks/useSettings";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/queryKeys";
......@@ -104,7 +104,7 @@ export function AuxiliaryActionsMenu({
const handleThemeSelect = async (themeId: string | null) => {
if (appId != null) {
// Update app-specific theme
await IpcClient.getInstance().setAppTheme({
await ipc.template.setAppTheme({
appId,
themeId,
});
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { AI_STREAMING_ERROR_MESSAGE_PREFIX } from "@/shared/texts";
import {
X,
......@@ -156,7 +156,7 @@ function ExternalLink({
return (
<a
className={`${baseClasses} ${variant === "primary" ? primaryClasses : secondaryClasses}`}
onClick={() => IpcClient.getInstance().openExternalUrl(href)}
onClick={() => ipc.system.openExternalUrl(href)}
>
<span>{children}</span>
{iconElement}
......@@ -191,7 +191,7 @@ function ChatErrorContainer({
onClick={(e) => {
e.preventDefault();
if (props.href) {
IpcClient.getInstance().openExternalUrl(props.href);
ipc.system.openExternalUrl(props.href);
}
}}
className="text-blue-500 hover:text-blue-700"
......
......@@ -16,7 +16,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useRouter } from "@tanstack/react-router";
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { useChats } from "@/hooks/useChats";
......@@ -84,7 +84,7 @@ export function ChatHeader({
const handleNewChat = async () => {
if (appId) {
try {
const chatId = await IpcClient.getInstance().createChat(appId);
const chatId = await ipc.chat.createChat(appId);
setSelectedChatId(chatId);
navigate({
to: "/chat",
......
......@@ -21,7 +21,7 @@ import type React from "react";
import { useCallback, useEffect, useState } from "react";
import { useSettings } from "@/hooks/useSettings";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import {
chatInputValueAtom,
chatMessagesByIdAtom,
......@@ -173,7 +173,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
if (!chatId) {
return;
}
const chat = await IpcClient.getInstance().getChat(chatId);
const chat = await ipc.chat.getChat(chatId);
setMessagesById((prev) => {
const next = new Map(prev);
next.set(chatId, chat.messages);
......@@ -222,7 +222,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
const handleCancel = () => {
if (chatId) {
IpcClient.getInstance().cancelChatStream(chatId);
ipc.chat.cancelStream(chatId);
}
setIsStreaming(false);
};
......@@ -240,7 +240,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
setIsApproving(true);
posthog.capture("chat:approve");
try {
const result = await IpcClient.getInstance().approveProposal({
const result = await ipc.proposal.approveProposal({
chatId,
messageId,
});
......@@ -277,7 +277,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
setIsRejecting(true);
posthog.capture("chat:reject");
try {
await IpcClient.getInstance().rejectProposal({
await ipc.proposal.rejectProposal({
chatId,
messageId,
});
......@@ -333,7 +333,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
consent={pendingAgentConsent}
queueTotal={consentsForThisChat.length}
onDecision={(decision) => {
IpcClient.getInstance().respondToAgentConsentRequest({
ipc.agent.respondToConsent({
requestId: pendingAgentConsent.requestId,
decision,
});
......@@ -345,7 +345,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
);
}}
onClose={() => {
IpcClient.getInstance().respondToAgentConsentRequest({
ipc.agent.respondToConsent({
requestId: pendingAgentConsent.requestId,
decision: "decline",
});
......@@ -413,9 +413,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
<TooltipTrigger asChild>
<button
onClick={() => {
IpcClient.getInstance().openExternalUrl(
"https://dyad.sh/pro",
);
ipc.system.openExternalUrl("https://dyad.sh/pro");
}}
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-primary transition-colors cursor-pointer"
>
......@@ -873,7 +871,7 @@ function ChatInputActions({
key={index}
className="flex items-center space-x-2"
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
`https://www.npmjs.com/package/${pkg}`,
);
}}
......
import type { Message } from "@/ipc/ipc_types";
import type { Message } from "@/ipc/types";
import {
DyadMarkdownParser,
VanillaMarkdownParser,
......
......@@ -2,7 +2,7 @@ import type React from "react";
import type { ReactNode } from "react";
import { useState } from "react";
import { IpcClient } from "../../ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { Package, ChevronsUpDown, ChevronsDownUp } from "lucide-react";
import { CodeHighlight } from "./CodeHighlight";
......@@ -45,7 +45,7 @@ export const DyadAddDependency: React.FC<DyadAddDependencyProps> = ({
className="cursor-pointer text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
key={p}
onClick={() => {
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
`https://www.npmjs.com/package/${p}`,
);
}}
......
......@@ -19,7 +19,7 @@ import { isStreamingByIdAtom, selectedChatIdAtom } from "@/atoms/chatAtoms";
import { CustomTagState } from "./stateTypes";
import { DyadOutput } from "./DyadOutput";
import { DyadProblemSummary } from "./DyadProblemSummary";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { DyadMcpToolCall } from "./DyadMcpToolCall";
import { DyadMcpToolResult } from "./DyadMcpToolResult";
import { DyadWebSearchResult } from "./DyadWebSearchResult";
......@@ -99,7 +99,7 @@ const customLink = ({
const url = props.href;
if (url) {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(url);
ipc.system.openExternalUrl(url);
}
}}
/>
......
......@@ -5,7 +5,7 @@ import {
AlertTriangle,
FileText,
} from "lucide-react";
import type { Problem } from "@/ipc/ipc_types";
import type { Problem } from "@/ipc/types";
type ProblemWithoutSnippet = Omit<Problem, "snippet">;
......
import React from "react";
import type { Message } from "@/ipc/ipc_types";
import type { Message } from "@/ipc/types";
import { forwardRef, useState, useCallback, useMemo } from "react";
import { Virtuoso } from "react-virtuoso";
import ChatMessage from "./ChatMessage";
......@@ -13,7 +13,7 @@ import { Button } from "@/components/ui/button";
import { useVersions } from "@/hooks/useVersions";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { showError, showWarning } from "@/lib/toast";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { chatMessagesByIdAtom } from "@/atoms/chatAtoms";
import { useLanguageModelProviders } from "@/hooks/useLanguageModelProviders";
import { useSettings } from "@/hooks/useSettings";
......@@ -121,8 +121,7 @@ function FooterComponent({ context }: { context?: FooterContext }) {
}
: undefined,
});
const chat =
await IpcClient.getInstance().getChat(selectedChatId);
const chat = await ipc.chat.getChat(selectedChatId);
setMessagesById((prev) => {
const next = new Map(prev);
next.set(selectedChatId, chat.messages);
......@@ -182,8 +181,7 @@ function FooterComponent({ context }: { context?: FooterContext }) {
});
shouldRedo = false;
} else {
const chat =
await IpcClient.getInstance().getChat(selectedChatId);
const chat = await ipc.chat.getChat(selectedChatId);
if (chat.initialCommitHash) {
console.debug(
"Reverting to initial commit hash",
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import React from "react";
// Types for the message system
......@@ -36,7 +36,7 @@ export function Message({ spans }: MessageConfig) {
if (span.action) {
span.action();
} else if (span.url) {
IpcClient.getInstance().openExternalUrl(span.url);
ipc.system.openExternalUrl(span.url);
}
}}
className="text-blue-600 hover:text-blue-800 underline cursor-pointer"
......
import { useState } from "react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { showError, showSuccess } from "@/lib/toast";
import {
Dialog,
......@@ -46,7 +46,7 @@ export function RenameChatDialog({
}
try {
await IpcClient.getInstance().updateChat({
await ipc.chat.updateChat({
chatId,
title: newTitle.trim(),
});
......
......@@ -3,7 +3,7 @@ import { useAtomValue } from "jotai";
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useStreamChat } from "@/hooks/useStreamChat";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { showError } from "@/lib/toast";
export function useSummarizeInNewChat() {
......@@ -22,7 +22,7 @@ export function useSummarizeInNewChat() {
return;
}
try {
const newChatId = await IpcClient.getInstance().createChat(appId);
const newChatId = await ipc.chat.createChat(appId);
// navigate to new chat
await navigate({ to: "/chat", search: { id: newChatId } });
await streamMessage({
......
import React, { useState } from "react";
import type { AgentTodo } from "@/ipc/ipc_types";
import type { AgentTodo } from "@/ipc/types";
import {
CheckCircle2,
Circle,
......
......@@ -16,7 +16,7 @@ import {
import { chatInputValueAtom } from "@/atoms/chatAtoms";
import { useAtom } from "jotai";
import { useSettings } from "@/hooks/useSettings";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
interface TokenBarProps {
chatId?: number;
......@@ -136,12 +136,10 @@ export function TokenBar({ chatId }: TokenBarProps) {
<a
onClick={() =>
settings?.enableDyadPro
? IpcClient.getInstance().openExternalUrl(
? ipc.system.openExternalUrl(
"https://www.dyad.sh/docs/guides/ai-models/pro-modes#smart-context",
)
: IpcClient.getInstance().openExternalUrl(
"https://dyad.sh/pro#ai",
)
: ipc.system.openExternalUrl("https://dyad.sh/pro#ai")
}
className="text-blue-500 dark:text-blue-400 cursor-pointer hover:underline"
>
......
......@@ -3,7 +3,7 @@ import { selectedAppIdAtom, selectedVersionIdAtom } from "@/atoms/appAtoms";
import { useVersions } from "@/hooks/useVersions";
import { formatDistanceToNow } from "date-fns";
import { RotateCcw, X, Database, Loader2 } from "lucide-react";
import type { Version } from "@/ipc/ipc_types";
import type { Version } from "@/ipc/types";
import { cn } from "@/lib/utils";
import { useEffect, useRef, useState } from "react";
import { useCheckoutVersion } from "@/hooks/useCheckoutVersion";
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { Play } from "lucide-react";
export const OnboardingBanner = ({
......@@ -16,7 +16,7 @@ export const OnboardingBanner = ({
<a
onClick={(e) => {
e.preventDefault();
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.youtube.com/watch?v=rgdNoHLaRN4",
);
setIsVisible(false);
......
import { useAtom, useAtomValue } from "jotai";
import { previewModeAtom, selectedAppIdAtom } from "../../atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import {
Eye,
......@@ -87,8 +87,7 @@ export const ActionHeader = () => {
const useClearSessionData = () => {
return useMutation({
mutationFn: () => {
const ipcClient = IpcClient.getInstance();
return ipcClient.clearSessionData();
return ipc.system.clearSessionData();
},
onSuccess: async () => {
await refreshAppIframe();
......
import { Lock, ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
interface AnnotatorOnlyForProProps {
onGoBack: () => void;
......@@ -8,7 +8,7 @@ interface AnnotatorOnlyForProProps {
export const AnnotatorOnlyForPro = ({ onGoBack }: AnnotatorOnlyForProProps) => {
const handleGetPro = () => {
IpcClient.getInstance().openExternalUrl("https://dyad.sh/pro");
ipc.system.openExternalUrl("https://dyad.sh/pro");
};
return (
......
......@@ -21,7 +21,7 @@ import {
} from "lucide-react";
import { showError, showSuccess } from "@/lib/toast";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useNavigate } from "@tanstack/react-router";
import { NeonConfigure } from "./NeonConfigure";
import { queryKeys } from "@/lib/queryKeys";
......@@ -66,8 +66,7 @@ export const ConfigurePanel = () => {
queryKey: queryKeys.appEnvVars.byApp({ appId: selectedAppId }),
queryFn: async () => {
if (!selectedAppId) return [];
const ipcClient = IpcClient.getInstance();
return await ipcClient.getAppEnvVars({ appId: selectedAppId });
return await ipc.misc.getAppEnvVars({ appId: selectedAppId });
},
enabled: !!selectedAppId,
});
......@@ -76,8 +75,7 @@ export const ConfigurePanel = () => {
const saveEnvVarsMutation = useMutation({
mutationFn: async (newEnvVars: { key: string; value: string }[]) => {
if (!selectedAppId) throw new Error("No app selected");
const ipcClient = IpcClient.getInstance();
return await ipcClient.setAppEnvVars({
return await ipc.misc.setAppEnvVars({
appId: selectedAppId,
envVars: newEnvVars,
});
......
import { appConsoleEntriesAtom, selectedAppIdAtom } from "@/atoms/appAtoms";
import type { ConsoleEntry } from "@/ipc/ipc_types";
import type { ConsoleEntry } from "@/ipc/types";
import { useAtomValue, useSetAtom } from "jotai";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useEffect, useRef, useState, useMemo, useCallback, memo } from "react";
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
import { ConsoleEntryComponent } from "./ConsoleEntry";
......@@ -107,7 +107,7 @@ export const Console = () => {
if (selectedAppId) {
try {
// Clear logs from backend store
await IpcClient.getInstance().clearLogs(selectedAppId);
await ipc.misc.clearLogs({ appId: selectedAppId });
// Clear logs from UI
setConsoleEntries([]);
} catch (error) {
......
......@@ -4,7 +4,7 @@ import { useLoadAppFile } from "@/hooks/useLoadAppFile";
import { useTheme } from "@/contexts/ThemeContext";
import { ChevronRight, Circle, Save } from "lucide-react";
import "@/components/chat/monaco";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { showError, showSuccess, showWarning } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import {
......@@ -190,12 +190,11 @@ export const FileEditor = ({
isSavingRef.current = true;
setIsSaving(true);
const ipcClient = IpcClient.getInstance();
const { warning } = await ipcClient.editAppFile(
const { warning } = await ipc.app.editAppFile({
appId,
filePath,
currentValueRef.current,
);
content: currentValueRef.current,
});
await queryClient.invalidateQueries({
queryKey: queryKeys.versions.list({ appId }),
});
......
......@@ -11,7 +11,7 @@ import {
import { selectedFileAtom } from "@/atoms/viewAtoms";
import { useSetAtom } from "jotai";
import { Input } from "@/components/ui/input";
import type { AppFileSearchResult } from "@/ipc/ipc_types";
import type { AppFileSearchResult } from "@/ipc/types";
import { useSearchAppFiles } from "@/hooks/useSearchAppFiles";
interface FileTreeProps {
......
......@@ -7,8 +7,8 @@ import { Badge } from "@/components/ui/badge";
import { Database, GitBranch } from "lucide-react";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useLoadApp } from "@/hooks/useLoadApp";
import { IpcClient } from "@/ipc/ipc_client";
import type { GetNeonProjectResponse, NeonBranch } from "@/ipc/ipc_types";
import { ipc } from "@/ipc/types";
import type { GetNeonProjectResponse, NeonBranch } from "@/ipc/types";
import { NeonDisconnectButton } from "@/components/NeonDisconnectButton";
import { queryKeys } from "@/lib/queryKeys";
......@@ -44,8 +44,7 @@ export const NeonConfigure = () => {
queryKey: queryKeys.neon.project({ appId: selectedAppId }),
queryFn: async () => {
if (!selectedAppId) throw new Error("No app selected");
const ipcClient = IpcClient.getInstance();
return await ipcClient.getNeonProject({ appId: selectedAppId });
return await ipc.neon.getProject({ appId: selectedAppId });
},
enabled: !!selectedAppId && !!app?.neonProjectId,
meta: { showErrorToast: true },
......
......@@ -27,7 +27,7 @@ import {
} from "lucide-react";
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { CopyErrorMessage } from "@/components/CopyErrorMessage";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useParseRouter } from "@/hooks/useParseRouter";
import {
......@@ -46,7 +46,7 @@ import {
screenshotDataUrlAtom,
pendingVisualChangesAtom,
} from "@/atoms/previewAtoms";
import { ComponentSelection } from "@/ipc/ipc_types";
import { ComponentSelection } from "@/ipc/types";
import {
Tooltip,
TooltipContent,
......@@ -231,7 +231,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
if (!componentId || !selectedAppId) return;
try {
const result = await IpcClient.getInstance().analyzeComponent({
const result = await ipc.visualEditing.analyzeComponent({
appId: selectedAppId,
componentId,
});
......@@ -362,7 +362,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
};
// Send to central log store
IpcClient.getInstance().addLog(logEntry);
ipc.misc.addLog(logEntry);
// Also update UI state
setConsoleEntries((prev) => [...prev, logEntry]);
......@@ -382,7 +382,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
};
// Send to central log store
IpcClient.getInstance().addLog(logEntry);
ipc.misc.addLog(logEntry);
// Also update UI state
setConsoleEntries((prev) => [...prev, logEntry]);
......@@ -404,7 +404,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
};
// Send to central log store
IpcClient.getInstance().addLog(logEntry);
ipc.misc.addLog(logEntry);
// Also update UI state
setConsoleEntries((prev) => [...prev, logEntry]);
......@@ -425,7 +425,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
};
// Send to central log store
IpcClient.getInstance().addLog(logEntry);
ipc.misc.addLog(logEntry);
// Also update UI state
setConsoleEntries((prev) => [...prev, logEntry]);
......@@ -573,7 +573,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
};
// Send to central log store
IpcClient.getInstance().addLog(logEntry);
ipc.misc.addLog(logEntry);
// Also update UI state
setConsoleEntries((prev) => [...prev, logEntry]);
......@@ -590,7 +590,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
};
// Send to central log store
IpcClient.getInstance().addLog(logEntry);
ipc.misc.addLog(logEntry);
// Also update UI state
setConsoleEntries((prev) => [...prev, logEntry]);
......@@ -950,7 +950,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
data-testid="preview-open-browser-button"
onClick={() => {
if (originalUrl) {
IpcClient.getInstance().openExternalUrl(originalUrl);
ipc.system.openExternalUrl(originalUrl);
}
}}
className="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed dark:text-gray-300"
......
......@@ -10,7 +10,7 @@ import {
RefreshCw,
Check,
} from "lucide-react";
import { Problem, ProblemReport } from "@/ipc/ipc_types";
import { Problem, ProblemReport } from "@/ipc/types";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
......
......@@ -4,7 +4,7 @@ import { useLoadApp } from "@/hooks/useLoadApp";
import { GitHubConnector } from "@/components/GitHubConnector";
import { VercelConnector } from "@/components/VercelConnector";
import { PortalMigrate } from "@/components/PortalMigrate";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { GithubCollaboratorManager } from "@/components/GithubCollaboratorManager";
......@@ -120,8 +120,7 @@ export const PublishPanel = () => {
<CardTitle className="flex items-center gap-2">
<button
onClick={() => {
const ipcClient = IpcClient.getInstance();
ipcClient.openExternalUrl("https://vercel.com/dashboard");
ipc.system.openExternalUrl("https://vercel.com/dashboard");
}}
className="flex items-center gap-2 hover:text-blue-600 dark:hover:text-blue-400 transition-colors cursor-pointer bg-transparent border-none p-0"
>
......
......@@ -2,7 +2,7 @@ import { useAtomValue, useSetAtom } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { useSecurityReview } from "@/hooks/useSecurityReview";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
......@@ -28,7 +28,10 @@ import { useStreamChat } from "@/hooks/useStreamChat";
import { showError } from "@/lib/toast";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import type { SecurityFinding, SecurityReviewResult } from "@/ipc/ipc_types";
import type {
SecurityFinding,
SecurityReviewResult,
} from "@/ipc/types/security";
import { useState, useEffect } from "react";
import { VanillaMarkdownParser } from "@/components/chat/DyadMarkdownParser";
import { showSuccess, showWarning } from "@/lib/toast";
......@@ -251,7 +254,7 @@ function SecurityHeader({
<a
className="text-blue-600 dark:text-blue-400 hover:underline cursor-pointer"
onClick={() =>
IpcClient.getInstance().openExternalUrl(
ipc.system.openExternalUrl(
"https://www.dyad.sh/docs/guides/security-review",
)
}
......@@ -742,12 +745,11 @@ export const SecurityPanel = () => {
try {
setIsSaving(true);
const ipcClient = IpcClient.getInstance();
const { warning } = await ipcClient.editAppFile(
selectedAppId,
"SECURITY_RULES.md",
rulesContent,
);
const { warning } = await ipc.app.editAppFile({
appId: selectedAppId,
filePath: "SECURITY_RULES.md",
content: rulesContent,
});
await queryClient.invalidateQueries({
queryKey: queryKeys.versions.list({ appId: selectedAppId }),
});
......@@ -780,7 +782,7 @@ export const SecurityPanel = () => {
setIsRunningReview(true);
// Create a new chat
const chatId = await IpcClient.getInstance().createChat(selectedAppId);
const chatId = await ipc.chat.createChat(selectedAppId);
// Navigate to the new chat
setSelectedChatId(chatId);
......@@ -811,7 +813,7 @@ export const SecurityPanel = () => {
const key = createFindingKey(finding);
setFixingFindingKey(key);
const chatId = await IpcClient.getInstance().createChat(selectedAppId);
const chatId = await ipc.chat.createChat(selectedAppId);
// Navigate to the new chat
setSelectedChatId(chatId);
......@@ -880,7 +882,7 @@ ${finding.description}`;
);
// Create a new chat
const chatId = await IpcClient.getInstance().createChat(selectedAppId);
const chatId = await ipc.chat.createChat(selectedAppId);
// Navigate to the new chat
setSelectedChatId(chatId);
......
import { useAtom, useAtomValue } from "jotai";
import { pendingVisualChangesAtom } from "@/atoms/previewAtoms";
import { Button } from "@/components/ui/button";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { Check, X } from "lucide-react";
import { useState, useEffect, useRef } from "react";
import { showError, showSuccess } from "@/lib/toast";
......@@ -66,7 +66,7 @@ export function VisualEditingChangesDialog({
return change;
});
await IpcClient.getInstance().applyVisualEditingChanges({
await ipc.visualEditing.applyChanges({
appId: selectedAppId!,
changes: updatedChanges,
});
......@@ -130,7 +130,7 @@ export function VisualEditingChangesDialog({
setAllResponsesReceived(true);
}
} else {
await IpcClient.getInstance().applyVisualEditingChanges({
await ipc.visualEditing.applyChanges({
appId: selectedAppId!,
changes: changesToSave,
});
......
import { useState, useEffect } from "react";
import { X, Move, Square, Palette, Type } from "lucide-react";
import { Label } from "@/components/ui/label";
import { ComponentSelection } from "@/ipc/ipc_types";
import { ComponentSelection } from "@/ipc/types";
import { useSetAtom, useAtomValue } from "jotai";
import {
pendingVisualChangesAtom,
......
......@@ -8,7 +8,7 @@ import {
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import {
Popover,
PopoverContent,
......@@ -53,7 +53,7 @@ export function ProviderSettingsHeader({
const handleGetApiKeyClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (providerWebsiteUrl) {
IpcClient.getInstance().openExternalUrl(providerWebsiteUrl);
ipc.system.openExternalUrl(providerWebsiteUrl);
}
};
......
import React, { createContext, useContext, useEffect, useState } from "react";
import { useNavigate } from "@tanstack/react-router";
import { IpcClient } from "../ipc/ipc_client";
import { DeepLinkData } from "../ipc/deep_link_data";
import { ipc, DeepLinkData } from "../ipc/types";
import { useScrollAndNavigateTo } from "@/hooks/useScrollAndNavigateTo";
type DeepLinkContextType = {
......@@ -24,8 +23,7 @@ export function DeepLinkProvider({ children }: { children: React.ReactNode }) {
block: "start",
});
useEffect(() => {
const ipcClient = IpcClient.getInstance();
const unsubscribe = ipcClient.onDeepLinkReceived((data) => {
const unsubscribe = ipc.events.misc.onDeepLinkReceived((data) => {
// Update with timestamp to ensure state change even if same type comes twice
setLastDeepLink({ ...data, timestamp: Date.now() });
if (data.type === "add-mcp-server") {
......
import { useMutation } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { showError, showSuccess } from "@/lib/toast";
import { useAtom } from "jotai";
import { appsListAtom } from "@/atoms/appAtoms";
......@@ -9,7 +9,7 @@ export function useAddAppToFavorite() {
const mutation = useMutation<boolean, Error, number>({
mutationFn: async (appId: number): Promise<boolean> => {
const result = await IpcClient.getInstance().addAppToFavorite(appId);
const result = await ipc.app.addToFavorite({ appId });
return result.isFavorite;
},
onSuccess: (newIsFavorite, appId) => {
......
......@@ -3,9 +3,9 @@
*/
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import type { AgentToolName } from "../pro/main/ipc/handlers/local_agent/tool_definitions";
import type { AgentTool } from "@/ipc/ipc_types";
import type { AgentTool } from "@/ipc/types";
import { AgentToolConsent } from "@/lib/schemas";
import { queryKeys } from "@/lib/queryKeys";
......@@ -18,8 +18,7 @@ export function useAgentTools() {
const toolsQuery = useQuery({
queryKey: queryKeys.agentTools.all,
queryFn: async () => {
const ipcClient = IpcClient.getInstance();
return ipcClient.getAgentTools();
return ipc.agent.getTools();
},
});
......@@ -28,8 +27,7 @@ export function useAgentTools() {
toolName: AgentToolName;
consent: AgentToolConsent;
}) => {
const ipcClient = IpcClient.getInstance();
return ipcClient.setAgentToolConsent(params);
return ipc.agent.setConsent(params);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.agentTools.all });
......
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
export function useAppTheme(appId: number | undefined) {
......@@ -8,7 +8,7 @@ export function useAppTheme(appId: number | undefined) {
const query = useQuery({
queryKey: queryKeys.appTheme.byApp({ appId }),
queryFn: async (): Promise<string | null> => {
return IpcClient.getInstance().getAppTheme({ appId: appId! });
return ipc.template.getAppTheme({ appId: appId! });
},
enabled: !!appId,
});
......
import { useState, useEffect } from "react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function useAppVersion() {
const [appVersion, setAppVersion] = useState<string | null>(null);
......@@ -7,8 +7,8 @@ export function useAppVersion() {
useEffect(() => {
const fetchVersion = async () => {
try {
const version = await IpcClient.getInstance().getAppVersion();
setAppVersion(version);
const result = await ipc.system.getAppVersion();
setAppVersion(result.version);
} catch {
setAppVersion(null);
}
......
import React, { useRef, useState } from "react";
import type { FileAttachment } from "@/ipc/ipc_types";
import type { FileAttachment } from "@/ipc/types";
import { useAtom } from "jotai";
import { attachmentsAtom } from "@/atoms/chatAtoms";
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import type { ChatSummary } from "@/lib/schemas";
import { queryKeys } from "@/lib/queryKeys";
import { useQuery, useQueryClient } from "@tanstack/react-query";
......@@ -9,7 +9,7 @@ export function useChats(appId: number | null) {
const { data, isLoading } = useQuery<ChatSummary[]>({
queryKey: queryKeys.chats.list({ appId }),
queryFn: async () => {
return IpcClient.getInstance().getChats(appId ?? undefined);
return ipc.chat.getChats(appId ?? undefined);
},
});
......
import { useQuery } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
export const useCheckName = (appName: string) => {
return useQuery({
queryKey: queryKeys.appName.check({ name: appName }),
queryFn: async () => {
const result = await IpcClient.getInstance().checkAppName({ appName });
const result = await ipc.app.checkAppName({ appName });
return result;
},
enabled: !!appName && !!appName.trim(),
......
import { useQuery } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import type { ProblemReport } from "@/ipc/ipc_types";
import { ipc, type ProblemReport } from "@/ipc/types";
import { useSettings } from "./useSettings";
import { queryKeys } from "@/lib/queryKeys";
......@@ -17,8 +16,7 @@ export function useCheckProblems(appId: number | null) {
if (!appId) {
throw new Error("App ID is required");
}
const ipcClient = IpcClient.getInstance();
return ipcClient.checkProblems({ appId });
return ipc.misc.checkProblems({ appId });
},
enabled: !!appId && settings?.enableAutoFixProblems,
// DO NOT SHOW ERROR TOAST.
......
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useSetAtom } from "jotai";
import { activeCheckoutCounterAtom } from "@/store/appAtoms";
import { queryKeys } from "@/lib/queryKeys";
......@@ -20,10 +20,9 @@ export function useCheckoutVersion() {
// Should be caught by UI logic before calling, but as a safeguard.
throw new Error("App ID is null, cannot checkout version.");
}
const ipcClient = IpcClient.getInstance();
setActiveCheckouts((prev) => prev + 1); // Increment counter
try {
await ipcClient.checkoutVersion({ appId, versionId });
await ipc.version.checkoutVersion({ appId, versionId });
} finally {
setActiveCheckouts((prev) => prev - 1); // Decrement counter
}
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { showError, showSuccess } from "@/lib/toast";
import { queryKeys } from "@/lib/queryKeys";
......@@ -14,8 +14,7 @@ export function useCommitChanges() {
appId: number;
message: string;
}) => {
const ipcClient = IpcClient.getInstance();
return ipcClient.commitChanges({ appId, message });
return ipc.git.commitChanges({ appId, message });
},
onSuccess: (_, { appId }) => {
showSuccess("Changes committed successfully");
......
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useAtomValue } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { GlobPath, ContextPathResults } from "@/lib/schemas";
import { queryKeys } from "@/lib/queryKeys";
......@@ -22,8 +22,7 @@ export function useContextPaths() {
smartContextAutoIncludes: [],
excludePaths: [],
};
const ipcClient = IpcClient.getInstance();
return ipcClient.getChatContextResults({ appId });
return ipc.context.getContextPaths({ appId });
},
enabled: !!appId,
});
......@@ -43,8 +42,7 @@ export function useContextPaths() {
excludePaths,
}) => {
if (!appId) throw new Error("No app selected");
const ipcClient = IpcClient.getInstance();
return ipcClient.setChatContext({
return ipc.context.setContextPaths({
appId,
chatContext: {
contextPaths,
......
......@@ -3,8 +3,7 @@ import {
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import type { TokenCountResult } from "@/ipc/ipc_types";
import { ipc, type TokenCountResult } from "@/ipc/types";
import { useCallback, useEffect, useState } from "react";
import { queryKeys } from "@/lib/queryKeys";
......@@ -37,7 +36,7 @@ export function useCountTokens(chatId: number | null, input: string = "") {
queryKey: queryKeys.tokenCount.forChat({ chatId, input: debouncedInput }),
queryFn: async () => {
if (chatId === null) return null;
return IpcClient.getInstance().countTokens({
return ipc.chat.countTokens({
chatId,
input: debouncedInput,
});
......
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import type { CreateAppParams, CreateAppResult } from "@/ipc/types";
import { showError } from "@/lib/toast";
import type { CreateAppParams, CreateAppResult } from "@/ipc/ipc_types";
import { queryKeys } from "@/lib/queryKeys";
export function useCreateApp() {
......@@ -13,8 +13,7 @@ export function useCreateApp() {
throw new Error("App name is required");
}
const ipcClient = IpcClient.getInstance();
return ipcClient.createApp(params);
return ipc.app.createApp(params);
},
onSuccess: () => {
// Invalidate apps list to trigger refetch
......
import { IpcClient } from "@/ipc/ipc_client";
import { ipc, type BranchResult } from "@/ipc/types";
import { useQuery } from "@tanstack/react-query";
import type { BranchResult } from "@/ipc/ipc_types";
import { queryKeys } from "@/lib/queryKeys";
export function useCurrentBranch(appId: number | null) {
......@@ -16,8 +15,7 @@ export function useCurrentBranch(appId: number | null) {
// but as a safeguard, and to ensure queryFn always has a valid appId if called.
throw new Error("appId is null, cannot fetch current branch.");
}
const ipcClient = IpcClient.getInstance();
return ipcClient.getCurrentBranch(appId);
return ipc.version.getCurrentBranch({ appId });
},
enabled: appId !== null,
meta: { showErrorToast: true },
......
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import type {
CreateCustomLanguageModelProviderParams,
LanguageModelProvider,
} from "@/ipc/ipc_types";
import {
ipc,
type CreateCustomLanguageModelProviderParams,
type LanguageModelProvider,
} from "@/ipc/types";
import { showError } from "@/lib/toast";
import { queryKeys } from "@/lib/queryKeys";
export function useCustomLanguageModelProvider() {
const queryClient = useQueryClient();
const ipcClient = IpcClient.getInstance();
const createProviderMutation = useMutation({
mutationFn: async (
......@@ -25,7 +24,7 @@ export function useCustomLanguageModelProvider() {
throw new Error("API base URL is required");
}
return ipcClient.createCustomLanguageModelProvider({
return ipc.languageModel.createCustomProvider({
id: params.id.trim(),
name: params.name.trim(),
apiBaseUrl: params.apiBaseUrl.trim(),
......@@ -57,7 +56,7 @@ export function useCustomLanguageModelProvider() {
throw new Error("API base URL is required");
}
return ipcClient.editCustomLanguageModelProvider({
return ipc.languageModel.editCustomProvider({
id: params.id.trim(),
name: params.name.trim(),
apiBaseUrl: params.apiBaseUrl.trim(),
......@@ -81,7 +80,7 @@ export function useCustomLanguageModelProvider() {
throw new Error("Provider ID is required");
}
return ipcClient.deleteCustomLanguageModelProvider(providerId);
return ipc.languageModel.deleteCustomProvider({ providerId });
},
onSuccess: () => {
// Invalidate and refetch
......
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import type {
CustomTheme,
CreateCustomThemeParams,
UpdateCustomThemeParams,
GenerateThemePromptParams,
GenerateThemePromptResult,
} from "@/ipc/ipc_types";
} from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
/**
......@@ -16,8 +16,7 @@ export function useCustomThemes() {
const query = useQuery({
queryKey: queryKeys.customThemes.all,
queryFn: async (): Promise<CustomTheme[]> => {
const ipcClient = IpcClient.getInstance();
return ipcClient.getCustomThemes();
return ipc.template.getCustomThemes();
},
meta: {
showErrorToast: true,
......@@ -39,8 +38,7 @@ export function useCreateCustomTheme() {
mutationFn: async (
params: CreateCustomThemeParams,
): Promise<CustomTheme> => {
const ipcClient = IpcClient.getInstance();
return ipcClient.createCustomTheme(params);
return ipc.template.createCustomTheme(params);
},
onSuccess: () => {
// Invalidate all custom theme queries using prefix matching
......@@ -58,8 +56,7 @@ export function useUpdateCustomTheme() {
mutationFn: async (
params: UpdateCustomThemeParams,
): Promise<CustomTheme> => {
const ipcClient = IpcClient.getInstance();
return ipcClient.updateCustomTheme(params);
return ipc.template.updateCustomTheme(params);
},
onSuccess: () => {
// Invalidate all custom theme queries using prefix matching
......@@ -75,8 +72,7 @@ export function useDeleteCustomTheme() {
return useMutation({
mutationFn: async (id: number): Promise<void> => {
const ipcClient = IpcClient.getInstance();
await ipcClient.deleteCustomTheme({ id });
await ipc.template.deleteCustomTheme({ id });
},
onSuccess: () => {
// Invalidate all custom theme queries using prefix matching
......@@ -92,8 +88,7 @@ export function useGenerateThemePrompt() {
mutationFn: async (
params: GenerateThemePromptParams,
): Promise<GenerateThemePromptResult> => {
const ipcClient = IpcClient.getInstance();
return ipcClient.generateThemePrompt(params);
return ipc.template.generateThemePrompt(params);
},
});
}
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
interface DeleteCustomModelParams {
......@@ -23,9 +23,7 @@ export function useDeleteCustomModel({
"Provider ID and Model API Name are required for deletion.",
);
}
const ipcClient = IpcClient.getInstance();
// This method will be added to IpcClient next
await ipcClient.deleteCustomModel(params);
await ipc.languageModel.deleteModel(params);
},
onSuccess: (data, params: DeleteCustomModelParams) => {
// Invalidate queries related to language models for the specific provider
......
......@@ -5,22 +5,21 @@ import {
lmStudioModelsLoadingAtom,
lmStudioModelsErrorAtom,
} from "@/atoms/localModelsAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function useLocalLMSModels() {
const [models, setModels] = useAtom(lmStudioModelsAtom);
const [loading, setLoading] = useAtom(lmStudioModelsLoadingAtom);
const [error, setError] = useAtom(lmStudioModelsErrorAtom);
const ipcClient = IpcClient.getInstance();
/**
* Load local models from Ollama
* Load local models from LMStudio
*/
const loadModels = useCallback(async () => {
setLoading(true);
try {
const modelList = await ipcClient.listLocalLMStudioModels();
const { models: modelList } =
await ipc.languageModel.listLMStudioModels();
setModels(modelList);
setError(null);
......@@ -32,7 +31,7 @@ export function useLocalLMSModels() {
} finally {
setLoading(false);
}
}, [ipcClient, setModels, setError, setLoading]);
}, [setModels, setError, setLoading]);
return {
models,
......
import { useQuery } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import type { LanguageModelProvider } from "@/ipc/ipc_types";
import { ipc, type LanguageModelProvider } from "@/ipc/types";
import { useSettings } from "./useSettings";
import {
cloudProviders,
......@@ -10,13 +9,12 @@ import {
import { queryKeys } from "@/lib/queryKeys";
export function useLanguageModelProviders() {
const ipcClient = IpcClient.getInstance();
const { settings, envVars } = useSettings();
const queryResult = useQuery<LanguageModelProvider[], Error>({
queryKey: queryKeys.languageModels.providers,
queryFn: async () => {
return ipcClient.getLanguageModelProviders();
return ipc.languageModel.getProviders();
},
});
......
import { useQuery } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import type { LanguageModel } from "@/ipc/ipc_types";
import { ipc, type LanguageModel } from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
/**
......@@ -9,12 +8,10 @@ import { queryKeys } from "@/lib/queryKeys";
* @returns TanStack Query result object for the language models organized by provider.
*/
export function useLanguageModelsByProviders() {
const ipcClient = IpcClient.getInstance();
return useQuery<Record<string, LanguageModel[]>, Error>({
queryKey: queryKeys.languageModels.byProviders,
queryFn: async () => {
return ipcClient.getLanguageModelsByProviders();
return ipc.languageModel.getModelsByProviders();
},
});
}
import { useQuery } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import type { LanguageModel } from "@/ipc/ipc_types";
import { ipc, type LanguageModel } from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
/**
......@@ -10,8 +9,6 @@ import { queryKeys } from "@/lib/queryKeys";
* @returns TanStack Query result object for the language models.
*/
export function useLanguageModelsForProvider(providerId: string | undefined) {
const ipcClient = IpcClient.getInstance();
return useQuery<
LanguageModel[],
Error // Specify Error type for better error handling
......@@ -25,7 +22,7 @@ export function useLanguageModelsForProvider(providerId: string | undefined) {
// Return an empty array as it's a query, not an error state
return [];
}
return ipcClient.getLanguageModels({ providerId });
return ipc.languageModel.getModels({ providerId });
},
enabled: !!providerId,
});
......
import { useEffect } from "react";
import { useQuery, QueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc, type App } from "@/ipc/types";
import { useAtom } from "jotai";
import { currentAppAtom } from "@/atoms/appAtoms";
import { App } from "@/ipc/ipc_types";
import { queryKeys } from "@/lib/queryKeys";
export function useLoadApp(appId: number | null) {
......@@ -20,8 +19,7 @@ export function useLoadApp(appId: number | null) {
if (appId === null) {
return null;
}
const ipcClient = IpcClient.getInstance();
return ipcClient.getApp(appId);
return ipc.app.getApp(appId);
},
enabled: appId !== null,
// Deliberately not showing error toast here because
......
import { useState, useEffect } from "react";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function useLoadAppFile(appId: number | null, filePath: string | null) {
const [content, setContent] = useState<string | null>(null);
......@@ -16,8 +16,7 @@ export function useLoadAppFile(appId: number | null, filePath: string | null) {
setLoading(true);
try {
const ipcClient = IpcClient.getInstance();
const fileContent = await ipcClient.readAppFile(appId, filePath);
const fileContent = await ipc.app.readAppFile({ appId, filePath });
setContent(fileContent);
setError(null);
......@@ -43,8 +42,7 @@ export function useLoadAppFile(appId: number | null, filePath: string | null) {
setLoading(true);
try {
const ipcClient = IpcClient.getInstance();
const fileContent = await ipcClient.readAppFile(appId, filePath);
const fileContent = await ipc.app.readAppFile({ appId, filePath });
setContent(fileContent);
setError(null);
} catch (error) {
......
import { useState, useEffect, useCallback } from "react";
import { useAtom } from "jotai";
import { appsListAtom } from "@/atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function useLoadApps() {
const [apps, setApps] = useAtom(appsListAtom);
......@@ -11,8 +11,7 @@ export function useLoadApps() {
const refreshApps = useCallback(async () => {
setLoading(true);
try {
const ipcClient = IpcClient.getInstance();
const appListResponse = await ipcClient.listApps();
const appListResponse = await ipc.app.listApps();
setApps(appListResponse.apps);
setError(null);
} catch (error) {
......
......@@ -5,22 +5,20 @@ import {
localModelsLoadingAtom,
localModelsErrorAtom,
} from "@/atoms/localModelsAtoms";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
export function useLocalModels() {
const [models, setModels] = useAtom(localModelsAtom);
const [loading, setLoading] = useAtom(localModelsLoadingAtom);
const [error, setError] = useAtom(localModelsErrorAtom);
const ipcClient = IpcClient.getInstance();
/**
* Load local models from Ollama
*/
const loadModels = useCallback(async () => {
setLoading(true);
try {
const modelList = await ipcClient.listLocalOllamaModels();
const { models: modelList } = await ipc.languageModel.listOllamaModels();
setModels(modelList);
setError(null);
......@@ -32,7 +30,7 @@ export function useLocalModels() {
} finally {
setLoading(false);
}
}, [ipcClient, setModels, setError, setLoading]);
}, [setModels, setError, setLoading]);
return {
models,
......
import { useMemo } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import type {
McpServer,
McpServerUpdate,
McpTool,
McpToolConsent,
CreateMcpServer,
} from "@/ipc/ipc_types";
} from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
export type Transport = "stdio" | "http";
......@@ -18,8 +18,7 @@ export function useMcp() {
const serversQuery = useQuery<McpServer[], Error>({
queryKey: queryKeys.mcp.servers,
queryFn: async () => {
const ipc = IpcClient.getInstance();
const list = await ipc.listMcpServers();
const list = await ipc.mcp.listServers();
return (list || []) as McpServer[];
},
meta: { showErrorToast: true },
......@@ -34,9 +33,8 @@ export function useMcp() {
queryKey: queryKeys.mcp.toolsByServer.list({ serverIds }),
enabled: serverIds.length > 0,
queryFn: async () => {
const ipc = IpcClient.getInstance();
const entries = await Promise.all(
serverIds.map(async (id) => [id, await ipc.listMcpTools(id)] as const),
serverIds.map(async (id) => [id, await ipc.mcp.listTools(id)] as const),
);
return Object.fromEntries(entries) as Record<number, McpTool[]>;
},
......@@ -46,8 +44,7 @@ export function useMcp() {
const consentsQuery = useQuery<McpToolConsent[], Error>({
queryKey: queryKeys.mcp.consents,
queryFn: async () => {
const ipc = IpcClient.getInstance();
const list = await ipc.getMcpToolConsents();
const list = await ipc.mcp.getToolConsents();
return (list || []) as McpToolConsent[];
},
meta: { showErrorToast: true },
......@@ -63,8 +60,7 @@ export function useMcp() {
const createServerMutation = useMutation({
mutationFn: async (params: CreateMcpServer) => {
const ipc = IpcClient.getInstance();
return ipc.createMcpServer(params);
return ipc.mcp.createServer(params);
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.mcp.servers });
......@@ -77,8 +73,7 @@ export function useMcp() {
const updateServerMutation = useMutation({
mutationFn: async (params: McpServerUpdate) => {
const ipc = IpcClient.getInstance();
return ipc.updateMcpServer(params);
return ipc.mcp.updateServer(params);
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.mcp.servers });
......@@ -91,8 +86,7 @@ export function useMcp() {
const deleteServerMutation = useMutation({
mutationFn: async (id: number) => {
const ipc = IpcClient.getInstance();
return ipc.deleteMcpServer(id);
return ipc.mcp.deleteServer(id);
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.mcp.servers });
......@@ -109,8 +103,7 @@ export function useMcp() {
toolName: string;
consent: McpToolConsent["consent"];
}) => {
const ipc = IpcClient.getInstance();
return ipc.setMcpToolConsent(params);
return ipc.mcp.setToolConsent(params);
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.mcp.consents });
......
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { IpcClient } from "@/ipc/ipc_client";
import { ipc } from "@/ipc/types";
import { queryKeys } from "@/lib/queryKeys";
export interface PromptItem {
......@@ -16,8 +16,7 @@ export function usePrompts() {
const listQuery = useQuery({
queryKey: queryKeys.prompts.all,
queryFn: async (): Promise<PromptItem[]> => {
const ipc = IpcClient.getInstance();
return ipc.listPrompts();
return ipc.prompt.list();
},
meta: { showErrorToast: true },
});
......@@ -28,8 +27,7 @@ export function usePrompts() {
description?: string;
content: string;
}): Promise<PromptItem> => {
const ipc = IpcClient.getInstance();
return ipc.createPrompt(params);
return ipc.prompt.create(params);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.prompts.all });
......@@ -46,8 +44,7 @@ export function usePrompts() {
description?: string;
content: string;
}): Promise<void> => {
const ipc = IpcClient.getInstance();
return ipc.updatePrompt(params);
return ipc.prompt.update(params);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.prompts.all });
......@@ -59,8 +56,7 @@ export function usePrompts() {
const deleteMutation = useMutation({
mutationFn: async (id: number): Promise<void> => {
const ipc = IpcClient.getInstance();
return ipc.deletePrompt(id);
return ipc.prompt.delete(id);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.prompts.all });
......
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论