fabric is an open-source framework for augmenting humans using AI. It provides a modular framework for solving specific problems using a crowdsourced set of AI prompts that can be used anywhere.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

278 lines
8.8 KiB

document.addEventListener("DOMContentLoaded", async function () {
const patternSelector = document.getElementById("patternSelector");
const modelSelector = document.getElementById("modelSelector");
const userInput = document.getElementById("userInput");
const submitButton = document.getElementById("submit");
const responseContainer = document.getElementById("responseContainer");
const themeChanger = document.getElementById("themeChanger");
const configButton = document.getElementById("configButton");
const configSection = document.getElementById("configSection");
const saveApiKeyButton = document.getElementById("saveApiKey");
const openaiApiKeyInput = document.getElementById("apiKeyInput");
const claudeApiKeyInput = document.getElementById("claudeApiKeyInput");
const updatePatternsButton = document.getElementById("updatePatternsButton");
const copyButton = document.createElement("button");
window.electronAPI.on("patterns-ready", () => {
console.log("Patterns are ready. Refreshing the pattern list.");
loadPatterns();
});
window.electronAPI.on("request-api-key", () => {
configSection.classList.remove("hidden");
});
copyButton.textContent = "Copy";
copyButton.id = "copyButton";
document.addEventListener("click", function (e) {
if (e.target && e.target.id === "copyButton") {
copyToClipboard();
}
});
window.electronAPI.on("no-api-key", () => {
alert("API key is missing. Please enter your OpenAI API key.");
});
window.electronAPI.on("patterns-updated", () => {
alert("Patterns updated. Refreshing the pattern list.");
loadPatterns();
});
function htmlToPlainText(html) {
var tempDiv = document.createElement("div");
tempDiv.innerHTML = html;
tempDiv.querySelectorAll("br").forEach((br) => br.replaceWith("\n"));
tempDiv.querySelectorAll("p, div").forEach((block) => {
block.prepend("\n");
block.replaceWith(...block.childNodes);
});
return tempDiv.textContent.trim();
}
async function submitQuery(userInputValue) {
userInput.value = ""; // Clear the input after submitting
const systemCommand = await window.electronAPI.invoke(
"get-pattern-content",
patternSelector.value
);
const selectedModel = modelSelector.value;
responseContainer.innerHTML = ""; // Clear previous responses
if (responseContainer.classList.contains("hidden")) {
responseContainer.classList.remove("hidden");
responseContainer.appendChild(copyButton);
}
window.electronAPI.send(
"start-query",
systemCommand,
userInputValue,
selectedModel
);
}
function copyToClipboard() {
const containerClone = responseContainer.cloneNode(true);
const copyButtonClone = containerClone.querySelector("#copyButton");
if (copyButtonClone) {
copyButtonClone.parentNode.removeChild(copyButtonClone);
}
const plainText = htmlToPlainText(containerClone.innerHTML);
const textArea = document.createElement("textarea");
textArea.style.position = "absolute";
textArea.style.left = "-9999px";
textArea.setAttribute("aria-hidden", "true");
textArea.value = plainText;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand("copy");
console.log("Text successfully copied to clipboard");
} catch (err) {
console.error("Failed to copy text: ", err);
}
document.body.removeChild(textArea);
}
async function loadPatterns() {
try {
const patterns = await window.electronAPI.invoke("get-patterns");
patternSelector.innerHTML = ""; // Clear existing options first
patterns.forEach((pattern) => {
const option = document.createElement("option");
option.value = pattern;
option.textContent = pattern;
patternSelector.appendChild(option);
});
} catch (error) {
console.error("Failed to load patterns:", error);
}
}
async function loadModels() {
try {
const models = await window.electronAPI.invoke("get-models");
modelSelector.innerHTML = ""; // Clear existing options first
models.gptModels.forEach((model) => {
const option = document.createElement("option");
option.value = model.id;
option.textContent = model.id;
modelSelector.appendChild(option);
});
models.claudeModels.forEach((model) => {
const option = document.createElement("option");
option.value = model;
option.textContent = model;
modelSelector.appendChild(option);
});
models.ollamaModels.forEach((model) => {
const option = document.createElement("option");
option.value = model;
option.textContent = model;
modelSelector.appendChild(option);
});
} catch (error) {
console.error("Failed to load models:", error);
}
}
updatePatternsButton.addEventListener("click", () => {
window.electronAPI.send("update-patterns");
});
// Load patterns and models on startup
loadPatterns();
loadModels();
// Listen for model responses
window.electronAPI.on("model-response", (message) => {
const formattedMessage = message.replace(/\n/g, "<br>");
responseContainer.innerHTML += formattedMessage; // Append new data as it arrives
});
window.electronAPI.on("model-response-end", (message) => {
// Handle the end of the model response if needed
});
window.electronAPI.on("model-response-error", (message) => {
alert(message);
});
window.electronAPI.on("file-response", (message) => {
if (message.startsWith("Error")) {
alert(message);
return;
}
submitQuery(message);
});
window.electronAPI.on;
"reload-app",
() => {
// Reload the app
loadModels();
location.reload();
};
// Submit button click handler
submitButton.addEventListener("click", async () => {
const userInputValue = userInput.value;
submitQuery(userInputValue);
});
// Theme changer click handler
themeChanger.addEventListener("click", function (e) {
e.preventDefault();
document.body.classList.toggle("light-theme");
themeChanger.innerText =
themeChanger.innerText === "Dark" ? "Light" : "Dark";
});
// Config button click handler - toggles the config section visibility
configButton.addEventListener("click", function (e) {
e.preventDefault();
configSection.classList.toggle("hidden");
});
// Save API Key button click handler
saveApiKeyButton.addEventListener("click", () => {
const openAIKey = openaiApiKeyInput.value;
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.");
});
});
// Handler for pattern selection change
patternSelector.addEventListener("change", async () => {
const selectedPattern = patternSelector.value;
const systemCommand = await window.electronAPI.invoke(
"get-pattern-content",
selectedPattern
);
// Use systemCommand as part of the input for querying the model
});
// drag and drop
userInput.addEventListener("dragover", (event) => {
event.stopPropagation();
event.preventDefault();
// Add some visual feedback
userInput.classList.add("drag-over");
userInput.placeholder = "Drop file here";
});
userInput.addEventListener("dragleave", (event) => {
event.stopPropagation();
event.preventDefault();
// Remove visual feedback
userInput.classList.remove("drag-over");
userInput.placeholder = originalPlaceholder;
});
userInput.addEventListener("drop", (event) => {
event.stopPropagation();
event.preventDefault();
const file = event.dataTransfer.files[0];
userInput.classList.remove("drag-over");
userInput.placeholder = originalPlaceholder;
processFile(file);
});
function processFile(file) {
const fileType = file.type;
const reader = new FileReader();
let content = "";
reader.onload = (event) => {
content = event.target.result;
userInput.value = content;
submitQuery(content);
};
if (fileType === "text/plain" || fileType === "image/svg+xml") {
reader.readAsText(file);
} else if (
fileType === "application/pdf" ||
fileType.match(/wordprocessingml/)
) {
// For PDF and DOCX, we need to handle them in the main process due to complexity
window.electronAPI.send("process-complex-file", file.path);
} else {
console.error("Unsupported file type");
}
}
});