Repository: Mintplex-Labs/anythingllm-extension
Branch: main
Commit: 385d36c08072
Files: 18
Total size: 27.9 KB
Directory structure:
gitextract_zf654o63/
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── package.json
├── postcss.config.js
├── public/
│ ├── background.js
│ ├── contentScript.js
│ └── manifest.json
├── src/
│ ├── App.jsx
│ ├── components/
│ │ └── Config.jsx
│ ├── hooks/
│ │ └── useApiConnection.js
│ ├── index.css
│ ├── main.jsx
│ ├── models/
│ │ └── browserExtension.js
│ └── utils/
│ └── constants.js
├── tailwind.config.js
└── vite.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: LICENSE
================================================
The MIT License
Copyright (c) Mintplex Labs Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# AnythingLLM Chrome Extension
<p align="center">
<img src="src/media/anything-llm.png" alt="AnythingLLM Chrome Extension logo" width="200">
</p>
<p align="center">
Seamlessly integrate AnythingLLM into Google Chrome.
</p>
<p align="center">
<a href="#features">Features</a> •
<a href="#installation">Installation</a> •
<a href="#development">Development</a> •
<a href="#usage">Usage</a> •
<a href="#contributing">Contributing</a> •
<a href="#license">License</a>
</p>
## Features
- 🔗 Connect to your AnythingLLM instance with a simple connection string or automatic browser extension registration
- 📑 Save selected text to AnythingLLM directly from any webpage
- 📄 Upload entire web pages to AnythingLLM for processing
- 🗂️ Embed content into specific workspaces
- 🔄 Automatic logo synchronization with your AnythingLLM instance
## Installation
<a href="https://chromewebstore.google.com/detail/anythingllm-browser-compa/pncmdlebcopjodenlllcomedphdmeogm">
<img src="https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/iNEddTyWiMfLSwFD6qGq.png" alt="Chrome Extension" width="200">
</a>
_or_
1. Clone this repository or download the latest release.
2. Open Chrome and navigate to `chrome://extensions`.
3. Enable "Developer mode" in the top right corner.
4. Click "Load unpacked" and select the `dist` folder from this project.
## Development
To set up the project for development:
1. Install dependencies:
```
yarn install
```
2. Run the development server:
```
yarn dev
```
3. To build the extension:
```
yarn build
```
The built extension will be in the `dist` folder.
## Usage
1. Click on the AnythingLLM extension icon in your Chrome toolbar.
2. Enter your AnythingLLM browser extension API key to connect to your instance (or create the API key inside AnythingLLM and have it automatically register to the extension).
3. Right-click on selected text or anywhere on a webpage to see AnythingLLM options.
4. Choose to save selected text or the entire page to AnythingLLM.
## Contributing
Contributions are welcome! Feel free to submit a PR.
## Acknowledgements
- This extension is designed to work with [AnythingLLM](https://github.com/Mintplex-Labs/anything-llm).
---
Copyright © 2024 [Mintplex Labs](https://github.com/Mintplex-Labs). <br />
This project is [MIT](../LICENSE) licensed.
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AnythingLLM Document Saver</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "anything-llm-extension",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "nodemon --watch src --watch public -e js,jsx,css,html --exec \"yarn dev:build\"",
"dev:build": "vite build && cp public/background.js dist/",
"build": "vite build && cp public/background.js dist/",
"lint": "yarn prettier --ignore-path ../.prettierignore --write ./src",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"nodemon": "^3.1.4",
"postcss": "^8.4.40",
"prettier": "^3.0.3",
"tailwindcss": "^3.4.7",
"vite": "^5.3.4"
}
}
================================================
FILE: postcss.config.js
================================================
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: public/background.js
================================================
const ContextMenuModel = {
async create(workspaces) {
await chrome.contextMenus.removeAll();
if (workspaces && workspaces.length > 0) {
chrome.contextMenus.create({
id: "saveToAnythingLLM",
title: "Save selected to AnythingLLM",
contexts: ["selection"],
});
chrome.contextMenus.create({
id: "embedToWorkspace",
title: "Embed selected content to workspace",
contexts: ["selection"],
});
chrome.contextMenus.create({
id: "saveEntirePageToAnythingLLM",
title: "Save entire page to AnythingLLM",
contexts: ["page"],
});
chrome.contextMenus.create({
id: "embedEntirePageToWorkspace",
title: "Embed entire page to workspace",
contexts: ["page"],
});
workspaces.forEach((workspace) => {
chrome.contextMenus.create({
id: `workspace-selected-${workspace.id}`,
parentId: "embedToWorkspace",
title: workspace.name,
contexts: ["selection"],
});
chrome.contextMenus.create({
id: `workspace-page-${workspace.id}`,
parentId: "embedEntirePageToWorkspace",
title: workspace.name,
contexts: ["page"],
});
});
} else {
chrome.contextMenus.create({
id: "saveToAnythingLLM",
title: "Save selected to AnythingLLM",
contexts: ["selection"],
});
chrome.contextMenus.create({
id: "saveEntirePageToAnythingLLM",
title: "Save entire page to AnythingLLM",
contexts: ["page"],
});
}
},
async remove() {
await chrome.contextMenus.removeAll();
},
};
const ExtensionModel = {
async checkApiKeyValidity() {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) {
await ContextMenuModel.remove();
return false;
}
const data = await fetch(`${apiBase}/browser-extension/check`, {
headers: { Authorization: `Bearer ${apiKey}` },
})
.then((res) => {
if (!res.ok) throw new Error('Response not ok.')
return res.json();
})
.catch(() => null);
if (data === null) {
await chrome.storage.sync.remove(["apiBase", "apiKey"]);
await ContextMenuModel.remove();
return false;
}
await ContextMenuModel.create(data.workspaces);
return true;
},
async updateWorkspaces() {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) return await ContextMenuModel.remove();
const data = await fetch(`${apiBase}/browser-extension/check`, {
headers: { Authorization: `Bearer ${apiKey}` },
})
.then((res) => {
if (!res.ok) throw new Error('Response not ok.')
return res.json();
})
.catch(() => null);
if (data === null) return await ContextMenuModel.remove();
await ContextMenuModel.create(data.workspaces);
return;
},
async saveToAnythingLLM(selectedText, pageTitle, pageUrl) {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) return;
this.showNotification(
'loading',
"Uploading entire page into available documents. Please wait."
);
const response = await fetch(
`${apiBase}/browser-extension/upload-content`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
textContent: selectedText,
metadata: { title: pageTitle, url: pageUrl },
}),
}
);
this.handleResponse(response, "save content");
},
async embedToWorkspace(workspaceId, selectedText, pageTitle, pageUrl) {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) return;
this.showNotification(
'loading',
"Uploading selected text into workspace. Please wait."
);
const response = await fetch(`${apiBase}/browser-extension/embed-content`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
workspaceId,
textContent: selectedText,
metadata: { title: pageTitle, url: pageUrl },
}),
});
this.handleResponse(response, "embed content");
},
async saveEntirePageToAnythingLLM(pageContent, pageTitle, pageUrl) {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) return;
this.showNotification(
'loading',
"Uploading entire page text into available documents. Please wait."
);
const response = await fetch(
`${apiBase}/browser-extension/upload-content`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
textContent: pageContent,
metadata: { title: pageTitle, url: pageUrl },
}),
}
);
this.handleResponse(response, "save entire page");
},
async embedEntirePageToWorkspace(
workspaceId,
pageContent,
pageTitle,
pageUrl
) {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) return;
this.showNotification(
'loading',
"Embedding entire page into workspace. Please wait."
);
const response = await fetch(`${apiBase}/browser-extension/embed-content`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
workspaceId,
textContent: pageContent,
metadata: { title: pageTitle, url: pageUrl },
}),
});
this.handleResponse(response, "embed entire page");
},
async handleResponse(response, action) {
if (response.status === 401 || response.status === 403) {
await chrome.storage.sync.remove(["apiBase", "apiKey"]);
await ContextMenuModel.remove();
this.showNotification(
'error',
"Authentication failed. Please reconnect the extension."
);
} else if (!response.ok) {
await this.checkApiKeyValidity();
this.showNotification("error", `Failed to ${action}. Please try again.`);
} else {
this.showNotification(
"success",
"Successfully saved content to AnythingLLM."
);
}
},
/**
* Shows badge notification on extension icon
* @param {"success"|"error"|"loading"} type
* @param {string} message
*/
showNotification(type, message) {
const NOTIFICATION_MAP = {
success: {
title: "Success",
icon: "✅",
},
error: {
title: "Error",
icon: "❌",
},
loading: {
title: "Loading",
icon: "⏳",
}
}
if (!NOTIFICATION_MAP.hasOwnProperty(type)) return;
const { icon, title } = NOTIFICATION_MAP[type];
chrome.action.setBadgeText({ text: icon })
chrome.action.setTitle({ title: `${title}: ${message}` });
setTimeout(() => {
chrome.action.setBadgeText({ text: "" });
chrome.action.setTitle({ title: "AnythingLLM Extension" });
}, 5000);
},
};
// Event Listeners
chrome.runtime.onInstalled.addListener(async () => {
await ExtensionModel.checkApiKeyValidity();
});
chrome.runtime.onMessage.addListener((message, _sender, _sendResponse) => {
if (message.action === "connectionUpdated") return ExtensionModel.checkApiKeyValidity();
if (message.action === "newApiKey") {
const [apiBase, apiKey] = message.connectionString.split("|");
chrome.storage.sync.set({ apiBase, apiKey }, () => {
ExtensionModel.checkApiKeyValidity();
chrome.action.openPopup();
});
return;
}
});
function getPageContent(tabId) {
return new Promise((resolve, reject) => {
chrome.tabs.sendMessage(tabId, { action: "getPageContent" }, (response) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else if (response && response.content) {
resolve(response.content);
} else {
reject(new Error("Failed to get page content"));
}
});
});
}
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === "saveToAnythingLLM") {
ExtensionModel.saveToAnythingLLM(info.selectionText, tab.title, tab.url);
return;
}
if (info.menuItemId.startsWith("workspace-selected-")) {
const workspaceId = info.menuItemId.split("-")[2];
ExtensionModel.embedToWorkspace(
workspaceId,
info.selectionText,
tab.title,
tab.url
);
return;
}
if (info.menuItemId === "saveEntirePageToAnythingLLM") {
getPageContent(tab.id)
.then((content) => {
ExtensionModel.saveEntirePageToAnythingLLM(content, tab.title, tab.url);
})
.catch((error) => {
console.error("Error getting page content:", error);
ExtensionModel.showNotification(
"error",
"Failed to get page content. Please try again."
);
});
return;
}
if (info.menuItemId.startsWith("workspace-page-")) {
const workspaceId = info.menuItemId.split("-")[2];
getPageContent(tab.id)
.then((content) => {
ExtensionModel.embedEntirePageToWorkspace(
workspaceId,
content,
tab.title,
tab.url
);
})
.catch((error) => {
console.error("Error getting page content:", error);
ExtensionModel.showNotification(
"error",
"Failed to get page content. Please try again."
);
});
return;
}
});
// Remove context menu items when connection is lost
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === "sync" && (changes.apiBase || changes.apiKey)) {
if (!changes.apiBase?.newValue || !changes.apiKey?.newValue) {
ContextMenuModel.remove();
}
}
});
// Update workspaces periodically
chrome.alarms.create("updateWorkspaces", { periodInMinutes: 1 });
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === "updateWorkspaces") {
ExtensionModel.updateWorkspaces();
}
});
================================================
FILE: public/contentScript.js
================================================
window.addEventListener("message", (event) => {
if (event.data.type === "NEW_BROWSER_EXTENSION_CONNECTION") {
chrome.runtime.sendMessage({
action: "newApiKey",
connectionString: event.data.apiKey,
});
}
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "getPageContent") {
sendResponse({ content: document.body.innerText });
}
});
================================================
FILE: public/manifest.json
================================================
{
"manifest_version": 3,
"name": "AnythingLLM Browser Companion",
"version": "1.0.0",
"description": "Bring AnythingLLM directly into your browser to curate and collect content on the web directly into your AnythingLLM workspaces.",
"icons": {
"16": "icon16.png",
"32": "icon32.png",
"48": "icon48.png",
"128": "icon128.png"
},
"permissions": [
"contextMenus",
"activeTab",
"storage",
"notifications",
"alarms"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "index.html",
"default_icon": {
"16": "icon16.png",
"32": "icon32.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"contentScript.js"
]
}
]
}
================================================
FILE: src/App.jsx
================================================
import React from "react";
import Config from "./components/Config";
import useApiConnection from "./hooks/useApiConnection";
const App = () => {
const { status, checkApiKeyStatus, logoUrl } = useApiConnection();
return (
<div className="p-6 bg-[#25272C] min-h-screen flex flex-col items-center">
<img src={logoUrl} alt="AnythingLLM Logo" className="w-40 mb-6" />
<div className="bg-[#2C2E33] p-6 rounded-lg shadow-lg w-full max-w-md">
<p className="text-white text-sm font-medium mb-6">
Right click on any page and send selected text or entire pages to
AnythingLLM.
</p>
<Config status={status} onStatusChange={checkApiKeyStatus} />
</div>
</div>
);
};
export default App;
================================================
FILE: src/components/Config.jsx
================================================
import React, { useState, useEffect } from "react";
import BrowserExtension from "../models/browserExtension";
export default function Config({ status, onStatusChange }) {
const [connectionString, setConnectionString] = useState("");
const [saveStatus, setSaveStatus] = useState("");
useEffect(() => {
if (saveStatus) {
const timer = setTimeout(() => {
setSaveStatus("");
}, 5000);
return () => clearTimeout(timer);
}
}, [saveStatus]);
// Disconnects & de-registers the current extension from the set API key.
async function disconnectFromExtension() {
await chrome.storage.sync.remove(["apiBase", "apiKey"]);
onStatusChange();
setSaveStatus("Successfully disconnected from AnythingLLM");
chrome.runtime.sendMessage({ action: "connectionUpdated" });
}
const handleConnect = async () => {
try {
const [apiBase, apiKey] = connectionString.split("|");
if (!apiBase || !apiKey) {
setSaveStatus("Invalid connection string format.");
return;
}
const { online } = await BrowserExtension.checkOnline(apiBase);
if (!online) {
setSaveStatus(
"AnythingLLM is currently offline. Please try again later."
);
return;
}
const { response } = await BrowserExtension.checkApiKey(apiBase, apiKey);
if (!response.ok)
return setSaveStatus("Failed to connect: Invalid API key");
// Saves the apiBase and apiKey to storage sync.
await chrome.storage.sync.set({ apiBase, apiKey });
onStatusChange();
setSaveStatus("Successfully connected to AnythingLLM");
chrome.runtime.sendMessage({ action: "connectionUpdated" });
} catch (error) {
setSaveStatus(`An error occurred during connection: ${error.message}`);
}
};
const handleDisconnect = async () => {
try {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) throw new Error("No connection found");
const { success, error } = await BrowserExtension.disconnect(
apiBase,
apiKey
);
if (!success)
throw new Error(error || "Failed to disconnect from the server");
await disconnectFromExtension();
} catch (error) {
setSaveStatus(`An error occurred during disconnection: ${error.message}`);
}
};
return (
<div className="w-full flex flex-col gap-y-4">
{status === "notConnected" && (
<div className="w-full flex flex-col gap-y-4">
<div className="flex flex-col w-full">
<label className="text-white text-sm font-semibold block mb-3">
AnythingLLM Connection String
</label>
<input
type="text"
value={connectionString}
onChange={(e) => setConnectionString(e.target.value)}
placeholder="Paste connection string here"
className="bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-[#46C8FF] active:outline-[#46C8FF] outline-none block w-full p-2.5"
/>
</div>
<button
onClick={handleConnect}
className="bg-[#46C8FF] hover:bg-[#3BA3D0] text-white font-bold py-2 px-4 rounded-lg transition duration-300 border border-[#46C8FF] hover:border-[#3BA3D0] focus:outline-none focus:ring-2 focus:ring-[#46C8FF] focus:ring-opacity-50"
>
Connect
</button>
</div>
)}
{status === "connected" && (
<div className="w-full flex flex-col gap-y-4">
<div className="flex items-center justify-center gap-x-2 bg-zinc-900 p-2.5 rounded-lg">
<div className="w-2 h-2 rounded-full bg-green-400 animate-pulse" />
<p className="text-green-400 text-sm font-medium">
Connected to AnythingLLM
</p>
</div>
<button
onClick={handleDisconnect}
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg transition duration-300 border border-red-500 hover:border-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50"
>
Disconnect
</button>
</div>
)}
{status === "offline" && (
<div className="w-full flex flex-col gap-y-4">
<button
onClick={disconnectFromExtension}
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg transition duration-300 border border-red-500 hover:border-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50"
>
Disconnect
</button>
<div className="bg-red-500/10 border border-red-500/50 text-red-400 p-2.5 rounded-lg">
AnythingLLM is currently offline. Please try again later.
</div>
</div>
)}
{status === "error" && (
<div className="bg-red-500/10 border border-red-500/50 text-red-400 p-2.5 rounded-lg">
An error occurred. Please try again later.
</div>
)}
{saveStatus && (
<div
className={`p-2.5 rounded-lg ${
saveStatus.includes("Successfully")
? "bg-green-500/10 border border-green-500/50 text-green-400"
: "bg-red-500/10 border border-red-500/50 text-red-400"
}`}
>
{saveStatus}
</div>
)}
</div>
);
}
================================================
FILE: src/hooks/useApiConnection.js
================================================
import { useState, useEffect } from "react";
import AnythingLLMLogo from "@/media/anything-llm.png";
import BrowserExtension from "@/models/browserExtension";
/**
* Fetches connection information for API key provided
* @returns {{
* status: ("loading"|"notConnected"|"offline"|"connected")
* }}
*/
export default function useApiConnection() {
const [status, setStatus] = useState("loading");
const [logoUrl, setLogoUrl] = useState(AnythingLLMLogo);
useEffect(() => {
checkApiKeyStatus();
fetchLogo();
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "newApiKey") {
checkApiKeyStatus();
fetchLogo();
}
});
}, []);
const checkApiKeyStatus = async () => {
const { apiBase, apiKey } = await chrome.storage.sync.get([
"apiBase",
"apiKey",
]);
if (!apiBase || !apiKey) {
setStatus("notConnected");
return;
}
try {
const { online } = await BrowserExtension.checkOnline(apiBase);
if (!online) {
setStatus("offline");
return;
}
const { response } = await BrowserExtension.checkApiKey(apiBase, apiKey);
if (response.ok) {
setStatus("connected");
chrome.runtime.sendMessage({ action: "connectionUpdated" });
} else {
await chrome.storage.sync.remove(["apiBase", "apiKey"]);
setStatus("notConnected");
chrome.runtime.sendMessage({ action: "connectionUpdated" });
}
} catch (error) {
setStatus("error");
}
};
const fetchLogo = async () => {
const { apiBase } = await chrome.storage.sync.get(["apiBase"]);
if (!apiBase) return;
const { success, logoURL } = await BrowserExtension.fetchLogo(apiBase);
setLogoUrl(success ? logoURL : AnythingLLMLogo);
};
return { status, logoUrl, checkApiKeyStatus };
}
================================================
FILE: src/index.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
================================================
FILE: src/main.jsx
================================================
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
================================================
FILE: src/models/browserExtension.js
================================================
const BrowserExtension = {
checkApiKey: async function (apiBase, apiKey) {
return await fetch(`${apiBase}/browser-extension/check`, {
headers: { Authorization: `Bearer ${apiKey}` },
})
.then((res) => {
if (!res.ok) throw new Error("Bad response to /check");
return res.json();
})
.then((data) => ({ response: { ok: true }, data, error: null }))
.catch((e) => {
console.error(e);
return { response: { ok: false }, data: null, error: e.message };
});
},
checkOnline: async function (apiBase) {
return await fetch(`${apiBase}/ping`)
.then((res) => {
if (!res.ok) throw new Error("Bad response to /ping");
return res.json();
})
.then((data) => ({ online: true, data, error: null }))
.catch((e) => {
return { online: false, data: null, error: e.message };
});
},
fetchLogo: async function (apiBase) {
try {
const response = await fetch(`${apiBase}/system/logo`, {
method: "GET",
cache: "no-cache",
});
if (response.ok && response.status !== 204) {
const blob = await response.blob();
const logoURL = URL.createObjectURL(blob);
return { success: true, error: null, logoURL };
} else {
return { success: false, error: "Logo not available", logoURL: null };
}
} catch (error) {
console.error("Error fetching logo:", error);
return { success: false, error: error.message, logoURL: null };
}
},
disconnect: async function (apiBase, apiKey) {
try {
await fetch(`${apiBase}/browser-extension/disconnect`, {
method: "DELETE",
headers: { Authorization: `Bearer ${apiKey}` },
})
.then((res) => res.json())
.then((data) => {
if (!!data.error)
throw new Error(errorData.error || "Failed to disconnect");
return;
});
return { success: true, error: null };
} catch (error) {
console.error("Disconnect error:", error);
return { success: false, error: error.message };
}
},
};
export default BrowserExtension;
================================================
FILE: src/utils/constants.js
================================================
// Constants for the chrome extension
================================================
FILE: tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {}
},
plugins: []
}
================================================
FILE: vite.config.js
================================================
import { defineConfig } from "vite"
import { fileURLToPath, URL } from "url"
import react from "@vitejs/plugin-react"
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
input: {
main: "index.html"
}
},
outDir: "dist"
},
resolve: {
alias: [
{
find: "@",
replacement: fileURLToPath(new URL("./src", import.meta.url))
},
]
}
})
gitextract_zf654o63/ ├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── package.json ├── postcss.config.js ├── public/ │ ├── background.js │ ├── contentScript.js │ └── manifest.json ├── src/ │ ├── App.jsx │ ├── components/ │ │ └── Config.jsx │ ├── hooks/ │ │ └── useApiConnection.js │ ├── index.css │ ├── main.jsx │ ├── models/ │ │ └── browserExtension.js │ └── utils/ │ └── constants.js ├── tailwind.config.js └── vite.config.js
SYMBOL INDEX (13 symbols across 3 files)
FILE: public/background.js
method create (line 2) | async create(workspaces) {
method remove (line 58) | async remove() {
method checkApiKeyValidity (line 64) | async checkApiKeyValidity() {
method updateWorkspaces (line 94) | async updateWorkspaces() {
method saveToAnythingLLM (line 116) | async saveToAnythingLLM(selectedText, pageTitle, pageUrl) {
method embedToWorkspace (line 145) | async embedToWorkspace(workspaceId, selectedText, pageTitle, pageUrl) {
method saveEntirePageToAnythingLLM (line 172) | async saveEntirePageToAnythingLLM(pageContent, pageTitle, pageUrl) {
method embedEntirePageToWorkspace (line 201) | async embedEntirePageToWorkspace(
method handleResponse (line 233) | async handleResponse(response, action) {
method showNotification (line 257) | showNotification(type, message) {
function getPageContent (line 302) | function getPageContent(tabId) {
FILE: src/components/Config.jsx
function Config (line 4) | function Config({ status, onStatusChange }) {
FILE: src/hooks/useApiConnection.js
function useApiConnection (line 11) | function useApiConnection() {
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
{
"path": ".gitignore",
"chars": 253,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "The MIT License\n\nCopyright (c) Mintplex Labs Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 2391,
"preview": "# AnythingLLM Chrome Extension\n\n<p align=\"center\">\n <img src=\"src/media/anything-llm.png\" alt=\"AnythingLLM Chrome Exten"
},
{
"path": "index.html",
"chars": 313,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "package.json",
"chars": 791,
"preview": "{\n \"name\": \"anything-llm-extension\",\n \"version\": \"1.0.0\",\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"nodemon --wat"
},
{
"path": "postcss.config.js",
"chars": 80,
"preview": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}\n"
},
{
"path": "public/background.js",
"chars": 10575,
"preview": "const ContextMenuModel = {\n async create(workspaces) {\n await chrome.contextMenus.removeAll();\n\n if (workspaces &"
},
{
"path": "public/contentScript.js",
"chars": 415,
"preview": "window.addEventListener(\"message\", (event) => {\n if (event.data.type === \"NEW_BROWSER_EXTENSION_CONNECTION\") {\n chro"
},
{
"path": "public/manifest.json",
"chars": 898,
"preview": "{\n \"manifest_version\": 3,\n \"name\": \"AnythingLLM Browser Companion\",\n \"version\": \"1.0.0\",\n \"description\": \"Bring Anyt"
},
{
"path": "src/App.jsx",
"chars": 752,
"preview": "import React from \"react\";\nimport Config from \"./components/Config\";\nimport useApiConnection from \"./hooks/useApiConnect"
},
{
"path": "src/components/Config.jsx",
"chars": 5536,
"preview": "import React, { useState, useEffect } from \"react\";\nimport BrowserExtension from \"../models/browserExtension\";\n\nexport d"
},
{
"path": "src/hooks/useApiConnection.js",
"chars": 1882,
"preview": "import { useState, useEffect } from \"react\";\nimport AnythingLLMLogo from \"@/media/anything-llm.png\";\nimport BrowserExten"
},
{
"path": "src/index.css",
"chars": 567,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n:root {\n font-family: Inter, system-ui, Avenir, Helvetica, "
},
{
"path": "src/main.jsx",
"chars": 239,
"preview": "import React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport App from \"./App.jsx\";\nimport \"./index.css\";\n\n"
},
{
"path": "src/models/browserExtension.js",
"chars": 2160,
"preview": "const BrowserExtension = {\n checkApiKey: async function (apiBase, apiKey) {\n return await fetch(`${apiBase}/browser-"
},
{
"path": "src/utils/constants.js",
"chars": 38,
"preview": "// Constants for the chrome extension\n"
},
{
"path": "tailwind.config.js",
"chars": 167,
"preview": "/** @type {import('tailwindcss').Config} */\nexport default {\n content: [\"./index.html\", \"./src/**/*.{js,ts,jsx,tsx}\"],\n"
},
{
"path": "vite.config.js",
"chars": 429,
"preview": "import { defineConfig } from \"vite\"\nimport { fileURLToPath, URL } from \"url\"\nimport react from \"@vitejs/plugin-react\"\n\ne"
}
]
About this extraction
This page contains the full source code of the Mintplex-Labs/anythingllm-extension GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (27.9 KB), approximately 7.4k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.