Files
moreminimore-vibe/src/ipc/ipc_client.ts
Mohamed Aziz Mejri 2a7f5a8909 feat: multi-component-selector (#1728)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Adds multi-component selection in the preview and sends all selected
components to chat for targeted edits. Updates overlays, UI, and IPC to
support arrays, smarter context focusing, and cross-platform path
normalization.

- **New Features**
- Select multiple components in the iframe; selection mode stays active
until you deactivate it.
- Show a scrollable list of selections with remove buttons and a Clear
all; remove from the list or click an overlay in the preview to
deselect. Sending clears all overlays.
- Separate hover vs selected overlays with labels on hover; overlays
persist after deactivation and re-position on layout changes/resizes.
- Chat input and streaming now send selectedComponents; server builds
per-component snippets and focuses their files in smart context.

- **Migration**
- Replace selectedComponentPreviewAtom with
selectedComponentsPreviewAtom (ComponentSelection[]).
- ChatStreamParams now uses selectedComponents; migrate any
single-selection usages.
  - previewIframeRefAtom added for clearing overlays from the parent.

<sup>Written for commit da0d64cc9e9f83fbf4b975278f6c869f0d3a8c7d.
Summary will update automatically on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2025-11-13 13:26:41 -08:00

1327 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { IpcRenderer } from "electron";
import {
type ChatSummary,
ChatSummariesSchema,
type UserSettings,
type ContextPathResults,
ChatSearchResultsSchema,
AppSearchResultsSchema,
} from "../lib/schemas";
import type {
AppOutput,
Chat,
ChatResponseEnd,
ChatProblemsEvent,
CreateAppParams,
CreateAppResult,
ListAppsResponse,
NodeSystemInfo,
Message,
Version,
SystemDebugInfo,
LocalModel,
TokenCountParams,
TokenCountResult,
ChatLogsData,
BranchResult,
LanguageModelProvider,
LanguageModel,
CreateCustomLanguageModelProviderParams,
CreateCustomLanguageModelParams,
DoesReleaseNoteExistParams,
ApproveProposalResult,
ImportAppResult,
ImportAppParams,
RenameBranchParams,
UserBudgetInfo,
CopyAppParams,
App,
ComponentSelection,
AppUpgrade,
ProblemReport,
EditAppFileReturnType,
GetAppEnvVarsParams,
SetAppEnvVarsParams,
ConnectToExistingVercelProjectParams,
IsVercelProjectAvailableResponse,
CreateVercelProjectParams,
VercelDeployment,
GetVercelDeploymentsParams,
DisconnectVercelProjectParams,
SecurityReviewResult,
IsVercelProjectAvailableParams,
SaveVercelAccessTokenParams,
VercelProject,
UpdateChatParams,
FileAttachment,
CreateNeonProjectParams,
NeonProject,
GetNeonProjectParams,
GetNeonProjectResponse,
RevertVersionResponse,
RevertVersionParams,
RespondToAppInputParams,
PromptDto,
CreatePromptParamsDto,
UpdatePromptParamsDto,
McpServerUpdate,
CreateMcpServer,
CloneRepoParams,
SupabaseBranch,
SetSupabaseAppProjectParams,
SelectNodeFolderResult,
} from "./ipc_types";
import type { Template } from "../shared/templates";
import type {
AppChatContext,
AppSearchResult,
ChatSearchResult,
ProposalResult,
} from "@/lib/schemas";
import { showError } from "@/lib/toast";
import { DeepLinkData } from "./deep_link_data";
export interface ChatStreamCallbacks {
onUpdate: (messages: Message[]) => void;
onEnd: (response: ChatResponseEnd) => void;
onError: (error: string) => void;
}
export interface AppStreamCallbacks {
onOutput: (output: AppOutput) => void;
}
export interface GitHubDeviceFlowUpdateData {
userCode?: string;
verificationUri?: string;
message?: string;
}
export interface GitHubDeviceFlowSuccessData {
message?: string;
}
export interface GitHubDeviceFlowErrorData {
error: string;
}
interface DeleteCustomModelParams {
providerId: string;
modelApiName: string;
}
export class IpcClient {
private static instance: IpcClient;
private ipcRenderer: IpcRenderer;
private chatStreams: Map<number, ChatStreamCallbacks>;
private appStreams: Map<number, AppStreamCallbacks>;
private helpStreams: Map<
string,
{
onChunk: (delta: string) => void;
onEnd: () => void;
onError: (error: string) => void;
}
>;
private mcpConsentHandlers: Map<string, (payload: any) => void>;
private constructor() {
this.ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer;
this.chatStreams = new Map();
this.appStreams = new Map();
this.helpStreams = new Map();
this.mcpConsentHandlers = new Map();
// Set up listeners for stream events
this.ipcRenderer.on("chat:response:chunk", (data) => {
if (
data &&
typeof data === "object" &&
"chatId" in data &&
"messages" in data
) {
const { chatId, messages } = data as {
chatId: number;
messages: Message[];
};
const callbacks = this.chatStreams.get(chatId);
if (callbacks) {
callbacks.onUpdate(messages);
} else {
console.warn(
`[IPC] No callbacks found for chat ${chatId}`,
this.chatStreams,
);
}
} else {
showError(new Error(`[IPC] Invalid chunk data received: ${data}`));
}
});
this.ipcRenderer.on("app:output", (data) => {
if (
data &&
typeof data === "object" &&
"type" in data &&
"message" in data &&
"appId" in data
) {
const { type, message, appId } = data as unknown as AppOutput;
const callbacks = this.appStreams.get(appId);
if (callbacks) {
callbacks.onOutput({ type, message, appId, timestamp: Date.now() });
}
} else {
showError(new Error(`[IPC] Invalid app output data received: ${data}`));
}
});
this.ipcRenderer.on("chat:response:end", (payload) => {
const { chatId } = payload as unknown as ChatResponseEnd;
const callbacks = this.chatStreams.get(chatId);
if (callbacks) {
callbacks.onEnd(payload as unknown as ChatResponseEnd);
console.debug("chat:response:end");
this.chatStreams.delete(chatId);
} else {
console.error(
new Error(
`[IPC] No callbacks found for chat ${chatId} on stream end`,
),
);
}
});
this.ipcRenderer.on("chat:response:error", (payload) => {
console.debug("chat:response:error");
if (
payload &&
typeof payload === "object" &&
"chatId" in payload &&
"error" in payload
) {
const { chatId, error } = payload as { chatId: number; error: string };
const callbacks = this.chatStreams.get(chatId);
if (callbacks) {
callbacks.onError(error);
this.chatStreams.delete(chatId);
} else {
console.warn(
`[IPC] No callbacks found for chat ${chatId} on error`,
this.chatStreams,
);
}
} else {
console.error("[IPC] Invalid error data received:", payload);
}
});
// Help bot events
this.ipcRenderer.on("help:chat:response:chunk", (data) => {
if (
data &&
typeof data === "object" &&
"sessionId" in data &&
"delta" in data
) {
const { sessionId, delta } = data as {
sessionId: string;
delta: string;
};
const callbacks = this.helpStreams.get(sessionId);
if (callbacks) callbacks.onChunk(delta);
}
});
this.ipcRenderer.on("help:chat:response:end", (data) => {
if (data && typeof data === "object" && "sessionId" in data) {
const { sessionId } = data as { sessionId: string };
const callbacks = this.helpStreams.get(sessionId);
if (callbacks) callbacks.onEnd();
this.helpStreams.delete(sessionId);
}
});
this.ipcRenderer.on("help:chat:response:error", (data) => {
if (
data &&
typeof data === "object" &&
"sessionId" in data &&
"error" in data
) {
const { sessionId, error } = data as {
sessionId: string;
error: string;
};
const callbacks = this.helpStreams.get(sessionId);
if (callbacks) callbacks.onError(error);
this.helpStreams.delete(sessionId);
}
});
// MCP tool consent request from main
this.ipcRenderer.on("mcp:tool-consent-request", (payload) => {
const handler = this.mcpConsentHandlers.get("consent");
if (handler) handler(payload);
});
}
public static getInstance(): IpcClient {
if (!IpcClient.instance) {
IpcClient.instance = new IpcClient();
}
return IpcClient.instance;
}
public async restartDyad(): Promise<void> {
await this.ipcRenderer.invoke("restart-dyad");
}
public async reloadEnvPath(): Promise<void> {
await this.ipcRenderer.invoke("reload-env-path");
}
// Create a new app with an initial chat
public async createApp(params: CreateAppParams): Promise<CreateAppResult> {
return this.ipcRenderer.invoke("create-app", params);
}
public async getApp(appId: number): Promise<App> {
return this.ipcRenderer.invoke("get-app", appId);
}
public async addAppToFavorite(
appId: number,
): Promise<{ isFavorite: boolean }> {
try {
const result = await this.ipcRenderer.invoke("add-to-favorite", {
appId,
});
return result;
} catch (error) {
showError(error);
throw error;
}
}
public async getAppEnvVars(
params: GetAppEnvVarsParams,
): Promise<{ key: string; value: string }[]> {
return this.ipcRenderer.invoke("get-app-env-vars", params);
}
public async setAppEnvVars(params: SetAppEnvVarsParams): Promise<void> {
return this.ipcRenderer.invoke("set-app-env-vars", params);
}
public async getChat(chatId: number): Promise<Chat> {
try {
const data = await this.ipcRenderer.invoke("get-chat", chatId);
return data;
} catch (error) {
showError(error);
throw error;
}
}
// Get all chats
public async getChats(appId?: number): Promise<ChatSummary[]> {
try {
const data = await this.ipcRenderer.invoke("get-chats", appId);
return ChatSummariesSchema.parse(data);
} catch (error) {
showError(error);
throw error;
}
}
// search for chats
public async searchChats(
appId: number,
query: string,
): Promise<ChatSearchResult[]> {
try {
const data = await this.ipcRenderer.invoke("search-chats", appId, query);
return ChatSearchResultsSchema.parse(data);
} catch (error) {
showError(error);
throw error;
}
}
// Get all apps
public async listApps(): Promise<ListAppsResponse> {
return this.ipcRenderer.invoke("list-apps");
}
// Search apps by name
public async searchApps(searchQuery: string): Promise<AppSearchResult[]> {
try {
const data = await this.ipcRenderer.invoke("search-app", searchQuery);
return AppSearchResultsSchema.parse(data);
} catch (error) {
showError(error);
throw error;
}
}
public async readAppFile(appId: number, filePath: string): Promise<string> {
return this.ipcRenderer.invoke("read-app-file", {
appId,
filePath,
});
}
// Edit a file in an app directory
public async editAppFile(
appId: number,
filePath: string,
content: string,
): Promise<EditAppFileReturnType> {
return this.ipcRenderer.invoke("edit-app-file", {
appId,
filePath,
content,
});
}
// New method for streaming responses
public streamMessage(
prompt: string,
options: {
selectedComponents?: ComponentSelection[];
chatId: number;
redo?: boolean;
attachments?: FileAttachment[];
onUpdate: (messages: Message[]) => void;
onEnd: (response: ChatResponseEnd) => void;
onError: (error: string) => void;
onProblems?: (problems: ChatProblemsEvent) => void;
},
): void {
const {
chatId,
redo,
attachments,
selectedComponents,
onUpdate,
onEnd,
onError,
} = options;
this.chatStreams.set(chatId, { onUpdate, onEnd, onError });
// Handle file attachments if provided
if (attachments && attachments.length > 0) {
// Process each file attachment and convert to base64
Promise.all(
attachments.map(async (attachment) => {
return new Promise<{
name: string;
type: string;
data: string;
attachmentType: "upload-to-codebase" | "chat-context";
}>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve({
name: attachment.file.name,
type: attachment.file.type,
data: reader.result as string,
attachmentType: attachment.type,
});
};
reader.onerror = () =>
reject(new Error(`Failed to read file: ${attachment.file.name}`));
reader.readAsDataURL(attachment.file);
});
}),
)
.then((fileDataArray) => {
// Use invoke to start the stream and pass the chatId and attachments
this.ipcRenderer
.invoke("chat:stream", {
prompt,
chatId,
redo,
selectedComponents,
attachments: fileDataArray,
})
.catch((err) => {
console.error("Error streaming message:", err);
showError(err);
onError(String(err));
this.chatStreams.delete(chatId);
});
})
.catch((err) => {
console.error("Error streaming message:", err);
showError(err);
onError(String(err));
this.chatStreams.delete(chatId);
});
} else {
// No attachments, proceed normally
this.ipcRenderer
.invoke("chat:stream", {
prompt,
chatId,
redo,
selectedComponents,
})
.catch((err) => {
console.error("Error streaming message:", err);
showError(err);
onError(String(err));
this.chatStreams.delete(chatId);
});
}
}
// Method to cancel an ongoing stream
public cancelChatStream(chatId: number): void {
this.ipcRenderer.invoke("chat:cancel", chatId);
}
// Create a new chat for an app
public async createChat(appId: number): Promise<number> {
return this.ipcRenderer.invoke("create-chat", appId);
}
public async updateChat(params: UpdateChatParams): Promise<void> {
return this.ipcRenderer.invoke("update-chat", params);
}
public async deleteChat(chatId: number): Promise<void> {
await this.ipcRenderer.invoke("delete-chat", chatId);
}
public async deleteMessages(chatId: number): Promise<void> {
await this.ipcRenderer.invoke("delete-messages", chatId);
}
// Open an external URL using the default browser
public async openExternalUrl(url: string): Promise<void> {
await this.ipcRenderer.invoke("open-external-url", url);
}
public async showItemInFolder(fullPath: string): Promise<void> {
await this.ipcRenderer.invoke("show-item-in-folder", fullPath);
}
// Run an app
public async runApp(
appId: number,
onOutput: (output: AppOutput) => void,
): Promise<void> {
await this.ipcRenderer.invoke("run-app", { appId });
this.appStreams.set(appId, { onOutput });
}
// Stop a running app
public async stopApp(appId: number): Promise<void> {
await this.ipcRenderer.invoke("stop-app", { appId });
}
// Restart a running app
public async restartApp(
appId: number,
onOutput: (output: AppOutput) => void,
removeNodeModules?: boolean,
): Promise<{ success: boolean }> {
try {
const result = await this.ipcRenderer.invoke("restart-app", {
appId,
removeNodeModules,
});
this.appStreams.set(appId, { onOutput });
return result;
} catch (error) {
showError(error);
throw error;
}
}
// Respond to an app input request (y/n prompts)
public async respondToAppInput(
params: RespondToAppInputParams,
): Promise<void> {
try {
await this.ipcRenderer.invoke("respond-to-app-input", params);
} catch (error) {
showError(error);
throw error;
}
}
// Get allow-listed environment variables
public async getEnvVars(): Promise<Record<string, string | undefined>> {
try {
const envVars = await this.ipcRenderer.invoke("get-env-vars");
return envVars as Record<string, string | undefined>;
} catch (error) {
showError(error);
throw error;
}
}
// List all versions (commits) of an app
public async listVersions({ appId }: { appId: number }): Promise<Version[]> {
try {
const versions = await this.ipcRenderer.invoke("list-versions", {
appId,
});
return versions;
} catch (error) {
showError(error);
throw error;
}
}
// Revert to a specific version
public async revertVersion(
params: RevertVersionParams,
): Promise<RevertVersionResponse> {
return this.ipcRenderer.invoke("revert-version", params);
}
// Checkout a specific version without creating a revert commit
public async checkoutVersion({
appId,
versionId,
}: {
appId: number;
versionId: string;
}): Promise<void> {
await this.ipcRenderer.invoke("checkout-version", {
appId,
versionId,
});
}
// Get the current branch of an app
public async getCurrentBranch(appId: number): Promise<BranchResult> {
return this.ipcRenderer.invoke("get-current-branch", {
appId,
});
}
// Get user settings
public async getUserSettings(): Promise<UserSettings> {
try {
const settings = await this.ipcRenderer.invoke("get-user-settings");
return settings;
} catch (error) {
showError(error);
throw error;
}
}
// Update user settings
public async setUserSettings(
settings: Partial<UserSettings>,
): Promise<UserSettings> {
try {
const updatedSettings = await this.ipcRenderer.invoke(
"set-user-settings",
settings,
);
return updatedSettings;
} catch (error) {
showError(error);
throw error;
}
}
// Delete an app and all its files
public async deleteApp(appId: number): Promise<void> {
await this.ipcRenderer.invoke("delete-app", { appId });
}
// Rename an app (update name and path)
public async renameApp({
appId,
appName,
appPath,
}: {
appId: number;
appName: string;
appPath: string;
}): Promise<void> {
await this.ipcRenderer.invoke("rename-app", {
appId,
appName,
appPath,
});
}
public async copyApp(params: CopyAppParams): Promise<{ app: App }> {
return this.ipcRenderer.invoke("copy-app", params);
}
// Reset all - removes all app files, settings, and drops the database
public async resetAll(): Promise<void> {
await this.ipcRenderer.invoke("reset-all");
}
public async addDependency({
chatId,
packages,
}: {
chatId: number;
packages: string[];
}): Promise<void> {
await this.ipcRenderer.invoke("chat:add-dep", {
chatId,
packages,
});
}
// Check Node.js and npm status
public async getNodejsStatus(): Promise<NodeSystemInfo> {
return this.ipcRenderer.invoke("nodejs-status");
}
// --- GitHub Device Flow ---
public startGithubDeviceFlow(appId: number | null): void {
this.ipcRenderer.invoke("github:start-flow", { appId });
}
public onGithubDeviceFlowUpdate(
callback: (data: GitHubDeviceFlowUpdateData) => void,
): () => void {
const listener = (data: any) => {
console.log("github:flow-update", data);
callback(data as GitHubDeviceFlowUpdateData);
};
this.ipcRenderer.on("github:flow-update", listener);
// Return a function to remove the listener
return () => {
this.ipcRenderer.removeListener("github:flow-update", listener);
};
}
public onGithubDeviceFlowSuccess(
callback: (data: GitHubDeviceFlowSuccessData) => void,
): () => void {
const listener = (data: any) => {
console.log("github:flow-success", data);
callback(data as GitHubDeviceFlowSuccessData);
};
this.ipcRenderer.on("github:flow-success", listener);
return () => {
this.ipcRenderer.removeListener("github:flow-success", listener);
};
}
public onGithubDeviceFlowError(
callback: (data: GitHubDeviceFlowErrorData) => void,
): () => void {
const listener = (data: any) => {
console.log("github:flow-error", data);
callback(data as GitHubDeviceFlowErrorData);
};
this.ipcRenderer.on("github:flow-error", listener);
return () => {
this.ipcRenderer.removeListener("github:flow-error", listener);
};
}
// --- End GitHub Device Flow ---
// --- GitHub Repo Management ---
public async listGithubRepos(): Promise<
{ name: string; full_name: string; private: boolean }[]
> {
return this.ipcRenderer.invoke("github:list-repos");
}
public async getGithubRepoBranches(
owner: string,
repo: string,
): Promise<{ name: string; commit: { sha: string } }[]> {
return this.ipcRenderer.invoke("github:get-repo-branches", {
owner,
repo,
});
}
public async connectToExistingGithubRepo(
owner: string,
repo: string,
branch: string,
appId: number,
): Promise<void> {
await this.ipcRenderer.invoke("github:connect-existing-repo", {
owner,
repo,
branch,
appId,
});
}
public async checkGithubRepoAvailable(
org: string,
repo: string,
): Promise<{ available: boolean; error?: string }> {
return this.ipcRenderer.invoke("github:is-repo-available", {
org,
repo,
});
}
public async createGithubRepo(
org: string,
repo: string,
appId: number,
branch?: string,
): Promise<void> {
await this.ipcRenderer.invoke("github:create-repo", {
org,
repo,
appId,
branch,
});
}
// Sync (push) local repo to GitHub
public async syncGithubRepo(
appId: number,
force?: boolean,
): Promise<{ success: boolean; error?: string }> {
return this.ipcRenderer.invoke("github:push", {
appId,
force,
});
}
public async disconnectGithubRepo(appId: number): Promise<void> {
await this.ipcRenderer.invoke("github:disconnect", {
appId,
});
}
// --- End GitHub Repo Management ---
// --- Vercel Token Management ---
public async saveVercelAccessToken(
params: SaveVercelAccessTokenParams,
): Promise<void> {
await this.ipcRenderer.invoke("vercel:save-token", params);
}
// --- End Vercel Token Management ---
// --- Vercel Project Management ---
public async listVercelProjects(): Promise<VercelProject[]> {
return this.ipcRenderer.invoke("vercel:list-projects", undefined);
}
public async connectToExistingVercelProject(
params: ConnectToExistingVercelProjectParams,
): Promise<void> {
await this.ipcRenderer.invoke("vercel:connect-existing-project", params);
}
public async isVercelProjectAvailable(
params: IsVercelProjectAvailableParams,
): Promise<IsVercelProjectAvailableResponse> {
return this.ipcRenderer.invoke("vercel:is-project-available", params);
}
public async createVercelProject(
params: CreateVercelProjectParams,
): Promise<void> {
await this.ipcRenderer.invoke("vercel:create-project", params);
}
// Get Vercel Deployments
public async getVercelDeployments(
params: GetVercelDeploymentsParams,
): Promise<VercelDeployment[]> {
return this.ipcRenderer.invoke("vercel:get-deployments", params);
}
public async disconnectVercelProject(
params: DisconnectVercelProjectParams,
): Promise<void> {
await this.ipcRenderer.invoke("vercel:disconnect", params);
}
// --- End Vercel Project Management ---
// Get the main app version
public async getAppVersion(): Promise<string> {
const result = await this.ipcRenderer.invoke("get-app-version");
return result.version as string;
}
// --- MCP Client Methods ---
public async listMcpServers() {
return this.ipcRenderer.invoke("mcp:list-servers");
}
public async createMcpServer(params: CreateMcpServer) {
return this.ipcRenderer.invoke("mcp:create-server", params);
}
public async updateMcpServer(params: McpServerUpdate) {
return this.ipcRenderer.invoke("mcp:update-server", params);
}
public async deleteMcpServer(id: number) {
return this.ipcRenderer.invoke("mcp:delete-server", id);
}
public async listMcpTools(serverId: number) {
return this.ipcRenderer.invoke("mcp:list-tools", serverId);
}
// Removed: upsertMcpTools and setMcpToolActive tools are fetched dynamically at runtime
public async getMcpToolConsents() {
return this.ipcRenderer.invoke("mcp:get-tool-consents");
}
public async setMcpToolConsent(params: {
serverId: number;
toolName: string;
consent: "ask" | "always" | "denied";
}) {
return this.ipcRenderer.invoke("mcp:set-tool-consent", params);
}
public onMcpToolConsentRequest(
handler: (payload: {
requestId: string;
serverId: number;
serverName: string;
toolName: string;
toolDescription?: string | null;
inputPreview?: string | null;
}) => void,
) {
this.mcpConsentHandlers.set("consent", handler as any);
return () => {
this.mcpConsentHandlers.delete("consent");
};
}
public respondToMcpConsentRequest(
requestId: string,
decision: "accept-once" | "accept-always" | "decline",
) {
this.ipcRenderer.invoke("mcp:tool-consent-response", {
requestId,
decision,
});
}
// Get proposal details
public async getProposal(chatId: number): Promise<ProposalResult | null> {
try {
const data = await this.ipcRenderer.invoke("get-proposal", { chatId });
// Assuming the main process returns data matching the ProposalResult interface
// Add a type check/guard if necessary for robustness
return data as ProposalResult | null;
} catch (error) {
showError(error);
throw error;
}
}
// Example methods for listening to events (if needed)
// public on(channel: string, func: (...args: any[]) => void): void {
// --- Proposal Management ---
public async approveProposal({
chatId,
messageId,
}: {
chatId: number;
messageId: number;
}): Promise<ApproveProposalResult> {
return this.ipcRenderer.invoke("approve-proposal", {
chatId,
messageId,
});
}
public async rejectProposal({
chatId,
messageId,
}: {
chatId: number;
messageId: number;
}): Promise<void> {
await this.ipcRenderer.invoke("reject-proposal", {
chatId,
messageId,
});
}
// --- End Proposal Management ---
// --- Supabase Management ---
public async listSupabaseProjects(): Promise<any[]> {
return this.ipcRenderer.invoke("supabase:list-projects");
}
public async listSupabaseBranches(params: {
projectId: string;
}): Promise<SupabaseBranch[]> {
return this.ipcRenderer.invoke("supabase:list-branches", params);
}
public async setSupabaseAppProject(
params: SetSupabaseAppProjectParams,
): Promise<void> {
await this.ipcRenderer.invoke("supabase:set-app-project", params);
}
public async unsetSupabaseAppProject(app: number): Promise<void> {
await this.ipcRenderer.invoke("supabase:unset-app-project", {
app,
});
}
public async fakeHandleSupabaseConnect(params: {
appId: number;
fakeProjectId: string;
}): Promise<void> {
await this.ipcRenderer.invoke(
"supabase:fake-connect-and-set-project",
params,
);
}
// --- End Supabase Management ---
// --- Neon Management ---
public async fakeHandleNeonConnect(): Promise<void> {
await this.ipcRenderer.invoke("neon:fake-connect");
}
public async createNeonProject(
params: CreateNeonProjectParams,
): Promise<NeonProject> {
return this.ipcRenderer.invoke("neon:create-project", params);
}
public async getNeonProject(
params: GetNeonProjectParams,
): Promise<GetNeonProjectResponse> {
return this.ipcRenderer.invoke("neon:get-project", params);
}
// --- End Neon Management ---
// --- Portal Management ---
public async portalMigrateCreate(params: {
appId: number;
}): Promise<{ output: string }> {
return this.ipcRenderer.invoke("portal:migrate-create", params);
}
// --- End Portal Management ---
public async getSystemDebugInfo(): Promise<SystemDebugInfo> {
return this.ipcRenderer.invoke("get-system-debug-info");
}
public async getChatLogs(chatId: number): Promise<ChatLogsData> {
return this.ipcRenderer.invoke("get-chat-logs", chatId);
}
public async uploadToSignedUrl(
url: string,
contentType: string,
data: any,
): Promise<void> {
await this.ipcRenderer.invoke("upload-to-signed-url", {
url,
contentType,
data,
});
}
public async listLocalOllamaModels(): Promise<LocalModel[]> {
const response = await this.ipcRenderer.invoke("local-models:list-ollama");
return response?.models || [];
}
public async listLocalLMStudioModels(): Promise<LocalModel[]> {
const response = await this.ipcRenderer.invoke(
"local-models:list-lmstudio",
);
return response?.models || [];
}
// Listen for deep link events
public onDeepLinkReceived(
callback: (data: DeepLinkData) => void,
): () => void {
const listener = (data: any) => {
callback(data as DeepLinkData);
};
this.ipcRenderer.on("deep-link-received", listener);
return () => {
this.ipcRenderer.removeListener("deep-link-received", listener);
};
}
// Count tokens for a chat and input
public async countTokens(
params: TokenCountParams,
): Promise<TokenCountResult> {
try {
const result = await this.ipcRenderer.invoke("chat:count-tokens", params);
return result as TokenCountResult;
} catch (error) {
showError(error);
throw error;
}
}
// Window control methods
public async minimizeWindow(): Promise<void> {
try {
await this.ipcRenderer.invoke("window:minimize");
} catch (error) {
showError(error);
throw error;
}
}
public async maximizeWindow(): Promise<void> {
try {
await this.ipcRenderer.invoke("window:maximize");
} catch (error) {
showError(error);
throw error;
}
}
public async closeWindow(): Promise<void> {
try {
await this.ipcRenderer.invoke("window:close");
} catch (error) {
showError(error);
throw error;
}
}
// Get system platform (win32, darwin, linux)
public async getSystemPlatform(): Promise<string> {
return this.ipcRenderer.invoke("get-system-platform");
}
public async doesReleaseNoteExist(
params: DoesReleaseNoteExistParams,
): Promise<{ exists: boolean; url?: string }> {
return this.ipcRenderer.invoke("does-release-note-exist", params);
}
public async getLanguageModelProviders(): Promise<LanguageModelProvider[]> {
return this.ipcRenderer.invoke("get-language-model-providers");
}
public async getLanguageModels(params: {
providerId: string;
}): Promise<LanguageModel[]> {
return this.ipcRenderer.invoke("get-language-models", params);
}
public async getLanguageModelsByProviders(): Promise<
Record<string, LanguageModel[]>
> {
return this.ipcRenderer.invoke("get-language-models-by-providers");
}
public async createCustomLanguageModelProvider({
id,
name,
apiBaseUrl,
envVarName,
}: CreateCustomLanguageModelProviderParams): Promise<LanguageModelProvider> {
return this.ipcRenderer.invoke("create-custom-language-model-provider", {
id,
name,
apiBaseUrl,
envVarName,
});
}
public async editCustomLanguageModelProvider(
params: CreateCustomLanguageModelProviderParams,
): Promise<LanguageModelProvider> {
return this.ipcRenderer.invoke(
"edit-custom-language-model-provider",
params,
);
}
public async createCustomLanguageModel(
params: CreateCustomLanguageModelParams,
): Promise<void> {
await this.ipcRenderer.invoke("create-custom-language-model", params);
}
public async deleteCustomLanguageModel(modelId: string): Promise<void> {
return this.ipcRenderer.invoke("delete-custom-language-model", modelId);
}
async deleteCustomModel(params: DeleteCustomModelParams): Promise<void> {
return this.ipcRenderer.invoke("delete-custom-model", params);
}
async deleteCustomLanguageModelProvider(providerId: string): Promise<void> {
return this.ipcRenderer.invoke("delete-custom-language-model-provider", {
providerId,
});
}
public async selectAppFolder(): Promise<{
path: string | null;
name: string | null;
}> {
return this.ipcRenderer.invoke("select-app-folder");
}
// Add these methods to IpcClient class
public async selectNodeFolder(): Promise<SelectNodeFolderResult> {
return this.ipcRenderer.invoke("select-node-folder");
}
public async getNodePath(): Promise<string | null> {
return this.ipcRenderer.invoke("get-node-path");
}
public async checkAiRules(params: {
path: string;
}): Promise<{ exists: boolean }> {
return this.ipcRenderer.invoke("check-ai-rules", params);
}
public async getLatestSecurityReview(
appId: number,
): Promise<SecurityReviewResult> {
return this.ipcRenderer.invoke("get-latest-security-review", appId);
}
public async importApp(params: ImportAppParams): Promise<ImportAppResult> {
return this.ipcRenderer.invoke("import-app", params);
}
async checkAppName(params: {
appName: string;
}): Promise<{ exists: boolean }> {
return this.ipcRenderer.invoke("check-app-name", params);
}
public async renameBranch(params: RenameBranchParams): Promise<void> {
await this.ipcRenderer.invoke("rename-branch", params);
}
async clearSessionData(): Promise<void> {
return this.ipcRenderer.invoke("clear-session-data");
}
// Method to get user budget information
public async getUserBudget(): Promise<UserBudgetInfo | null> {
return this.ipcRenderer.invoke("get-user-budget");
}
public async getChatContextResults(params: {
appId: number;
}): Promise<ContextPathResults> {
return this.ipcRenderer.invoke("get-context-paths", params);
}
public async setChatContext(params: {
appId: number;
chatContext: AppChatContext;
}): Promise<void> {
await this.ipcRenderer.invoke("set-context-paths", params);
}
public async getAppUpgrades(params: {
appId: number;
}): Promise<AppUpgrade[]> {
return this.ipcRenderer.invoke("get-app-upgrades", params);
}
public async executeAppUpgrade(params: {
appId: number;
upgradeId: string;
}): Promise<void> {
return this.ipcRenderer.invoke("execute-app-upgrade", params);
}
// Capacitor methods
public async isCapacitor(params: { appId: number }): Promise<boolean> {
return this.ipcRenderer.invoke("is-capacitor", params);
}
public async syncCapacitor(params: { appId: number }): Promise<void> {
return this.ipcRenderer.invoke("sync-capacitor", params);
}
public async openIos(params: { appId: number }): Promise<void> {
return this.ipcRenderer.invoke("open-ios", params);
}
public async openAndroid(params: { appId: number }): Promise<void> {
return this.ipcRenderer.invoke("open-android", params);
}
public async checkProblems(params: {
appId: number;
}): Promise<ProblemReport> {
return this.ipcRenderer.invoke("check-problems", params);
}
// Template methods
public async getTemplates(): Promise<Template[]> {
return this.ipcRenderer.invoke("get-templates");
}
// --- Prompts Library ---
public async listPrompts(): Promise<PromptDto[]> {
return this.ipcRenderer.invoke("prompts:list");
}
public async createPrompt(params: CreatePromptParamsDto): Promise<PromptDto> {
return this.ipcRenderer.invoke("prompts:create", params);
}
public async updatePrompt(params: UpdatePromptParamsDto): Promise<void> {
await this.ipcRenderer.invoke("prompts:update", params);
}
public async deletePrompt(id: number): Promise<void> {
await this.ipcRenderer.invoke("prompts:delete", id);
}
public async cloneRepoFromUrl(
params: CloneRepoParams,
): Promise<{ app: App; hasAiRules: boolean } | { error: string }> {
return this.ipcRenderer.invoke("github:clone-repo-from-url", params);
}
// --- Help bot ---
public startHelpChat(
sessionId: string,
message: string,
options: {
onChunk: (delta: string) => void;
onEnd: () => void;
onError: (error: string) => void;
},
): void {
this.helpStreams.set(sessionId, options);
this.ipcRenderer
.invoke("help:chat:start", { sessionId, message })
.catch((err) => {
this.helpStreams.delete(sessionId);
showError(err);
options.onError(String(err));
});
}
public cancelHelpChat(sessionId: string): void {
this.ipcRenderer.invoke("help:chat:cancel", sessionId).catch(() => {});
}
}