added functionality to gui to create your own patterns
This commit is contained in:
parent
3121730102
commit
7eb5f953d7
@ -1,45 +0,0 @@
|
||||
const { OpenAI } = require("openai");
|
||||
require("dotenv").config({
|
||||
path: require("os").homedir() + "/.config/fabric/.env",
|
||||
});
|
||||
|
||||
let openaiClient = null;
|
||||
|
||||
// Function to initialize and get the OpenAI client
|
||||
function getOpenAIClient() {
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
throw new Error(
|
||||
"The OPENAI_API_KEY environment variable is missing or empty."
|
||||
);
|
||||
}
|
||||
return new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
}
|
||||
|
||||
async function queryOpenAI(system, user, callback) {
|
||||
const openai = getOpenAIClient(); // Ensure the client is initialized here
|
||||
const messages = [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: user },
|
||||
];
|
||||
try {
|
||||
const stream = await openai.chat.completions.create({
|
||||
model: "gpt-4-1106-preview", // Adjust the model as necessary.
|
||||
messages: messages,
|
||||
temperature: 0.0,
|
||||
top_p: 1,
|
||||
frequency_penalty: 0.1,
|
||||
presence_penalty: 0.1,
|
||||
stream: true,
|
||||
});
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const message = chunk.choices[0]?.delta?.content || "";
|
||||
callback(message); // Process each chunk of data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error querying OpenAI:", error);
|
||||
callback("Error querying OpenAI. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { queryOpenAI };
|
@ -36,6 +36,9 @@
|
||||
>
|
||||
Update Patterns
|
||||
</button>
|
||||
<button id="createPattern" class="btn btn-outline-success my-2 my-sm-0">
|
||||
Create Pattern
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse"></div>
|
||||
<div class="m1-auto">
|
||||
<a class="navbar-brand" id="themeChanger" href="#">Dark</a>
|
||||
@ -53,6 +56,24 @@
|
||||
></textarea>
|
||||
<button class="btn btn-primary" id="submit">Submit</button>
|
||||
</div>
|
||||
<div id="patternCreator" class="container hidden">
|
||||
<input
|
||||
type="text"
|
||||
id="patternName"
|
||||
placeholder="Enter Pattern Name"
|
||||
class="form-control"
|
||||
/>
|
||||
<textarea
|
||||
rows="5"
|
||||
class="form-control"
|
||||
id="patternBody"
|
||||
placeholder="Create your pattern"
|
||||
></textarea>
|
||||
<button class="btn btn-primary" id="submitPattern">Submit</button>
|
||||
<div id="patternCreatedMessage" class="hidden">
|
||||
Pattern created successfully!
|
||||
</div>
|
||||
</div>
|
||||
<div id="configSection" class="container hidden">
|
||||
<input
|
||||
type="text"
|
||||
|
@ -1,10 +1,13 @@
|
||||
const { app, BrowserWindow, ipcMain, dialog } = require("electron");
|
||||
const fs = require("fs");
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const OpenAI = require("openai");
|
||||
const Ollama = require("ollama");
|
||||
const Anthropic = require("@anthropic-ai/sdk");
|
||||
const axios = require("axios");
|
||||
const fsExtra = require("fs-extra");
|
||||
const fsConstants = require("fs").constants;
|
||||
|
||||
let fetch, allModels;
|
||||
|
||||
@ -17,26 +20,22 @@ let win;
|
||||
let openai;
|
||||
let ollama;
|
||||
|
||||
function ensureFabricFoldersExist() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const fabricPath = path.join(os.homedir(), ".config", "fabric");
|
||||
const patternsPath = path.join(fabricPath, "patterns");
|
||||
async function ensureFabricFoldersExist() {
|
||||
const fabricPath = path.join(os.homedir(), ".config", "fabric");
|
||||
const patternsPath = path.join(fabricPath, "patterns");
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(fabricPath)) {
|
||||
fs.mkdirSync(fabricPath, { recursive: true });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(patternsPath)) {
|
||||
fs.mkdirSync(patternsPath, { recursive: true });
|
||||
await downloadAndUpdatePatterns(patternsPath);
|
||||
}
|
||||
resolve(); // Resolve the promise once everything is set up
|
||||
} catch (error) {
|
||||
console.error("Error ensuring fabric folders exist:", error);
|
||||
reject(error); // Reject the promise if an error occurs
|
||||
}
|
||||
});
|
||||
try {
|
||||
await fs
|
||||
.access(fabricPath, fsConstants.F_OK)
|
||||
.catch(() => fs.mkdir(fabricPath, { recursive: true }));
|
||||
await fs
|
||||
.access(patternsPath, fsConstants.F_OK)
|
||||
.catch(() => fs.mkdir(patternsPath, { recursive: true }));
|
||||
// Optionally download and update patterns after ensuring the directories exist
|
||||
} catch (error) {
|
||||
console.error("Error ensuring fabric folders exist:", error);
|
||||
throw error; // Make sure to re-throw the error to handle it further up the call stack if necessary
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAndUpdatePatterns() {
|
||||
@ -105,51 +104,77 @@ async function downloadAndUpdatePatterns() {
|
||||
}
|
||||
function getPatternFolders() {
|
||||
const patternsPath = path.join(os.homedir(), ".config", "fabric", "patterns");
|
||||
return fs
|
||||
.readdirSync(patternsPath, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(patternsPath, { withFileTypes: true }, (error, dirents) => {
|
||||
if (error) {
|
||||
console.error("Failed to read pattern folders:", error);
|
||||
reject(error);
|
||||
} else {
|
||||
const folders = dirents
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
resolve(folders);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkApiKeyExists() {
|
||||
async function checkApiKeyExists() {
|
||||
const configPath = path.join(os.homedir(), ".config", "fabric", ".env");
|
||||
return fs.existsSync(configPath);
|
||||
try {
|
||||
await fs.access(configPath, fsConstants.F_OK);
|
||||
return true; // The file exists
|
||||
} catch (e) {
|
||||
return false; // The file does not exist
|
||||
}
|
||||
}
|
||||
|
||||
function loadApiKeys() {
|
||||
async function loadApiKeys() {
|
||||
const configPath = path.join(os.homedir(), ".config", "fabric", ".env");
|
||||
let keys = { openAIKey: null, claudeKey: null };
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
const envContents = fs.readFileSync(configPath, { encoding: "utf8" });
|
||||
try {
|
||||
// Use fs.promises.readFile and await its result
|
||||
const envContents = await fs.readFile(configPath, { encoding: "utf8" });
|
||||
const openAIMatch = envContents.match(/^OPENAI_API_KEY=(.*)$/m);
|
||||
const claudeMatch = envContents.match(/^CLAUDE_API_KEY=(.*)$/m);
|
||||
|
||||
if (openAIMatch && openAIMatch[1]) {
|
||||
keys.openAIKey = openAIMatch[1];
|
||||
openai = new OpenAI({ apiKey: keys.openAIKey });
|
||||
// Initialize your OpenAI client here if necessary
|
||||
}
|
||||
if (claudeMatch && claudeMatch[1]) {
|
||||
keys.claudeKey = claudeMatch[1];
|
||||
claude = new Anthropic({ apiKey: keys.claudeKey });
|
||||
// Initialize your Claude client here if necessary
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle the case where the .env file doesn't exist or can't be read
|
||||
console.error("Could not load API keys:", error);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
function saveApiKeys(openAIKey, claudeKey) {
|
||||
async function saveApiKeys(openAIKey, claudeKey) {
|
||||
const configPath = path.join(os.homedir(), ".config", "fabric");
|
||||
const envFilePath = path.join(configPath, ".env");
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
fs.mkdirSync(configPath, { recursive: true });
|
||||
try {
|
||||
await fs.access(configPath);
|
||||
} catch {
|
||||
await fs.mkdir(configPath, { recursive: true });
|
||||
}
|
||||
|
||||
let envContent = "";
|
||||
|
||||
// Read the existing .env file if it exists
|
||||
if (fs.existsSync(envFilePath)) {
|
||||
envContent = fs.readFileSync(envFilePath, "utf8");
|
||||
try {
|
||||
envContent = await fs.readFile(envFilePath, "utf8");
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
}
|
||||
// If the file doesn't exist, create an empty .env file
|
||||
await fs.writeFile(envFilePath, "");
|
||||
}
|
||||
|
||||
// Update the specific API key
|
||||
@ -164,9 +189,9 @@ function saveApiKeys(openAIKey, claudeKey) {
|
||||
claude = new Anthropic({ apiKey: claudeKey });
|
||||
}
|
||||
|
||||
fs.writeFileSync(envFilePath, envContent.trim());
|
||||
loadApiKeys();
|
||||
win.webContents.send("reload-app");
|
||||
await fs.writeFile(envFilePath, envContent.trim());
|
||||
await loadApiKeys();
|
||||
win.webContents.send("api-keys-saved");
|
||||
}
|
||||
|
||||
function updateOrAddKey(envContent, keyName, keyValue) {
|
||||
@ -195,7 +220,7 @@ async function getModels() {
|
||||
ollamaModels: [],
|
||||
};
|
||||
|
||||
let keys = loadApiKeys(); // Assuming loadApiKeys() is updated to return both keys
|
||||
let keys = await loadApiKeys(); // Assuming loadApiKeys() is updated to return both keys
|
||||
|
||||
if (keys.claudeKey) {
|
||||
// Assuming claudeModels do not require an asynchronous call to be fetched
|
||||
@ -245,7 +270,7 @@ async function getModels() {
|
||||
return allModels; // Return the aggregated results
|
||||
}
|
||||
|
||||
function getPatternContent(patternName) {
|
||||
async function getPatternContent(patternName) {
|
||||
const patternPath = path.join(
|
||||
os.homedir(),
|
||||
".config",
|
||||
@ -255,7 +280,8 @@ function getPatternContent(patternName) {
|
||||
"system.md"
|
||||
);
|
||||
try {
|
||||
return fs.readFileSync(patternPath, "utf8");
|
||||
const content = await fs.readFile(patternPath, "utf8");
|
||||
return content;
|
||||
} catch (error) {
|
||||
console.error("Error reading pattern file:", error);
|
||||
return "";
|
||||
@ -332,6 +358,33 @@ async function claudeMessage(system, user, model, event) {
|
||||
event.reply("model-response-end", responseMessage);
|
||||
}
|
||||
|
||||
async function createPatternFolder(patternName, patternBody) {
|
||||
try {
|
||||
const patternsPath = path.join(
|
||||
os.homedir(),
|
||||
".config",
|
||||
"fabric",
|
||||
"patterns"
|
||||
);
|
||||
const patternFolderPath = path.join(patternsPath, patternName);
|
||||
|
||||
// Create the pattern folder using the promise-based API
|
||||
await fs.mkdir(patternFolderPath, { recursive: true });
|
||||
|
||||
// Create the system.md file inside the pattern folder
|
||||
const filePath = path.join(patternFolderPath, "system.md");
|
||||
await fs.writeFile(filePath, patternBody);
|
||||
|
||||
console.log(
|
||||
`Pattern folder '${patternName}' created successfully with system.md inside.`
|
||||
);
|
||||
return `Pattern folder '${patternName}' created successfully with system.md inside.`;
|
||||
} catch (err) {
|
||||
console.error(`Failed to create the pattern folder: ${err.message}`);
|
||||
throw err; // Ensure the error is thrown so it can be caught by the caller
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
width: 800,
|
||||
@ -377,10 +430,21 @@ ipcMain.on("start-query", async (event, system, user, model) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("create-pattern", async (event, patternName, patternContent) => {
|
||||
try {
|
||||
const result = await createPatternFolder(patternName, patternContent);
|
||||
return { status: "success", message: result }; // Use a response object for more detailed responses
|
||||
} catch (error) {
|
||||
console.error("Error creating pattern:", error);
|
||||
return { status: "error", message: error.message }; // Return an error object
|
||||
}
|
||||
});
|
||||
|
||||
// Example of using ipcMain.handle for asynchronous operations
|
||||
ipcMain.handle("get-patterns", async (event) => {
|
||||
try {
|
||||
return getPatternFolders();
|
||||
const patterns = await getPatternFolders();
|
||||
return patterns;
|
||||
} catch (error) {
|
||||
console.error("Failed to get patterns:", error);
|
||||
return [];
|
||||
@ -394,7 +458,8 @@ ipcMain.on("update-patterns", () => {
|
||||
|
||||
ipcMain.handle("get-pattern-content", async (event, patternName) => {
|
||||
try {
|
||||
return getPatternContent(patternName);
|
||||
const content = await getPatternContent(patternName);
|
||||
return content;
|
||||
} catch (error) {
|
||||
console.error("Failed to get pattern content:", error);
|
||||
return "";
|
||||
@ -403,7 +468,7 @@ ipcMain.handle("get-pattern-content", async (event, patternName) => {
|
||||
|
||||
ipcMain.handle("save-api-keys", async (event, { openAIKey, claudeKey }) => {
|
||||
try {
|
||||
saveApiKeys(openAIKey, claudeKey);
|
||||
await saveApiKeys(openAIKey, claudeKey);
|
||||
return "API Keys saved successfully.";
|
||||
} catch (error) {
|
||||
console.error("Error saving API keys:", error);
|
||||
@ -423,22 +488,13 @@ ipcMain.handle("get-models", async (event) => {
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
try {
|
||||
const keys = loadApiKeys();
|
||||
if (!keys.openAIKey && !keys.claudeKey) {
|
||||
promptUserForApiKey();
|
||||
} else {
|
||||
createWindow();
|
||||
}
|
||||
const keys = await loadApiKeys();
|
||||
await ensureFabricFoldersExist(); // Ensure fabric folders exist
|
||||
createWindow(); // Create the application window
|
||||
|
||||
// After window creation, check if the API key exists
|
||||
if (!checkApiKeyExists()) {
|
||||
console.log("API key is missing. Prompting user to input API key.");
|
||||
win.webContents.send("request-api-key");
|
||||
}
|
||||
await getModels(); // Fetch models after loading API keys
|
||||
createWindow(); // Keep this line
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize fabric folders:", error);
|
||||
await ensureFabricFoldersExist(); // Ensure fabric folders exist
|
||||
createWindow(); // Keep this line
|
||||
// Handle initialization failure (e.g., close the app or show an error message)
|
||||
}
|
||||
});
|
||||
|
@ -11,6 +11,10 @@ document.addEventListener("DOMContentLoaded", async function () {
|
||||
const openaiApiKeyInput = document.getElementById("apiKeyInput");
|
||||
const claudeApiKeyInput = document.getElementById("claudeApiKeyInput");
|
||||
const updatePatternsButton = document.getElementById("updatePatternsButton");
|
||||
const updatePatternButton = document.getElementById("createPattern");
|
||||
const patternCreator = document.getElementById("patternCreator");
|
||||
const submitPatternButton = document.getElementById("submitPattern");
|
||||
const myForm = document.getElementById("my-form");
|
||||
const copyButton = document.createElement("button");
|
||||
|
||||
window.electronAPI.on("patterns-ready", () => {
|
||||
@ -70,6 +74,35 @@ document.addEventListener("DOMContentLoaded", async function () {
|
||||
);
|
||||
}
|
||||
|
||||
async function submitPattern(patternName, patternText) {
|
||||
try {
|
||||
const response = await window.electronAPI.invoke(
|
||||
"create-pattern",
|
||||
patternName,
|
||||
patternText
|
||||
);
|
||||
if (response.status === "success") {
|
||||
console.log(response.message);
|
||||
// Show success message
|
||||
const patternCreatedMessage = document.getElementById(
|
||||
"patternCreatedMessage"
|
||||
);
|
||||
patternCreatedMessage.classList.remove("hidden");
|
||||
setTimeout(() => {
|
||||
patternCreatedMessage.classList.add("hidden");
|
||||
}, 3000); // Hide the message after 3 seconds
|
||||
|
||||
// Update pattern list
|
||||
loadPatterns();
|
||||
} else {
|
||||
console.error(response.message);
|
||||
// Handle failure (e.g., showing an error message to the user)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("IPC error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
const containerClone = responseContainer.cloneNode(true);
|
||||
const copyButtonClone = containerClone.querySelector("#copyButton");
|
||||
@ -135,13 +168,12 @@ document.addEventListener("DOMContentLoaded", async function () {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load models:", error);
|
||||
alert(
|
||||
"Failed to load models. Please check the console for more details."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
updatePatternsButton.addEventListener("click", () => {
|
||||
window.electronAPI.send("update-patterns");
|
||||
});
|
||||
|
||||
// Load patterns and models on startup
|
||||
loadPatterns();
|
||||
loadModels();
|
||||
@ -168,13 +200,18 @@ document.addEventListener("DOMContentLoaded", async function () {
|
||||
submitQuery(message);
|
||||
});
|
||||
|
||||
window.electronAPI.on;
|
||||
"reload-app",
|
||||
() => {
|
||||
// Reload the app
|
||||
loadModels();
|
||||
location.reload();
|
||||
};
|
||||
window.electronAPI.on("api-keys-saved", async () => {
|
||||
try {
|
||||
await loadModels();
|
||||
alert("API Keys saved successfully.");
|
||||
configSection.classList.add("hidden");
|
||||
openaiApiKeyInput.value = "";
|
||||
claudeApiKeyInput.value = "";
|
||||
} catch (error) {
|
||||
console.error("Failed to reload models:", error);
|
||||
alert("Failed to reload models.");
|
||||
}
|
||||
});
|
||||
|
||||
// Submit button click handler
|
||||
submitButton.addEventListener("click", async () => {
|
||||
@ -182,6 +219,14 @@ document.addEventListener("DOMContentLoaded", async function () {
|
||||
submitQuery(userInputValue);
|
||||
});
|
||||
|
||||
submitPatternButton.addEventListener("click", async () => {
|
||||
const patternName = document.getElementById("patternName").value;
|
||||
const patternText = document.getElementById("patternBody").value;
|
||||
document.getElementById("patternName").value = "";
|
||||
document.getElementById("patternBody").value = "";
|
||||
submitPattern(patternName, patternText);
|
||||
});
|
||||
|
||||
// Theme changer click handler
|
||||
themeChanger.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
@ -190,6 +235,14 @@ document.addEventListener("DOMContentLoaded", async function () {
|
||||
themeChanger.innerText === "Dark" ? "Light" : "Dark";
|
||||
});
|
||||
|
||||
updatePatternButton.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
patternCreator.classList.toggle("hidden");
|
||||
myForm.classList.toggle("hidden");
|
||||
|
||||
// window.electronAPI.send("create-pattern");
|
||||
});
|
||||
|
||||
// Config button click handler - toggles the config section visibility
|
||||
configButton.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
@ -202,14 +255,6 @@ document.addEventListener("DOMContentLoaded", async function () {
|
||||
const claudeKey = claudeApiKeyInput.value;
|
||||
window.electronAPI
|
||||
.invoke("save-api-keys", { openAIKey, claudeKey })
|
||||
.then(() => {
|
||||
alert("API Keys saved successfully.");
|
||||
configSection.classList.add("hidden");
|
||||
openaiApiKeyInput.value = "";
|
||||
claudeApiKeyInput.value = "";
|
||||
// Reload the models
|
||||
loadModels();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error saving API keys:", err);
|
||||
alert("Failed to save API Keys.");
|
||||
|
@ -158,3 +158,27 @@ body.light-theme .navbar-toggler-icon {
|
||||
#copyButton:focus {
|
||||
outline: none;
|
||||
}
|
||||
#patternCreatedMessage {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.light-theme #patternCreator {
|
||||
background: #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.light-theme #patternCreator input,
|
||||
.light-theme #patternCreator textarea {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
#patternCreator textarea {
|
||||
background-color: #424242;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user