Browse Source

updated gui to include local models and claud...more to comee

pull/261/head
xssdoctor 8 months ago
parent
commit
d1b59367bd
  1. 5
      installer/client/gui/claude.mjs
  2. 7
      installer/client/gui/index.html
  3. 376
      installer/client/gui/main.js
  4. 65
      installer/client/gui/package-lock.json
  5. 4
      installer/client/gui/package.json
  6. 110
      installer/client/gui/static/js/index.js
  7. 0
      installer/client/gui/utils.js

5
installer/client/gui/claude.mjs

@ -0,0 +1,5 @@
import Claude from "claude-ai";
export function MakeClaude(apiKey) {
return new Claude({ sessionKey: apiKey });
}

7
installer/client/gui/index.html

@ -44,6 +44,7 @@
<main>
<div class="container" id="my-form">
<select class="form-control" id="patternSelector"></select>
<select class="form-control" id="modelSelector"></select>
<textarea
rows="5"
class="form-control"
@ -59,6 +60,12 @@
placeholder="Enter OpenAI API Key"
class="form-control"
/>
<input
type="text"
id="claudeApiKeyInput"
placeholder="Enter Claude API Key"
class="form-control"
/>
<button id="saveApiKey" class="btn btn-primary">Save API Key</button>
</div>
<div class="container hidden" id="responseContainer"></div>

376
installer/client/gui/main.js

@ -1,69 +1,21 @@
const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const pdfParse = require("pdf-parse");
const mammoth = require("mammoth");
const fs = require("fs");
const path = require("path");
const os = require("os");
const { queryOpenAI } = require("./chatgpt.js");
const axios = require("axios");
const fsExtra = require("fs-extra");
const OpenAI = require("openai");
const Ollama = require("ollama");
const Anthropic = require("@anthropic-ai/sdk");
let fetch, allModels;
let fetch;
import("node-fetch").then((module) => {
fetch = module.default;
});
const unzipper = require("unzipper");
let win;
function promptUserForApiKey() {
// Create a new window to prompt the user for the API key
const promptWindow = new BrowserWindow({
// Window configuration for the prompt
width: 500,
height: 200,
webPreferences: {
nodeIntegration: true,
contextIsolation: false, // Consider security implications
},
});
// Handle the API key submission from the prompt window
ipcMain.on("submit-api-key", (event, apiKey) => {
if (apiKey) {
saveApiKey(apiKey);
promptWindow.close();
createWindow(); // Proceed to create the main window
} else {
// Handle invalid input or user cancellation
promptWindow.close();
}
});
}
function loadApiKey() {
const configPath = path.join(os.homedir(), ".config", "fabric", ".env");
if (fs.existsSync(configPath)) {
const envContents = fs.readFileSync(configPath, { encoding: "utf8" });
const matches = envContents.match(/^OPENAI_API_KEY=(.*)$/m);
if (matches && matches[1]) {
return matches[1];
}
}
return null;
}
function saveApiKey(apiKey) {
const configPath = path.join(os.homedir(), ".config", "fabric");
const envFilePath = path.join(configPath, ".env");
if (!fs.existsSync(configPath)) {
fs.mkdirSync(configPath, { recursive: true });
}
fs.writeFileSync(envFilePath, `OPENAI_API_KEY=${apiKey}`);
process.env.OPENAI_API_KEY = apiKey; // Set for current session
}
let openai;
let ollama;
function ensureFabricFoldersExist() {
return new Promise(async (resolve, reject) => {
@ -87,8 +39,9 @@ function ensureFabricFoldersExist() {
});
}
async function downloadAndUpdatePatterns(patternsPath) {
async function downloadAndUpdatePatterns() {
try {
// Download the zip file
const response = await axios({
method: "get",
url: "https://github.com/danielmiessler/fabric/archive/refs/heads/main.zip",
@ -99,16 +52,15 @@ async function downloadAndUpdatePatterns(patternsPath) {
fs.writeFileSync(zipPath, response.data);
console.log("Zip file written to:", zipPath);
// Prepare for extraction
const tempExtractPath = path.join(os.tmpdir(), "fabric_extracted");
fsExtra.emptyDirSync(tempExtractPath);
await fsExtra.remove(patternsPath); // Delete the existing patterns directory
await fsExtra.emptyDir(tempExtractPath);
// Extract the zip file
await fs
.createReadStream(zipPath)
.pipe(unzipper.Extract({ path: tempExtractPath }))
.promise();
console.log("Extraction complete");
const extractedPatternsPath = path.join(
@ -117,21 +69,40 @@ async function downloadAndUpdatePatterns(patternsPath) {
"patterns"
);
await fsExtra.copy(extractedPatternsPath, patternsPath);
// Compare and move folders
const existingPatternsPath = path.join(
os.homedir(),
".config",
"fabric",
"patterns"
);
if (fs.existsSync(existingPatternsPath)) {
const existingFolders = await fsExtra.readdir(existingPatternsPath);
for (const folder of existingFolders) {
if (!fs.existsSync(path.join(extractedPatternsPath, folder))) {
await fsExtra.move(
path.join(existingPatternsPath, folder),
path.join(extractedPatternsPath, folder)
);
console.log(
`Moved missing folder ${folder} to the extracted patterns directory.`
);
}
}
}
// Overwrite the existing patterns directory with the updated extracted directory
await fsExtra.copy(extractedPatternsPath, existingPatternsPath, {
overwrite: true,
});
console.log("Patterns successfully updated");
// Inform the renderer process that the patterns have been updated
win.webContents.send("patterns-updated");
// win.webContents.send("patterns-updated");
} catch (error) {
console.error("Error downloading or updating patterns:", error);
}
}
function checkApiKeyExists() {
const configPath = path.join(os.homedir(), ".config", "fabric", ".env");
return fs.existsSync(configPath);
}
function getPatternFolders() {
const patternsPath = path.join(os.homedir(), ".config", "fabric", "patterns");
return fs
@ -140,6 +111,114 @@ function getPatternFolders() {
.map((dirent) => dirent.name);
}
function checkApiKeyExists() {
const configPath = path.join(os.homedir(), ".config", "fabric", ".env");
return fs.existsSync(configPath);
}
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" });
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 });
}
if (claudeMatch && claudeMatch[1]) {
keys.claudeKey = claudeMatch[1];
claude = new Anthropic({ apiKey: keys.claudeKey });
}
}
return keys;
}
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 });
}
let envContent = "";
if (openAIKey) {
envContent += `OPENAI_API_KEY=${openAIKey}\n`;
process.env.OPENAI_API_KEY = openAIKey; // Set for current session
openai = new OpenAI({ apiKey: openAIKey });
}
if (claudeKey) {
envContent += `CLAUDE_API_KEY=${claudeKey}\n`;
process.env.CLAUDE_API_KEY = claudeKey; // Set for current session
claude = new Anthropic({ apiKey: claudeKey });
}
fs.writeFileSync(envFilePath, envContent.trim());
}
async function getOllamaModels() {
ollama = new Ollama.Ollama();
const _models = await ollama.list();
return _models.models.map((x) => x.name);
}
async function getModels() {
ollama = new Ollama.Ollama();
allModels = {
gptModels: [],
claudeModels: [],
ollamaModels: [],
};
let keys = loadApiKeys(); // Assuming loadApiKeys() is updated to return both keys
if (keys.claudeKey) {
// Assuming claudeModels do not require an asynchronous call to be fetched
claudeModels = [
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307",
"claude-2.1",
];
allModels.claudeModels = claudeModels;
}
if (keys.openAIKey) {
openai = new OpenAI({ apiKey: keys.openAIKey });
// Wrap asynchronous call with a Promise to handle it in parallel
gptModelsPromise = openai.models.list();
}
// Check if ollama exists and has a list method
if (
typeof ollama !== "undefined" &&
ollama.list &&
typeof ollama.list === "function"
) {
// Assuming ollama.list() returns a Promise
ollamaModelsPromise = getOllamaModels();
} else {
console.log("ollama is not available or does not support listing models.");
}
// Wait for all asynchronous operations to complete
try {
const results = await Promise.all(
[gptModelsPromise, ollamaModelsPromise].filter(Boolean)
); // Filter out undefined promises
allModels.gptModels = results[0]?.data || []; // Assuming the first promise is always GPT models if it exists
allModels.ollamaModels = results[1] || []; // Assuming the second promise is always Ollama models if it exists
} catch (error) {
console.error("Error fetching models from OpenAI or Ollama:", error);
}
return allModels; // Return the aggregated results
}
function getPatternContent(patternName) {
const patternPath = path.join(
os.homedir(),
@ -157,6 +236,76 @@ function getPatternContent(patternName) {
}
}
async function ollamaMessage(system, user, model, event) {
ollama = new Ollama.Ollama();
const userMessage = {
role: "user",
content: user,
};
const systemMessage = { role: "system", content: system };
const response = await ollama.chat({
model: model,
messages: [systemMessage, userMessage],
stream: true,
});
let responseMessage = "";
for await (const chunk of response) {
const content = chunk.message.content;
if (content) {
responseMessage += content;
event.reply("model-response", content);
}
event.reply("model-response-end", responseMessage);
}
}
async function openaiMessage(system, user, model, event) {
const userMessage = { role: "user", content: user };
const systemMessage = { role: "system", content: system };
const stream = await openai.chat.completions.create(
{
model: model,
messages: [systemMessage, userMessage],
stream: true,
},
{ responseType: "stream" }
);
let responseMessage = "";
for await (const chunk of stream) {
const content = chunk.choices[0].delta.content;
if (content) {
responseMessage += content;
event.reply("model-response", content);
}
}
event.reply("model-response-end", responseMessage);
}
async function claudeMessage(system, user, model, event) {
const userMessage = { role: "user", content: user };
const systemMessage = system;
const response = await claude.messages.create({
model: model,
system: systemMessage,
max_tokens: 4096,
messages: [userMessage],
stream: true,
temperature: 0.0,
top_p: 1.0,
});
let responseMessage = "";
for await (const chunk of response) {
if (chunk.delta && chunk.delta.text) {
responseMessage += chunk.delta.text;
event.reply("model-response", chunk.delta.text);
}
}
event.reply("model-response-end", responseMessage);
}
function createWindow() {
win = new BrowserWindow({
width: 800,
@ -174,50 +323,31 @@ function createWindow() {
win = null;
});
}
ipcMain.on("process-complex-file", (event, filePath) => {
const extension = path.extname(filePath).toLowerCase();
let fileProcessPromise;
if (extension === ".pdf") {
const dataBuffer = fs.readFileSync(filePath);
fileProcessPromise = pdfParse(dataBuffer).then((data) => data.text);
} else if (extension === ".docx") {
fileProcessPromise = mammoth
.extractRawText({ path: filePath })
.then((result) => result.value)
.catch((err) => {
console.error("Error processing DOCX file:", err);
throw new Error("Error processing DOCX file.");
});
} else {
event.reply("file-response", "Error: Unsupported file type");
return;
}
fileProcessPromise
.then((extractedText) => {
// Sending the extracted text back to the frontend.
event.reply("file-response", extractedText);
})
.catch((error) => {
// Handling any errors during file processing and sending them back to the frontend.
event.reply("file-response", `Error processing file: ${error.message}`);
});
});
ipcMain.on("start-query-openai", async (event, system, user) => {
if (system == null || user == null) {
console.error("Received null for system or user message");
event.reply("openai-response", "Error: System or user message is null.");
ipcMain.on("start-query", async (event, system, user, model) => {
if (system == null || user == null || model == null) {
console.error("Received null for system, user message, or model");
event.reply(
"model-response-error",
"Error: System, user message, or model is null."
);
return;
}
try {
await queryOpenAI(system, user, (message) => {
event.reply("openai-response", message);
});
const _gptModels = allModels.gptModels.map((model) => model.id);
if (allModels.claudeModels.includes(model)) {
await claudeMessage(system, user, model, event);
} else if (_gptModels.includes(model)) {
await openaiMessage(system, user, model, event);
} else if (allModels.ollamaModels.includes(model)) {
await ollamaMessage(system, user, model, event);
} else {
event.reply("model-response-error", "Unsupported model: " + model);
}
} catch (error) {
console.error("Error querying OpenAI:", error);
event.reply("no-api-key", "Error querying OpenAI.");
console.error("Error querying model:", error);
event.reply("model-response-error", "Error querying model.");
}
});
@ -245,31 +375,32 @@ ipcMain.handle("get-pattern-content", async (event, patternName) => {
}
});
ipcMain.handle("save-api-key", async (event, apiKey) => {
ipcMain.handle("save-api-keys", async (event, { openAIKey, claudeKey }) => {
try {
const configPath = path.join(os.homedir(), ".config", "fabric");
if (!fs.existsSync(configPath)) {
fs.mkdirSync(configPath, { recursive: true });
}
const envFilePath = path.join(configPath, ".env");
fs.writeFileSync(envFilePath, `OPENAI_API_KEY=${apiKey}`);
process.env.OPENAI_API_KEY = apiKey;
saveApiKeys(openAIKey, claudeKey);
return "API Keys saved successfully.";
} catch (error) {
console.error("Error saving API keys:", error);
throw new Error("Failed to save API Keys.");
}
});
return "API Key saved successfully.";
ipcMain.handle("get-models", async (event) => {
try {
const models = await getModels();
return models;
} catch (error) {
console.error("Error saving API key:", error);
throw new Error("Failed to save API Key.");
console.error("Failed to get models:", error);
return { gptModels: [], claudeModels: [], ollamaModels: [] };
}
});
app.whenReady().then(async () => {
try {
const apiKey = loadApiKey();
if (!apiKey) {
const keys = loadApiKeys();
if (!keys.openAIKey && !keys.claudeKey) {
promptUserForApiKey();
} else {
process.env.OPENAI_API_KEY = apiKey;
createWindow();
}
await ensureFabricFoldersExist(); // Ensure fabric folders exist
@ -278,7 +409,6 @@ app.whenReady().then(async () => {
// After window creation, check if the API key exists
if (!checkApiKeyExists()) {
console.log("API key is missing. Prompting user to input API key.");
// Optionally, directly invoke a function here to show a prompt in the renderer process
win.webContents.send("request-api-key");
}
} catch (error) {

65
installer/client/gui/package-lock.json generated

@ -9,16 +9,34 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@anthropic-ai/sdk": "^0.19.1",
"axios": "^1.6.7",
"mammoth": "^1.6.0",
"node-fetch": "^2.6.7",
"ollama": "^0.5.0",
"pdf-parse": "^1.1.1",
"unzipper": "^0.10.14"
},
"devDependencies": {
"dotenv": "^16.4.1",
"electron": "^28.2.6",
"openai": "^4.27.0"
"openai": "^4.31.0"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.19.1.tgz",
"integrity": "sha512-u9i8yN8wAr/ujaXSRjfYXiYzhCk2mdUG6G9y5IAKEAPJHwFTrEyf76Z4V1LqqFbDBlZqm0tkoMMpU8tmp65ocA==",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"digest-fetch": "^1.3.0",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7",
"web-streams-polyfill": "^3.2.1"
}
},
"node_modules/@electron/get": {
@ -97,7 +115,6 @@
"version": "18.19.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz",
"integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
@ -106,7 +123,6 @@
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
"dev": true,
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
@ -143,7 +159,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dev": true,
"dependencies": {
"event-target-shim": "^5.0.0"
},
@ -155,7 +170,6 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
"integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
"dev": true,
"dependencies": {
"humanize-ms": "^1.2.1"
},
@ -199,8 +213,7 @@
"node_modules/base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==",
"dev": true
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/base64-js": {
"version": "1.5.1",
@ -329,7 +342,6 @@
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"dev": true,
"engines": {
"node": "*"
}
@ -371,7 +383,6 @@
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"dev": true,
"engines": {
"node": "*"
}
@ -482,7 +493,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",
"integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==",
"dev": true,
"dependencies": {
"base-64": "^0.1.0",
"md5": "^2.3.0"
@ -591,7 +601,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -660,14 +669,12 @@
"node_modules/form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
"dev": true
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"dev": true,
"dependencies": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
@ -680,7 +687,6 @@
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"dev": true,
"engines": {
"node": ">= 14"
}
@ -950,7 +956,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"dev": true,
"dependencies": {
"ms": "^2.0.0"
}
@ -977,8 +982,7 @@
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"node_modules/isarray": {
"version": "1.0.0",
@ -1112,7 +1116,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dev": true,
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
@ -1186,7 +1189,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"dev": true,
"funding": [
{
"type": "github",
@ -1247,6 +1249,14 @@
"node": ">= 0.4"
}
},
"node_modules/ollama": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.0.tgz",
"integrity": "sha512-CRtRzsho210EGdK52GrUMohA2pU+7NbgEaBG3DcYeRmvQthDO7E2LHOkLlUUeaYUlNmEd8icbjC02ug9meSYnw==",
"dependencies": {
"whatwg-fetch": "^3.6.20"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -1256,9 +1266,9 @@
}
},
"node_modules/openai": {
"version": "4.27.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.27.0.tgz",
"integrity": "sha512-j1ZEx9NiBpm31rxWqQTjQt1QvH/8001xHsc/pRoPjkRDYWONCb+qkR6L9C7Wl6ar72Mz1ybtn1bv6fqAoTPlKw==",
"version": "4.31.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.31.0.tgz",
"integrity": "sha512-JebkRnRGEGLnJt3+bJ5B7au8nBeZvJjs9baVxDmUZ5+BgafAdy6KDxJGSuyaw/IA+ErqY3jmOH5cDC2mCDJF2w==",
"dev": true,
"dependencies": {
"@types/node": "^18.11.18",
@ -1553,8 +1563,7 @@
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/universalify": {
"version": "0.1.2",
@ -1591,7 +1600,6 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz",
"integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
@ -1601,6 +1609,11 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-fetch": {
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",

4
installer/client/gui/package.json

@ -11,12 +11,14 @@
"devDependencies": {
"dotenv": "^16.4.1",
"electron": "^28.2.6",
"openai": "^4.27.0"
"openai": "^4.31.0"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.19.1",
"axios": "^1.6.7",
"mammoth": "^1.6.0",
"node-fetch": "^2.6.7",
"ollama": "^0.5.0",
"pdf-parse": "^1.1.1",
"unzipper": "^0.10.14"
}

110
installer/client/gui/static/js/index.js

@ -1,5 +1,6 @@
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");
@ -7,8 +8,8 @@ document.addEventListener("DOMContentLoaded", async function () {
const configButton = document.getElementById("configButton");
const configSection = document.getElementById("configSection");
const saveApiKeyButton = document.getElementById("saveApiKey");
const apiKeyInput = document.getElementById("apiKeyInput");
const originalPlaceholder = userInput.placeholder;
const openaiApiKeyInput = document.getElementById("apiKeyInput");
const claudeApiKeyInput = document.getElementById("claudeApiKeyInput");
const updatePatternsButton = document.getElementById("updatePatternsButton");
const copyButton = document.createElement("button");
@ -17,14 +18,12 @@ document.addEventListener("DOMContentLoaded", async function () {
loadPatterns();
});
window.electronAPI.on("request-api-key", () => {
// Show the API key input section or modal to the user
configSection.classList.remove("hidden"); // Assuming 'configSection' is your API key input area
configSection.classList.remove("hidden");
});
copyButton.textContent = "Copy";
copyButton.id = "copyButton";
document.addEventListener("click", function (e) {
if (e.target && e.target.id === "copyButton") {
// Your copy to clipboard function
copyToClipboard();
}
});
@ -38,54 +37,48 @@ document.addEventListener("DOMContentLoaded", async function () {
});
function htmlToPlainText(html) {
// Create a temporary div element to hold the HTML
var tempDiv = document.createElement("div");
tempDiv.innerHTML = html;
// Replace <br> tags with newline characters
tempDiv.querySelectorAll("br").forEach((br) => br.replaceWith("\n"));
// Replace block elements like <p> and <div> with newline characters
tempDiv.querySelectorAll("p, div").forEach((block) => {
block.prepend("\n"); // Add a newline before the block element's content
block.replaceWith(...block.childNodes); // Replace the block element with its own contents
block.prepend("\n");
block.replaceWith(...block.childNodes);
});
// Return the text content, trimming leading and trailing newlines
return tempDiv.textContent.trim();
}
async function submitQuery(userInputValue) {
userInput.value = ""; // Clear the input after submitting
systemCommand = await window.electronAPI.invoke(
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")) {
console.log("contains hidden");
responseContainer.classList.remove("hidden");
responseContainer.appendChild(copyButton);
}
window.electronAPI.send(
"start-query-openai",
"start-query",
systemCommand,
userInputValue
userInputValue,
selectedModel
);
}
function copyToClipboard() {
const containerClone = responseContainer.cloneNode(true);
// Remove the copy button from the clone
const copyButtonClone = containerClone.querySelector("#copyButton");
if (copyButtonClone) {
copyButtonClone.parentNode.removeChild(copyButtonClone);
}
// Convert HTML to plain text, preserving newlines
const plainText = htmlToPlainText(containerClone.innerHTML);
// Use a temporary textarea for copying
const textArea = document.createElement("textarea");
textArea.style.position = "absolute";
textArea.style.left = "-9999px";
@ -118,47 +111,55 @@ document.addEventListener("DOMContentLoaded", async function () {
}
}
function fallbackCopyTextToClipboard(text) {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
async function loadModels() {
try {
const successful = document.execCommand("copy");
const msg = successful ? "successful" : "unsuccessful";
console.log("Fallback: Copying text command was " + msg);
} catch (err) {
console.error("Fallback: Oops, unable to copy", err);
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);
}
document.body.removeChild(textArea);
}
updatePatternsButton.addEventListener("click", () => {
window.electronAPI.send("update-patterns");
});
// Load patterns on startup
try {
const patterns = await window.electronAPI.invoke("get-patterns");
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);
}
// Load patterns and models on startup
loadPatterns();
loadModels();
// Listen for OpenAI responses
window.electronAPI.on("openai-response", (message) => {
// 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);
@ -189,18 +190,19 @@ document.addEventListener("DOMContentLoaded", async function () {
// Save API Key button click handler
saveApiKeyButton.addEventListener("click", () => {
const apiKey = apiKeyInput.value;
const openAIKey = openaiApiKeyInput.value;
const claudeKey = claudeApiKeyInput.value;
window.electronAPI
.invoke("save-api-key", apiKey)
.invoke("save-api-keys", { openAIKey, claudeKey })
.then(() => {
alert("API Key saved successfully.");
// Optionally hide the config section and clear the input after saving
alert("API Keys saved successfully.");
configSection.classList.add("hidden");
apiKeyInput.value = "";
openaiApiKeyInput.value = "";
claudeApiKeyInput.value = "";
})
.catch((err) => {
console.error("Error saving API key:", err);
alert("Failed to save API Key.");
console.error("Error saving API keys:", err);
alert("Failed to save API Keys.");
});
});
@ -211,7 +213,7 @@ document.addEventListener("DOMContentLoaded", async function () {
"get-pattern-content",
selectedPattern
);
// Use systemCommand as part of the input for querying OpenAI
// Use systemCommand as part of the input for querying the model
});
// drag and drop

0
installer/client/gui/utils.js

Loading…
Cancel
Save