Browse Source

Merge branch 'main' into main

pull/315/head
Kayvan Sylvan 8 months ago committed by GitHub
parent
commit
5e8f0d4f56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 68
      README.md
  2. 7
      installer/client/cli/fabric.py
  3. 35
      installer/client/cli/utils.py
  4. 45
      installer/client/gui/chatgpt.js
  5. 32
      installer/client/gui/index.html
  6. 530
      installer/client/gui/main.js
  7. 65
      installer/client/gui/package-lock.json
  8. 4
      installer/client/gui/package.json
  9. 182
      installer/client/gui/static/js/index.js
  10. 56
      installer/client/gui/static/stylesheet/style.css
  11. 31
      patterns/create_investigation_visualization/system.md

68
README.md

@ -46,7 +46,7 @@
- [Directly calling Patterns](#directly-calling-patterns) - [Directly calling Patterns](#directly-calling-patterns)
- [Examples](#examples) - [Examples](#examples)
- [Custom Patterns](#custom-patterns) - [Custom Patterns](#custom-patterns)
- [Helper Apps](#helper-apps) - [Helper Apps](#helper-apps)
- [Meta](#meta) - [Meta](#meta)
- [Primary contributors](#primary-contributors) - [Primary contributors](#primary-contributors)
@ -209,38 +209,8 @@ Once you have it all set up, here's how to use it.
`fabric -h` `fabric -h`
```bash ```bash
us the results in usage: fabric [-h] [--text TEXT] [--copy] [--agents {trip_planner,ApiKeys}] [--output [OUTPUT]] [--gui] [--stream] [--list] [--update] [--pattern PATTERN] [--setup] [--changeDefaultModel CHANGEDEFAULTMODEL]
realtime. NOTE: You will not be able to pipe the [--model MODEL] [--listmodels] [--remoteOllamaServer REMOTEOLLAMASERVER] [--context]
output into another command.
--list, -l List available patterns
--clear Clears your persistent model choice so that you can
once again use the --model flag
--update, -u Update patterns. NOTE: This will revert the default
model to gpt4-turbo. please run --changeDefaultModel
to once again set default model
--pattern PATTERN, -p PATTERN
The pattern (prompt) to use
--setup Set up your fabric instance
--changeDefaultModel CHANGEDEFAULTMODEL
Change the default model. For a list of available
models, use the --listmodels flag.
--model MODEL, -m MODEL
Select the model to use. NOTE: Will not work if you
have set a default model. please use --clear to clear
persistence before using this flag
--listmodels List all available models
--remoteOllamaServer REMOTEOLLAMASERVER
The URL of the remote ollamaserver to use. ONLY USE
THIS if you are using a local ollama server in an non-
deault location or port
--context, -c Use Context file (context.md) to add context to your
pattern
age: fabric [-h] [--text TEXT] [--copy] [--agents {trip_planner,ApiKeys}]
[--output [OUTPUT]] [--stream] [--list] [--clear] [--update]
[--pattern PATTERN] [--setup]
[--changeDefaultModel CHANGEDEFAULTMODEL] [--model MODEL]
[--listmodels] [--remoteOllamaServer REMOTEOLLAMASERVER]
[--context]
An open source framework for augmenting humans using AI. An open source framework for augmenting humans using AI.
@ -249,12 +219,24 @@ options:
--text TEXT, -t TEXT Text to extract summary from --text TEXT, -t TEXT Text to extract summary from
--copy, -C Copy the response to the clipboard --copy, -C Copy the response to the clipboard
--agents {trip_planner,ApiKeys}, -a {trip_planner,ApiKeys} --agents {trip_planner,ApiKeys}, -a {trip_planner,ApiKeys}
Use an AI agent to help you with a task. Acceptable Use an AI agent to help you with a task. Acceptable values are 'trip_planner' or 'ApiKeys'. This option cannot be used with any other flag.
values are 'trip_planner' or 'ApiKeys'. This option
cannot be used with any other flag.
--output [OUTPUT], -o [OUTPUT] --output [OUTPUT], -o [OUTPUT]
Save the response to a file Save the response to a file
--stream, -s Use this option if you want to see --gui Use the GUI (Node and npm need to be installed)
--stream, -s Use this option if you want to see the results in realtime. NOTE: You will not be able to pipe the output into another command.
--list, -l List available patterns
--update, -u Update patterns. NOTE: This will revert the default model to gpt4-turbo. please run --changeDefaultModel to once again set default model
--pattern PATTERN, -p PATTERN
The pattern (prompt) to use
--setup Set up your fabric instance
--changeDefaultModel CHANGEDEFAULTMODEL
Change the default model. For a list of available models, use the --listmodels flag.
--model MODEL, -m MODEL
Select the model to use. NOTE: Will not work if you have set a default model. please use --clear to clear persistence before using this flag
--listmodels List all available models
--remoteOllamaServer REMOTEOLLAMASERVER
The URL of the remote ollamaserver to use. ONLY USE THIS if you are using a local ollama server in an non-deault location or port
--context, -c Use Context file (context.md) to add context to your pattern
``` ```
#### Example commands #### Example commands
@ -515,7 +497,7 @@ options:
-h, --help Show this help message and exit -h, --help Show this help message and exit
--duration Output only the duration --duration Output only the duration
--transcript Output only the transcript --transcript Output only the transcript
--comments Output only the user comments --comments Output only the user comments
``` ```
## ts (Audio transcriptions) ## ts (Audio transcriptions)
@ -536,7 +518,7 @@ windows:
download instructions https://www.ffmpeg.org/download.html download instructions https://www.ffmpeg.org/download.html
``` ```
````bash ```bash
ts -h ts -h
usage: ts [-h] audio_file usage: ts [-h] audio_file
@ -547,17 +529,17 @@ positional arguments:
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
```` ```
## Save ## Save
`save` is a "tee-like" utility to pipeline saving of content, while keeping the output stream intact. Can optionally generate "frontmatter" for PKM utilities like Obsidian via the `save` is a "tee-like" utility to pipeline saving of content, while keeping the output stream intact. Can optionally generate "frontmatter" for PKM utilities like Obsidian via the
"FABRIC_FRONTMATTER" environment variable "FABRIC_FRONTMATTER" environment variable
If you'd like to default variables, set them in `~/.config/fabric/.env`. `FABRIC_OUTPUT_PATH` needs to be set so `save` where to write. `FABRIC_FRONTMATTER_TAGS` is optional, but useful for tracking how tags have entered your PKM, if that's important to you. If you'd like to default variables, set them in `~/.config/fabric/.env`. `FABRIC_OUTPUT_PATH` needs to be set so `save` where to write. `FABRIC_FRONTMATTER_TAGS` is optional, but useful for tracking how tags have entered your PKM, if that's important to you.
### usage ### usage
```bash ```bash
usage: save [-h] [-t, TAG] [-n] [-s] [stub] usage: save [-h] [-t, TAG] [-n] [-s] [stub]
@ -572,7 +554,7 @@ options:
-t, TAG, --tag TAG add an additional frontmatter tag. Use this argument multiple timesfor multiple tags -t, TAG, --tag TAG add an additional frontmatter tag. Use this argument multiple timesfor multiple tags
-n, --nofabric don't use the fabric tags, only use tags from --tag -n, --nofabric don't use the fabric tags, only use tags from --tag
-s, --silent don't use STDOUT for output, only save to the file -s, --silent don't use STDOUT for output, only save to the file
```` ```
### Example ### Example

7
installer/client/cli/fabric.py

@ -1,4 +1,4 @@
from .utils import Standalone, Update, Setup, Alias from .utils import Standalone, Update, Setup, Alias, run_electron_app
import argparse import argparse
import sys import sys
import os import os
@ -28,6 +28,8 @@ def main():
const="analyzepaper.txt", const="analyzepaper.txt",
default=None, default=None,
) )
parser.add_argument(
"--gui", help="Use the GUI (Node and npm need to be installed)", action="store_true")
parser.add_argument( parser.add_argument(
"--stream", "--stream",
"-s", "-s",
@ -90,6 +92,9 @@ def main():
from .utils import AgentSetup from .utils import AgentSetup
AgentSetup().run() AgentSetup().run()
sys.exit() sys.exit()
if args.gui:
run_electron_app()
sys.exit()
if args.update: if args.update:
Update() Update()
Alias() Alias()

35
installer/client/cli/utils.py

@ -8,6 +8,7 @@ import platform
from dotenv import load_dotenv from dotenv import load_dotenv
import zipfile import zipfile
import tempfile import tempfile
import subprocess
import shutil import shutil
from youtube_transcript_api import YouTubeTranscriptApi from youtube_transcript_api import YouTubeTranscriptApi
@ -663,3 +664,37 @@ class AgentSetup:
else: else:
# If it does not end with a newline, add one before the new entries # If it does not end with a newline, add one before the new entries
f.write(f"\n{browserless_entry}\n{serper_entry}\n") f.write(f"\n{browserless_entry}\n{serper_entry}\n")
def run_electron_app():
# Step 1: Set CWD to the directory of the script
os.chdir(os.path.dirname(os.path.realpath(__file__)))
# Step 2: Check for the './installer/client/gui' directory
target_dir = '../gui'
if not os.path.exists(target_dir):
print(f"""The directory {
target_dir} does not exist. Please check the path and try again.""")
return
# Step 3: Check for NPM installation
try:
subprocess.run(['npm', '--version'], check=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
print("NPM is not installed. Please install NPM and try again.")
return
# If this point is reached, NPM is installed.
# Step 4: Change directory to the Electron app's directory
os.chdir(target_dir)
# Step 5: Run 'npm install' and 'npm start'
try:
print("Running 'npm install'... This might take a few minutes.")
subprocess.run(['npm', 'install'], check=True)
print(
"'npm install' completed successfully. Starting the Electron app with 'npm start'...")
subprocess.run(['npm', 'start'], check=True)
except subprocess.CalledProcessError as e:
print(f"An error occurred while executing NPM commands: {e}")

45
installer/client/gui/chatgpt.js

@ -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 };

32
installer/client/gui/index.html

@ -36,6 +36,9 @@
> >
Update Patterns Update Patterns
</button> </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="collapse navbar-collapse" id="navbarCollapse"></div>
<div class="m1-auto"> <div class="m1-auto">
<a class="navbar-brand" id="themeChanger" href="#">Dark</a> <a class="navbar-brand" id="themeChanger" href="#">Dark</a>
@ -43,7 +46,10 @@
</nav> </nav>
<main> <main>
<div class="container" id="my-form"> <div class="container" id="my-form">
<select class="form-control" id="patternSelector"></select> <div class="selector-container">
<select class="form-control" id="patternSelector"></select>
<select class="form-control" id="modelSelector"></select>
</div>
<textarea <textarea
rows="5" rows="5"
class="form-control" class="form-control"
@ -52,6 +58,24 @@
></textarea> ></textarea>
<button class="btn btn-primary" id="submit">Submit</button> <button class="btn btn-primary" id="submit">Submit</button>
</div> </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"> <div id="configSection" class="container hidden">
<input <input
type="text" type="text"
@ -59,6 +83,12 @@
placeholder="Enter OpenAI API Key" placeholder="Enter OpenAI API Key"
class="form-control" 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> <button id="saveApiKey" class="btn btn-primary">Save API Key</button>
</div> </div>
<div class="container hidden" id="responseContainer"></div> <div class="container hidden" id="responseContainer"></div>

530
installer/client/gui/main.js

@ -1,94 +1,46 @@
const { app, BrowserWindow, ipcMain, dialog } = require("electron"); const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const pdfParse = require("pdf-parse"); const fs = require("fs").promises;
const mammoth = require("mammoth");
const fs = require("fs");
const path = require("path"); const path = require("path");
const os = require("os"); const os = require("os");
const { queryOpenAI } = require("./chatgpt.js"); const OpenAI = require("openai");
const Ollama = require("ollama");
const Anthropic = require("@anthropic-ai/sdk");
const axios = require("axios"); const axios = require("axios");
const fsExtra = require("fs-extra"); const fsExtra = require("fs-extra");
const fsConstants = require("fs").constants;
let fetch, allModels;
let fetch;
import("node-fetch").then((module) => { import("node-fetch").then((module) => {
fetch = module.default; fetch = module.default;
}); });
const unzipper = require("unzipper"); const unzipper = require("unzipper");
let win; let win;
let openai;
let ollama = new Ollama.Ollama();
function promptUserForApiKey() { async function ensureFabricFoldersExist() {
// Create a new window to prompt the user for the API key const fabricPath = path.join(os.homedir(), ".config", "fabric");
const promptWindow = new BrowserWindow({ const patternsPath = path.join(fabricPath, "patterns");
// 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)) { try {
fs.mkdirSync(configPath, { recursive: true }); 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
} }
fs.writeFileSync(envFilePath, `OPENAI_API_KEY=${apiKey}`);
process.env.OPENAI_API_KEY = apiKey; // Set for current session
} }
function ensureFabricFoldersExist() { async function downloadAndUpdatePatterns() {
return new Promise(async (resolve, reject) => {
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
}
});
}
async function downloadAndUpdatePatterns(patternsPath) {
try { try {
// Download the zip file
const response = await axios({ const response = await axios({
method: "get", method: "get",
url: "https://github.com/danielmiessler/fabric/archive/refs/heads/main.zip", url: "https://github.com/danielmiessler/fabric/archive/refs/heads/main.zip",
@ -99,16 +51,15 @@ async function downloadAndUpdatePatterns(patternsPath) {
fs.writeFileSync(zipPath, response.data); fs.writeFileSync(zipPath, response.data);
console.log("Zip file written to:", zipPath); console.log("Zip file written to:", zipPath);
// Prepare for extraction
const tempExtractPath = path.join(os.tmpdir(), "fabric_extracted"); const tempExtractPath = path.join(os.tmpdir(), "fabric_extracted");
fsExtra.emptyDirSync(tempExtractPath); await fsExtra.emptyDir(tempExtractPath);
await fsExtra.remove(patternsPath); // Delete the existing patterns directory
// Extract the zip file
await fs await fs
.createReadStream(zipPath) .createReadStream(zipPath)
.pipe(unzipper.Extract({ path: tempExtractPath })) .pipe(unzipper.Extract({ path: tempExtractPath }))
.promise(); .promise();
console.log("Extraction complete"); console.log("Extraction complete");
const extractedPatternsPath = path.join( const extractedPatternsPath = path.join(
@ -117,30 +68,207 @@ async function downloadAndUpdatePatterns(patternsPath) {
"patterns" "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"); console.log("Patterns successfully updated");
// Inform the renderer process that the patterns have been updated // Inform the renderer process that the patterns have been updated
win.webContents.send("patterns-updated"); // win.webContents.send("patterns-updated");
} catch (error) { } catch (error) {
console.error("Error downloading or updating patterns:", error); console.error("Error downloading or updating patterns:", error);
} }
} }
function getPatternFolders() {
const patternsPath = path.join(os.homedir(), ".config", "fabric", "patterns");
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"); 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 getPatternFolders() { async function loadApiKeys() {
const patternsPath = path.join(os.homedir(), ".config", "fabric", "patterns"); const configPath = path.join(os.homedir(), ".config", "fabric", ".env");
return fs let keys = { openAIKey: null, claudeKey: null };
.readdirSync(patternsPath, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory()) try {
.map((dirent) => dirent.name); 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];
}
if (claudeMatch && claudeMatch[1]) {
keys.claudeKey = claudeMatch[1];
claude = new Anthropic({ apiKey: keys.claudeKey });
}
} catch (error) {
console.error("Could not load API keys:", error);
}
return keys;
}
async function saveApiKeys(openAIKey, claudeKey) {
const configPath = path.join(os.homedir(), ".config", "fabric");
const envFilePath = path.join(configPath, ".env");
try {
await fs.access(configPath);
} catch {
await fs.mkdir(configPath, { recursive: true });
}
let envContent = "";
// Read the existing .env file if it exists
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
if (openAIKey) {
envContent = updateOrAddKey(envContent, "OPENAI_API_KEY", openAIKey);
process.env.OPENAI_API_KEY = openAIKey; // Set for current session
openai = new OpenAI({ apiKey: openAIKey });
}
if (claudeKey) {
envContent = updateOrAddKey(envContent, "CLAUDE_API_KEY", claudeKey);
process.env.CLAUDE_API_KEY = claudeKey; // Set for current session
claude = new Anthropic({ apiKey: claudeKey });
}
await fs.writeFile(envFilePath, envContent.trim());
await loadApiKeys();
win.webContents.send("api-keys-saved");
}
function updateOrAddKey(envContent, keyName, keyValue) {
const keyPattern = new RegExp(`^${keyName}=.*$`, "m");
if (keyPattern.test(envContent)) {
// Update the existing key
envContent = envContent.replace(keyPattern, `${keyName}=${keyValue}`);
} else {
// Add the new key
envContent += `\n${keyName}=${keyValue}`;
}
return envContent;
} }
function getPatternContent(patternName) { async function getOllamaModels() {
try {
ollama = new Ollama.Ollama();
const _models = await ollama.list();
return _models.models.map((x) => x.name);
} catch (error) {
if (error.cause && error.cause.code === "ECONNREFUSED") {
console.error(
"Failed to connect to Ollama. Make sure Ollama is running and accessible."
);
return []; // Return an empty array instead of throwing an error
} else {
console.error("Error fetching models from Ollama:", error);
throw error; // Re-throw the error for other types of errors
}
}
}
async function getModels() {
allModels = {
gptModels: [],
claudeModels: [],
ollamaModels: [],
};
let keys = await loadApiKeys();
if (keys.claudeKey) {
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 });
try {
const response = await openai.models.list();
allModels.gptModels = response.data;
} catch (error) {
console.error("Error fetching models from OpenAI:", error);
}
}
// Check if ollama exists and has a list method
if (
typeof ollama !== "undefined" &&
ollama.list &&
typeof ollama.list === "function"
) {
try {
allModels.ollamaModels = await getOllamaModels();
} catch (error) {
console.error("Error fetching models from Ollama:", error);
}
} else {
console.log("Ollama is not available or does not support listing models.");
}
return allModels;
}
async function getPatternContent(patternName) {
const patternPath = path.join( const patternPath = path.join(
os.homedir(), os.homedir(),
".config", ".config",
@ -150,13 +278,119 @@ function getPatternContent(patternName) {
"system.md" "system.md"
); );
try { try {
return fs.readFileSync(patternPath, "utf8"); const content = await fs.readFile(patternPath, "utf8");
return content;
} catch (error) { } catch (error) {
console.error("Error reading pattern file:", error); console.error("Error reading pattern file:", error);
return ""; return "";
} }
} }
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) {
if (!claude) {
event.reply(
"model-response-error",
"Claude API key is missing or invalid."
);
return;
}
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);
}
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() { function createWindow() {
win = new BrowserWindow({ win = new BrowserWindow({
width: 800, width: 800,
@ -174,57 +408,49 @@ function createWindow() {
win = null; win = null;
}); });
} }
ipcMain.on("process-complex-file", (event, filePath) => {
const extension = path.extname(filePath).toLowerCase(); ipcMain.on("start-query", async (event, system, user, model) => {
let fileProcessPromise; if (system == null || user == null || model == null) {
console.error("Received null for system, user message, or model");
if (extension === ".pdf") { event.reply(
const dataBuffer = fs.readFileSync(filePath); "model-response-error",
fileProcessPromise = pdfParse(dataBuffer).then((data) => data.text); "Error: System, user message, or model is null."
} 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; return;
} }
fileProcessPromise try {
.then((extractedText) => { const _gptModels = allModels.gptModels.map((model) => model.id);
// Sending the extracted text back to the frontend. if (allModels.claudeModels.includes(model)) {
event.reply("file-response", extractedText); await claudeMessage(system, user, model, event);
}) } else if (_gptModels.includes(model)) {
.catch((error) => { await openaiMessage(system, user, model, event);
// Handling any errors during file processing and sending them back to the frontend. } else if (allModels.ollamaModels.includes(model)) {
event.reply("file-response", `Error processing file: ${error.message}`); await ollamaMessage(system, user, model, event);
}); } else {
event.reply("model-response-error", "Unsupported model: " + model);
}
} catch (error) {
console.error("Error querying model:", error);
event.reply("model-response-error", "Error querying model.");
}
}); });
ipcMain.on("start-query-openai", async (event, system, user) => { ipcMain.handle("create-pattern", async (event, patternName, patternContent) => {
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.");
return;
}
try { try {
await queryOpenAI(system, user, (message) => { const result = await createPatternFolder(patternName, patternContent);
event.reply("openai-response", message); return { status: "success", message: result }; // Use a response object for more detailed responses
});
} catch (error) { } catch (error) {
console.error("Error querying OpenAI:", error); console.error("Error creating pattern:", error);
event.reply("no-api-key", "Error querying OpenAI."); return { status: "error", message: error.message }; // Return an error object
} }
}); });
// Example of using ipcMain.handle for asynchronous operations // Example of using ipcMain.handle for asynchronous operations
ipcMain.handle("get-patterns", async (event) => { ipcMain.handle("get-patterns", async (event) => {
try { try {
return getPatternFolders(); const patterns = await getPatternFolders();
return patterns;
} catch (error) { } catch (error) {
console.error("Failed to get patterns:", error); console.error("Failed to get patterns:", error);
return []; return [];
@ -238,51 +464,43 @@ ipcMain.on("update-patterns", () => {
ipcMain.handle("get-pattern-content", async (event, patternName) => { ipcMain.handle("get-pattern-content", async (event, patternName) => {
try { try {
return getPatternContent(patternName); const content = await getPatternContent(patternName);
return content;
} catch (error) { } catch (error) {
console.error("Failed to get pattern content:", error); console.error("Failed to get pattern content:", error);
return ""; return "";
} }
}); });
ipcMain.handle("save-api-key", async (event, apiKey) => { ipcMain.handle("save-api-keys", async (event, { openAIKey, claudeKey }) => {
try { try {
const configPath = path.join(os.homedir(), ".config", "fabric"); await saveApiKeys(openAIKey, claudeKey);
if (!fs.existsSync(configPath)) { return "API Keys saved successfully.";
fs.mkdirSync(configPath, { recursive: true }); } catch (error) {
} console.error("Error saving API keys:", error);
throw new Error("Failed to save API Keys.");
const envFilePath = path.join(configPath, ".env"); }
fs.writeFileSync(envFilePath, `OPENAI_API_KEY=${apiKey}`); });
process.env.OPENAI_API_KEY = apiKey;
return "API Key saved successfully."; ipcMain.handle("get-models", async (event) => {
try {
const models = await getModels();
return models;
} catch (error) { } catch (error) {
console.error("Error saving API key:", error); console.error("Failed to get models:", error);
throw new Error("Failed to save API Key."); return { gptModels: [], claudeModels: [], ollamaModels: [] };
} }
}); });
app.whenReady().then(async () => { app.whenReady().then(async () => {
try { try {
const apiKey = loadApiKey(); const keys = await loadApiKeys();
if (!apiKey) {
promptUserForApiKey();
} else {
process.env.OPENAI_API_KEY = apiKey;
createWindow();
}
await ensureFabricFoldersExist(); // Ensure fabric folders exist await ensureFabricFoldersExist(); // Ensure fabric folders exist
createWindow(); // Create the application window await getModels(); // Fetch models after loading API keys
createWindow(); // Keep this line
// 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) { } 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) // Handle initialization failure (e.g., close the app or show an error message)
} }
}); });

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

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

4
installer/client/gui/package.json

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

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

@ -1,5 +1,6 @@
document.addEventListener("DOMContentLoaded", async function () { document.addEventListener("DOMContentLoaded", async function () {
const patternSelector = document.getElementById("patternSelector"); const patternSelector = document.getElementById("patternSelector");
const modelSelector = document.getElementById("modelSelector");
const userInput = document.getElementById("userInput"); const userInput = document.getElementById("userInput");
const submitButton = document.getElementById("submit"); const submitButton = document.getElementById("submit");
const responseContainer = document.getElementById("responseContainer"); const responseContainer = document.getElementById("responseContainer");
@ -7,9 +8,13 @@ document.addEventListener("DOMContentLoaded", async function () {
const configButton = document.getElementById("configButton"); const configButton = document.getElementById("configButton");
const configSection = document.getElementById("configSection"); const configSection = document.getElementById("configSection");
const saveApiKeyButton = document.getElementById("saveApiKey"); const saveApiKeyButton = document.getElementById("saveApiKey");
const apiKeyInput = document.getElementById("apiKeyInput"); const openaiApiKeyInput = document.getElementById("apiKeyInput");
const originalPlaceholder = userInput.placeholder; const claudeApiKeyInput = document.getElementById("claudeApiKeyInput");
const updatePatternsButton = document.getElementById("updatePatternsButton"); 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"); const copyButton = document.createElement("button");
window.electronAPI.on("patterns-ready", () => { window.electronAPI.on("patterns-ready", () => {
@ -17,14 +22,12 @@ document.addEventListener("DOMContentLoaded", async function () {
loadPatterns(); loadPatterns();
}); });
window.electronAPI.on("request-api-key", () => { window.electronAPI.on("request-api-key", () => {
// Show the API key input section or modal to the user configSection.classList.remove("hidden");
configSection.classList.remove("hidden"); // Assuming 'configSection' is your API key input area
}); });
copyButton.textContent = "Copy"; copyButton.textContent = "Copy";
copyButton.id = "copyButton"; copyButton.id = "copyButton";
document.addEventListener("click", function (e) { document.addEventListener("click", function (e) {
if (e.target && e.target.id === "copyButton") { if (e.target && e.target.id === "copyButton") {
// Your copy to clipboard function
copyToClipboard(); copyToClipboard();
} }
}); });
@ -38,54 +41,77 @@ document.addEventListener("DOMContentLoaded", async function () {
}); });
function htmlToPlainText(html) { function htmlToPlainText(html) {
// Create a temporary div element to hold the HTML
var tempDiv = document.createElement("div"); var tempDiv = document.createElement("div");
tempDiv.innerHTML = html; tempDiv.innerHTML = html;
// Replace <br> tags with newline characters
tempDiv.querySelectorAll("br").forEach((br) => br.replaceWith("\n")); tempDiv.querySelectorAll("br").forEach((br) => br.replaceWith("\n"));
// Replace block elements like <p> and <div> with newline characters
tempDiv.querySelectorAll("p, div").forEach((block) => { tempDiv.querySelectorAll("p, div").forEach((block) => {
block.prepend("\n"); // Add a newline before the block element's content block.prepend("\n");
block.replaceWith(...block.childNodes); // Replace the block element with its own contents block.replaceWith(...block.childNodes);
}); });
// Return the text content, trimming leading and trailing newlines
return tempDiv.textContent.trim(); return tempDiv.textContent.trim();
} }
async function submitQuery(userInputValue) { async function submitQuery(userInputValue) {
userInput.value = ""; // Clear the input after submitting userInput.value = ""; // Clear the input after submitting
systemCommand = await window.electronAPI.invoke( const systemCommand = await window.electronAPI.invoke(
"get-pattern-content", "get-pattern-content",
patternSelector.value patternSelector.value
); );
const selectedModel = modelSelector.value;
responseContainer.innerHTML = ""; // Clear previous responses responseContainer.innerHTML = ""; // Clear previous responses
if (responseContainer.classList.contains("hidden")) { if (responseContainer.classList.contains("hidden")) {
console.log("contains hidden");
responseContainer.classList.remove("hidden"); responseContainer.classList.remove("hidden");
responseContainer.appendChild(copyButton); responseContainer.appendChild(copyButton);
} }
window.electronAPI.send( window.electronAPI.send(
"start-query-openai", "start-query",
systemCommand, systemCommand,
userInputValue userInputValue,
selectedModel
); );
} }
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() { function copyToClipboard() {
const containerClone = responseContainer.cloneNode(true); const containerClone = responseContainer.cloneNode(true);
// Remove the copy button from the clone
const copyButtonClone = containerClone.querySelector("#copyButton"); const copyButtonClone = containerClone.querySelector("#copyButton");
if (copyButtonClone) { if (copyButtonClone) {
copyButtonClone.parentNode.removeChild(copyButtonClone); copyButtonClone.parentNode.removeChild(copyButtonClone);
} }
// Convert HTML to plain text, preserving newlines
const plainText = htmlToPlainText(containerClone.innerHTML); const plainText = htmlToPlainText(containerClone.innerHTML);
// Use a temporary textarea for copying
const textArea = document.createElement("textarea"); const textArea = document.createElement("textarea");
textArea.style.position = "absolute"; textArea.style.position = "absolute";
textArea.style.left = "-9999px"; textArea.style.left = "-9999px";
@ -118,47 +144,54 @@ document.addEventListener("DOMContentLoaded", async function () {
} }
} }
function fallbackCopyTextToClipboard(text) { async function loadModels() {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try { try {
const successful = document.execCommand("copy"); const models = await window.electronAPI.invoke("get-models");
const msg = successful ? "successful" : "unsuccessful"; modelSelector.innerHTML = ""; // Clear existing options first
console.log("Fallback: Copying text command was " + msg); models.gptModels.forEach((model) => {
} catch (err) { const option = document.createElement("option");
console.error("Fallback: Oops, unable to copy", err); 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);
alert(
"Failed to load models. Please check the console for more details."
);
} }
document.body.removeChild(textArea);
} }
updatePatternsButton.addEventListener("click", () => { // Load patterns and models on startup
window.electronAPI.send("update-patterns"); loadPatterns();
}); loadModels();
// 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);
}
// Listen for OpenAI responses // Listen for model responses
window.electronAPI.on("openai-response", (message) => { window.electronAPI.on("model-response", (message) => {
const formattedMessage = message.replace(/\n/g, "<br>"); const formattedMessage = message.replace(/\n/g, "<br>");
responseContainer.innerHTML += formattedMessage; // Append new data as it arrives 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) => { window.electronAPI.on("file-response", (message) => {
if (message.startsWith("Error")) { if (message.startsWith("Error")) {
alert(message); alert(message);
@ -167,12 +200,36 @@ document.addEventListener("DOMContentLoaded", async function () {
submitQuery(message); submitQuery(message);
}); });
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.");
}
});
updatePatternsButton.addEventListener("click", async () => {
window.electronAPI.send("update-patterns");
});
// Submit button click handler // Submit button click handler
submitButton.addEventListener("click", async () => { submitButton.addEventListener("click", async () => {
const userInputValue = userInput.value; const userInputValue = userInput.value;
submitQuery(userInputValue); 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 // Theme changer click handler
themeChanger.addEventListener("click", function (e) { themeChanger.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
@ -181,6 +238,14 @@ document.addEventListener("DOMContentLoaded", async function () {
themeChanger.innerText === "Dark" ? "Light" : "Dark"; 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 // Config button click handler - toggles the config section visibility
configButton.addEventListener("click", function (e) { configButton.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
@ -189,18 +254,13 @@ document.addEventListener("DOMContentLoaded", async function () {
// Save API Key button click handler // Save API Key button click handler
saveApiKeyButton.addEventListener("click", () => { saveApiKeyButton.addEventListener("click", () => {
const apiKey = apiKeyInput.value; const openAIKey = openaiApiKeyInput.value;
const claudeKey = claudeApiKeyInput.value;
window.electronAPI 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
configSection.classList.add("hidden");
apiKeyInput.value = "";
})
.catch((err) => { .catch((err) => {
console.error("Error saving API key:", err); console.error("Error saving API keys:", err);
alert("Failed to save API Key."); alert("Failed to save API Keys.");
}); });
}); });
@ -211,7 +271,7 @@ document.addEventListener("DOMContentLoaded", async function () {
"get-pattern-content", "get-pattern-content",
selectedPattern 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 // drag and drop

56
installer/client/gui/static/stylesheet/style.css

@ -37,14 +37,27 @@ body {
border: 1px solid #555; /* Adjusted border color */ border: 1px solid #555; /* Adjusted border color */
padding: 10px; /* Added padding for better text visibility */ padding: 10px; /* Added padding for better text visibility */
} }
#patternSelector {
.selector-container {
display: flex;
gap: 10px;
margin-bottom: 10px; margin-bottom: 10px;
background-color: #424242; /* Darker shade for textarea */ }
color: #e0e0e0; /* Light text for readability */
border: 1px solid #555; /* Adjusted border color */ #patternSelector,
padding: 10px; /* Added padding for better text visibility */ #modelSelector {
flex: 1;
background-color: #424242;
color: #e0e0e0;
border: 1px solid #555;
padding: 10px;
height: 40px; height: 40px;
} }
.light-theme #modelSelector {
background-color: #fff;
color: #333;
border: 1px solid #ddd;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.container { .container {
@ -78,6 +91,7 @@ body {
.hidden { .hidden {
display: none; display: none;
} }
.drag-over { .drag-over {
background-color: #505050; /* Slightly lighter than the regular background for visibility */ background-color: #505050; /* Slightly lighter than the regular background for visibility */
border: 2px dashed #007bff; /* Dashed border with the primary button color for emphasis */ border: 2px dashed #007bff; /* Dashed border with the primary button color for emphasis */
@ -123,6 +137,7 @@ body.light-theme .navbar-toggler-icon {
padding: 0.25rem 0.5rem; /* Adjust padding for the toggle button */ padding: 0.25rem 0.5rem; /* Adjust padding for the toggle button */
} }
} }
#responseContainer { #responseContainer {
position: relative; /* Needed for absolute positioning of the child button */ position: relative; /* Needed for absolute positioning of the child button */
} }
@ -158,3 +173,34 @@ body.light-theme .navbar-toggler-icon {
#copyButton:focus { #copyButton:focus {
outline: none; 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;
}
#patternCreator input {
background-color: #424242;
color: #e0e0e0;
border: 1px solid #555;
}

31
patterns/create_investigation_visualization/system.md

@ -0,0 +1,31 @@
# IDENTITY AND GOAL
You are an expert in intelligence investigations and data visualization using GraphViz. You create full, detailed graphviz visualizations of the input you're given that show the most interesting, surprising, and useful aspects of the input.
# STEPS
- Fully understand the input you were given.
- Spend 3,503 virtual hours taking notes on and organizing your understanding of the input.
- Capture all your understanding of the input on a virtual whiteboard in your mind.
- Think about how you would graph your deep understanding of the concepts in the input into a Graphviz output.
# OUTPUT
- Create a full Graphviz output of all the most interesting aspects of the input.
- Use different shapes and colors to represent different types of nodes.
- Label all nodes, connections, and edges with the most relevant information.
- In the diagram and labels, make the verbs and subjects are clear, e.g., "called on phone, met in person, accessed the database."
- Ensure all the activities in the investigation are represented, including research, data sources, interviews, conversations, timelines, and conclusions.
- Ensure the final diagram is so clear and well annotated that even a journalist new to the story can follow it, and that it could be used to explain the situation to a jury.
- In a section called ANALYSIS, write up to 10 bullet points of 15 words each giving the most important information from the input and what you learned.
- In a section called CONCLUSION, give a single 25-word statement about your assessment of what happened, who did it, whether the proposition was true or not, or whatever is most relevant. In the final sentence give the CIA rating of certainty for your conclusion.
Loading…
Cancel
Save