Skip to content

Commit

Permalink
Merge pull request #18 from tolgayayci/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
tolgayayci authored Jul 14, 2024
2 parents f6e15d8 + b3dd787 commit 609bb36
Show file tree
Hide file tree
Showing 88 changed files with 12,060 additions and 246 deletions.
95 changes: 91 additions & 4 deletions main/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { findContracts } from "./helpers/find-contracts";
import { checkEditors } from "./helpers/check-editors";
import { openProjectInEditor } from "./helpers/open-project-in-editor";
import { handleContractEvents } from "./helpers/manage-contract-events";
import * as OpenAIHelper from "./helpers/openai-helper";

const path = require("node:path");
const fs = require("fs");
Expand Down Expand Up @@ -94,10 +95,16 @@ const schema = {
],
},
},
conversation: {
type: "object",
default: {
threadId: "",
assistantId: "",
},
},
};

const store = new Store({ schema });
store.set("identities", []);

// Aptabase Analytics
initialize("A-EU-8145589126");
Expand Down Expand Up @@ -144,8 +151,6 @@ if (isProd) {
trackEvent("app_started");
autoUpdater.checkForUpdatesAndNotify();

console.log(store.get("contracts"));

const mainWindow = createWindow("main", {
width: 1500,
height: 700,
Expand All @@ -167,6 +172,86 @@ if (isProd) {
}
});

ipcMain.handle("openai:saveApiKey", async (_, apiKey: string) => {
return await OpenAIHelper.saveApiKey(apiKey);
});

ipcMain.handle("openai:getApiKey", async () => {
return await OpenAIHelper.getApiKey();
});

ipcMain.handle("openai:deleteApiKey", async () => {
return await OpenAIHelper.deleteApiKey();
});

ipcMain.handle("openai:createGeneralAssistant", async () => {
return await OpenAIHelper.createGeneralAssistant();
});

ipcMain.handle("openai:createCliAssistant", async () => {
return await OpenAIHelper.createCliAssistant();
});

ipcMain.handle("openai:createThread", async (_, initialMessage?: string) => {
return await OpenAIHelper.createThread(initialMessage);
});

ipcMain.handle(
"openai:sendMessage",
async (_, threadId: string, message: string) => {
return await OpenAIHelper.sendMessage(threadId, message);
}
);

ipcMain.handle(
"openai:runAssistant",
async (_, threadId: string, assistantId: string) => {
return await OpenAIHelper.runAssistant(threadId, assistantId);
}
);

ipcMain.handle(
"openai:getRunStatus",
async (_, threadId: string, runId: string) => {
return await OpenAIHelper.getRunStatus(threadId, runId);
}
);

ipcMain.handle("openai:getMessages", async (_, threadId: string) => {
return await OpenAIHelper.getMessages(threadId);
});

ipcMain.handle(
"openai:saveConversation",
async (
_,
threadId: string,
assistantId: string,
assistantType: "general" | "cli"
) => {
return await OpenAIHelper.saveConversation(
store,
threadId,
assistantId,
assistantType
);
}
);

ipcMain.handle(
"openai:clearConversation",
async (_, assistantType: "general" | "cli") => {
return await OpenAIHelper.clearConversation(store, assistantType);
}
);

ipcMain.handle(
"openai:getConversation",
async (_, assistantType: "general" | "cli") => {
return await OpenAIHelper.getConversation(store, assistantType);
}
);

ipcMain.handle("check-editors", async () => {
return await checkEditors();
});
Expand Down Expand Up @@ -238,7 +323,9 @@ if (isProd) {

if (
command &&
(command === "contract" || command === "lab" || command === "events")
(command === "contract" ||
command === "lab xdr" ||
command === "events")
) {
const formattedResult = result
? `Result: ${JSON.stringify(result)}`
Expand Down
1 change: 0 additions & 1 deletion main/helpers/manage-contract-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export function handleContractEvents(store, action, contractSettings) {
typeof contractSettings === "object" &&
!Array.isArray(contractSettings) && // Make sure it's not an array
contractSettings.start_ledger &&
contractSettings.cursor &&
contractSettings.rpc_url &&
contractSettings.network_passphrase &&
contractSettings.network
Expand Down
234 changes: 234 additions & 0 deletions main/helpers/openai-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { safeStorage } from "electron";
import * as fs from "fs";
import * as path from "path";

const OpenAI = require("openai");
let openai: any;

const API_KEY_FILE = "openai_api_key.enc";

function getEncryptedFilePath() {
return path.join(
process.env.APPDATA ||
(process.platform == "darwin"
? process.env.HOME + "/Library/Preferences"
: process.env.HOME + "/.local/share"),
API_KEY_FILE
);
}

export function initializeOpenAI(apiKey: string) {
openai = new OpenAI({
apiKey: apiKey,
});
}

export async function saveApiKey(apiKey: string) {
try {
const encryptedKey = safeStorage.encryptString(apiKey);
fs.writeFileSync(getEncryptedFilePath(), encryptedKey);
initializeOpenAI(apiKey);
return true;
} catch (error) {
console.error("Error saving API key:", error);
return false;
}
}

export async function getApiKey() {
try {
const encryptedKey = fs.readFileSync(getEncryptedFilePath());
return safeStorage.decryptString(encryptedKey);
} catch (error) {
console.error("Error retrieving API key:", error);
return null;
}
}

export async function deleteApiKey() {
try {
fs.unlinkSync(getEncryptedFilePath());
return true;
} catch (error) {
console.error("Error deleting API key:", error);
return false;
}
}

async function ensureApiKey() {
if (!openai) {
const apiKey = await getApiKey();
if (!apiKey) {
throw new Error("API key not set. Please set your OpenAI API key.");
}
initializeOpenAI(apiKey);
}
}

export async function createGeneralAssistant() {
try {
await ensureApiKey();
const generalAssistant = await openai.beta.assistants.create({
name: "Soroban and Stellar General Assistant",
instructions:
"You are an AI assistant specializing in Soroban development and Stellar. Write and run code to answer questions related to these topics.",
tools: [{ type: "code_interpreter" }],
model: "gpt-4-1106-preview",
});
return generalAssistant;
} catch (error) {
console.error("Error creating general assistant:", error);
throw error;
}
}

export async function createCliAssistant() {
try {
await ensureApiKey();
const cliAssistant = await openai.beta.assistants.create({
name: "Soroban Command Assistant",
instructions:
"You are an AI assistant specialized in generating Soroban CLI commands. Your task is to interpret user requests and generate appropriate Soroban CLI commands based on the official documentation (https://github.com/stellar/stellar-cli/blob/v20.3.4/docs/soroban-cli-full-docs.md). When a user provides a task description, analyze it carefully to determine the appropriate Soroban CLI command. Follow these guidelines when generating commands: 1. Only generate commands related to Soroban contracts (soroban contract xxxx). 2. Ensure the command syntax matches the official documentation. 3. If the task description is unclear or lacks necessary information, do not ask for clarification. Instead, make reasonable assumptions based on common use cases. 4. Do not provide explanations or additional text beyond the command itself. Output your generated command like a codeblock soroban contract invoke --id CONTRACT_ID --method METHOD_NAME --arg ARG1 --arg ARG2 Remember, you are only responsible for generating Soroban contract-related commands. Do not respond to requests outside of this scope or provide any additional information or explanations.",
tools: [{ type: "code_interpreter" }],
model: "gpt-4-1106-preview",
});
return cliAssistant;
} catch (error) {
console.error("Error creating CLI assistant:", error);
throw error;
}
}

export async function createThread(initialMessage?: string) {
try {
await ensureApiKey();
const thread = await openai.beta.threads.create(
initialMessage
? {
messages: [
{
role: "user",
content: initialMessage,
},
],
}
: undefined
);
return thread;
} catch (error) {
console.error("Error creating thread:", error);
throw error;
}
}

export async function sendMessage(threadId: string, message: string) {
try {
await ensureApiKey();
const createdMessage = await openai.beta.threads.messages.create(threadId, {
role: "user",
content: message,
});
return createdMessage;
} catch (error) {
console.error("Error sending message:", error);
throw error;
}
}

export async function runAssistant(threadId: string, assistantId: string) {
try {
await ensureApiKey();
const run = await openai.beta.threads.runs.create(threadId, {
assistant_id: assistantId,
});
return run;
} catch (error) {
console.error("Error running assistant:", error);
throw error;
}
}

export async function getRunStatus(threadId: string, runId: string) {
try {
await ensureApiKey();
const run = await openai.beta.threads.runs.retrieve(threadId, runId);
return run.status;
} catch (error) {
console.error("Error getting run status:", error);
throw error;
}
}

export async function getMessages(threadId: string) {
try {
await ensureApiKey();
const messages = await openai.beta.threads.messages.list(threadId);
return messages.data;
} catch (error) {
console.error("Error getting messages:", error);
throw error;
}
}

export async function saveConversation(
store: any,
threadId: string,
assistantId: string,
assistantType: "general" | "cli"
) {
try {
const key = `conversation_${assistantType}`;
store.set(key, { threadId, assistantId });
console.log(
`${
assistantType.charAt(0).toUpperCase() + assistantType.slice(1)
} conversation saved successfully:`,
{ threadId, assistantId }
);
} catch (error) {
console.error(`Error saving ${assistantType} conversation:`, error);
throw error;
}
}

export async function clearConversation(
store: any,
assistantType: "general" | "cli"
) {
try {
const key = `conversation_${assistantType}`;
store.delete(key);
console.log(
`${
assistantType.charAt(0).toUpperCase() + assistantType.slice(1)
} conversation cleared successfully`
);
} catch (error) {
console.error(`Error clearing ${assistantType} conversation:`, error);
throw error;
}
}

export async function getConversation(
store: any,
assistantType: "general" | "cli"
) {
try {
const key = `conversation_${assistantType}`;
const conversation = store.get(key);
if (conversation) {
console.log(
`${
assistantType.charAt(0).toUpperCase() + assistantType.slice(1)
} conversation retrieved successfully`
);
return conversation;
} else {
console.log(`No saved ${assistantType} conversation found`);
return null;
}
} catch (error) {
console.error(`Error getting ${assistantType} conversation:`, error);
throw error;
}
}
Loading

0 comments on commit 609bb36

Please sign in to comment.