Create Upload Chat Session help feature (#48)
This commit is contained in:
@@ -1,92 +1,156 @@
|
||||
import { ipcMain, app } from "electron";
|
||||
import { platform, arch } from "os";
|
||||
import { SystemDebugInfo } from "../ipc_types";
|
||||
import { SystemDebugInfo, ChatLogsData } from "../ipc_types";
|
||||
import { readSettings } from "../../main/settings";
|
||||
import { execSync } from "child_process";
|
||||
import log from "electron-log";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { runShellCommand } from "../utils/runShellCommand";
|
||||
import { extractCodebase } from "../../utils/codebase";
|
||||
import { db } from "../../db";
|
||||
import { chats, apps } from "../../db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getDyadAppPath } from "../../paths/paths";
|
||||
|
||||
// Shared function to get system debug info
|
||||
async function getSystemDebugInfo(): Promise<SystemDebugInfo> {
|
||||
console.log("Getting system debug info");
|
||||
|
||||
// Get Node.js and pnpm versions
|
||||
let nodeVersion: string | null = null;
|
||||
let pnpmVersion: string | null = null;
|
||||
let nodePath: string | null = null;
|
||||
try {
|
||||
nodeVersion = await runShellCommand("node --version");
|
||||
} catch (err) {
|
||||
console.error("Failed to get Node.js version:", err);
|
||||
}
|
||||
|
||||
try {
|
||||
pnpmVersion = await runShellCommand("pnpm --version");
|
||||
} catch (err) {
|
||||
console.error("Failed to get pnpm version:", err);
|
||||
}
|
||||
|
||||
try {
|
||||
if (platform() === "win32") {
|
||||
nodePath = await runShellCommand("where.exe node");
|
||||
} else {
|
||||
nodePath = await runShellCommand("which node");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to get node path:", err);
|
||||
}
|
||||
|
||||
// Get Dyad version from package.json
|
||||
const packageJsonPath = path.resolve(__dirname, "..", "..", "package.json");
|
||||
let dyadVersion = "unknown";
|
||||
try {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
||||
dyadVersion = packageJson.version;
|
||||
} catch (err) {
|
||||
console.error("Failed to read package.json:", err);
|
||||
}
|
||||
|
||||
// Get telemetry info from settings
|
||||
const settings = readSettings();
|
||||
const telemetryId = settings.telemetryUserId || "unknown";
|
||||
|
||||
// Get logs from electron-log
|
||||
let logs = "";
|
||||
try {
|
||||
const logPath = log.transports.file.getFile().path;
|
||||
if (fs.existsSync(logPath)) {
|
||||
const logContent = fs.readFileSync(logPath, "utf8");
|
||||
const logLines = logContent.split("\n");
|
||||
logs = logLines.slice(-100).join("\n");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to read log file:", err);
|
||||
logs = `Error reading logs: ${err}`;
|
||||
}
|
||||
|
||||
return {
|
||||
nodeVersion,
|
||||
pnpmVersion,
|
||||
nodePath,
|
||||
telemetryId,
|
||||
telemetryConsent: settings.telemetryConsent || "unknown",
|
||||
telemetryUrl: "https://us.i.posthog.com", // Hardcoded from renderer.tsx
|
||||
dyadVersion,
|
||||
platform: process.platform,
|
||||
architecture: arch(),
|
||||
logs,
|
||||
};
|
||||
}
|
||||
|
||||
export function registerDebugHandlers() {
|
||||
ipcMain.handle(
|
||||
"get-system-debug-info",
|
||||
async (): Promise<SystemDebugInfo> => {
|
||||
console.log("IPC: get-system-debug-info called");
|
||||
return getSystemDebugInfo();
|
||||
}
|
||||
);
|
||||
|
||||
// Get Node.js and pnpm versions
|
||||
let nodeVersion: string | null = null;
|
||||
let pnpmVersion: string | null = null;
|
||||
let nodePath: string | null = null;
|
||||
try {
|
||||
nodeVersion = await runShellCommand("node --version");
|
||||
} catch (err) {
|
||||
console.error("Failed to get Node.js version:", err);
|
||||
}
|
||||
ipcMain.handle(
|
||||
"get-chat-logs",
|
||||
async (_, chatId: number): Promise<ChatLogsData> => {
|
||||
console.log(`IPC: get-chat-logs called for chat ${chatId}`);
|
||||
|
||||
try {
|
||||
pnpmVersion = await runShellCommand("pnpm --version");
|
||||
} catch (err) {
|
||||
console.error("Failed to get pnpm version:", err);
|
||||
}
|
||||
// Get system debug info using the shared function
|
||||
const debugInfo = await getSystemDebugInfo();
|
||||
|
||||
try {
|
||||
if (platform() === "win32") {
|
||||
nodePath = await runShellCommand("where.exe node");
|
||||
} else {
|
||||
nodePath = await runShellCommand("which node");
|
||||
// Get chat data from database
|
||||
const chatRecord = await db.query.chats.findFirst({
|
||||
where: eq(chats.id, chatId),
|
||||
with: {
|
||||
messages: {
|
||||
orderBy: (messages, { asc }) => [asc(messages.createdAt)],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!chatRecord) {
|
||||
throw new Error(`Chat with ID ${chatId} not found`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to get node path:", err);
|
||||
}
|
||||
|
||||
// Get Dyad version from package.json
|
||||
const packageJsonPath = path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"package.json"
|
||||
);
|
||||
let dyadVersion = "unknown";
|
||||
try {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(packageJsonPath, "utf8")
|
||||
);
|
||||
dyadVersion = packageJson.version;
|
||||
} catch (err) {
|
||||
console.error("Failed to read package.json:", err);
|
||||
}
|
||||
// Format the chat to match the Chat interface
|
||||
const chat = {
|
||||
id: chatRecord.id,
|
||||
title: chatRecord.title || "Untitled Chat",
|
||||
messages: chatRecord.messages.map((msg) => ({
|
||||
id: msg.id,
|
||||
role: msg.role,
|
||||
content: msg.content,
|
||||
approvalState: msg.approvalState,
|
||||
})),
|
||||
};
|
||||
|
||||
// Get telemetry info from settings
|
||||
const settings = readSettings();
|
||||
const telemetryId = settings.telemetryUserId || "unknown";
|
||||
// Get app data from database
|
||||
const app = await db.query.apps.findFirst({
|
||||
where: eq(apps.id, chatRecord.appId),
|
||||
});
|
||||
|
||||
// Get logs from electron-log
|
||||
let logs = "";
|
||||
try {
|
||||
const logPath = log.transports.file.getFile().path;
|
||||
if (fs.existsSync(logPath)) {
|
||||
const logContent = fs.readFileSync(logPath, "utf8");
|
||||
const logLines = logContent.split("\n");
|
||||
logs = logLines.slice(-100).join("\n");
|
||||
if (!app) {
|
||||
throw new Error(`App with ID ${chatRecord.appId} not found`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to read log file:", err);
|
||||
logs = `Error reading logs: ${err}`;
|
||||
}
|
||||
|
||||
return {
|
||||
nodeVersion,
|
||||
pnpmVersion,
|
||||
nodePath,
|
||||
telemetryId,
|
||||
telemetryConsent: settings.telemetryConsent || "unknown",
|
||||
telemetryUrl: "https://us.i.posthog.com", // Hardcoded from renderer.tsx
|
||||
dyadVersion,
|
||||
platform: process.platform,
|
||||
architecture: arch(),
|
||||
logs,
|
||||
};
|
||||
// Extract codebase
|
||||
const appPath = getDyadAppPath(app.path);
|
||||
const codebase = await extractCodebase(appPath);
|
||||
|
||||
return {
|
||||
debugInfo,
|
||||
chat,
|
||||
codebase,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error in get-chat-logs:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
59
src/ipc/handlers/upload_handlers.ts
Normal file
59
src/ipc/handlers/upload_handlers.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { ipcMain } from "electron";
|
||||
import log from "electron-log";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
const logger = log.scope("upload_handlers");
|
||||
|
||||
interface UploadToSignedUrlParams {
|
||||
url: string;
|
||||
contentType: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export function registerUploadHandlers() {
|
||||
ipcMain.handle(
|
||||
"upload-to-signed-url",
|
||||
async (_, params: UploadToSignedUrlParams) => {
|
||||
const { url, contentType, data } = params;
|
||||
logger.debug("IPC: upload-to-signed-url called");
|
||||
|
||||
try {
|
||||
// Validate the signed URL
|
||||
if (!url || typeof url !== "string" || !url.startsWith("https://")) {
|
||||
throw new Error("Invalid signed URL provided");
|
||||
}
|
||||
|
||||
// Validate content type
|
||||
if (!contentType || typeof contentType !== "string") {
|
||||
throw new Error("Invalid content type provided");
|
||||
}
|
||||
|
||||
// Perform the upload to the signed URL
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Upload failed with status ${response.status}: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug("Successfully uploaded data to signed URL");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error("Failed to upload to signed URL:", error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
logger.debug("Registered upload IPC handlers");
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
LocalModelListResponse,
|
||||
TokenCountParams,
|
||||
TokenCountResult,
|
||||
ChatLogsData,
|
||||
} from "./ipc_types";
|
||||
import type { CodeProposal, ProposalResult } from "@/lib/schemas";
|
||||
import { showError } from "@/lib/toast";
|
||||
@@ -749,7 +750,35 @@ export class IpcClient {
|
||||
public async getSystemDebugInfo(): Promise<SystemDebugInfo> {
|
||||
try {
|
||||
const data = await this.ipcRenderer.invoke("get-system-debug-info");
|
||||
return data;
|
||||
return data as SystemDebugInfo;
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async getChatLogs(chatId: number): Promise<ChatLogsData> {
|
||||
try {
|
||||
const data = await this.ipcRenderer.invoke("get-chat-logs", chatId);
|
||||
return data as ChatLogsData;
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async uploadToSignedUrl(
|
||||
url: string,
|
||||
contentType: string,
|
||||
data: any
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
const result = await this.ipcRenderer.invoke("upload-to-signed-url", {
|
||||
url,
|
||||
contentType,
|
||||
data,
|
||||
});
|
||||
return result as { success: boolean; error?: string };
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
throw error;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { registerSupabaseHandlers } from "./handlers/supabase_handlers";
|
||||
import { registerLocalModelHandlers } from "./handlers/local_model_handlers";
|
||||
import { registerTokenCountHandlers } from "./handlers/token_count_handlers";
|
||||
import { registerWindowHandlers } from "./handlers/window_handlers";
|
||||
import { registerUploadHandlers } from "./handlers/upload_handlers";
|
||||
|
||||
export function registerIpcHandlers() {
|
||||
// Register all IPC handlers by category
|
||||
@@ -29,4 +30,5 @@ export function registerIpcHandlers() {
|
||||
registerLocalModelHandlers();
|
||||
registerTokenCountHandlers();
|
||||
registerWindowHandlers();
|
||||
registerUploadHandlers();
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ export interface TokenCountParams {
|
||||
chatId: number;
|
||||
input: string;
|
||||
}
|
||||
|
||||
export interface TokenCountResult {
|
||||
totalTokens: number;
|
||||
messageHistoryTokens: number;
|
||||
@@ -114,3 +115,9 @@ export interface TokenCountResult {
|
||||
systemPromptTokens: number;
|
||||
contextWindow: number;
|
||||
}
|
||||
|
||||
export interface ChatLogsData {
|
||||
debugInfo: SystemDebugInfo;
|
||||
chat: Chat;
|
||||
codebase: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user