Skip to content

Commit

Permalink
AnythingLLM Chrome Extension (#2066)
Browse files Browse the repository at this point in the history
* initial commit for chrome extension

* wip browser extension backend

* wip frontend browser extension settings

* fix typo for browserExtension route

* implement verification codes + frontend panel for browser extension keys

* reorganize + state management for all connection states

* implement embed to workspace

* add send page to anythingllm extension option + refactor

* refactor connection string auth + update context menus + organize background.js into models

* popup extension from main app and save if successful

* fix hebrew translation misspelling

* fetch custom logo inside chrome extension

* delete api keys on disconnect of extension

* use correct apiUrl constant in frontend + remove unneeded comments

* remove upload-link endpoint and send inner text html to raw text collector endpoint

* update readme

* fix readme link

* fix readme typo

* update readme

* handle deletion of browser keys with key id and DELETE endpoint

* move event string to constant

* remove tablename and writable fields from BrowserExtensionApiKey backend model

* add border-none to all buttons and inputs for desktop compatibility

* patch prisma injections

* update delete endpoints to delete keys by id

* remove unused prop

* add button to attempt browser extension connection + remove max active keys

* wip multi user mode support

* multi user mode support

* clean up backend + show created by in frotend browser extension page

* show multi user warning message on key creation + hide context menus when no workspaces

* show browser extension options to managers

* small backend changes and refactors

* extension cleanup

* rename submodule

* extension updates & docs

* dev docker build

---------

Co-authored-by: shatfield4 <seanhatfield5@gmail.com>
  • Loading branch information
timothycarambat and shatfield4 authored Aug 27, 2024
1 parent fc6d735 commit 29df483
Show file tree
Hide file tree
Showing 30 changed files with 918 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dev-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ concurrency:

on:
push:
branches: ['encrypt-jwt-value'] # put your current branch to create a build. Core team only.
branches: ['chrome-extension'] # put your current branch to create a build. Core team only.
paths-ignore:
- '**.md'
- 'cloud-deployments/*'
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
branch = main
path = embed
url = git@github.com:Mintplex-Labs/anythingllm-embed.git
[submodule "browser-extension"]
path = browser-extension
url = git@github.com:Mintplex-Labs/anythingllm-extension.git
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ This monorepo consists of three main sections:
- `server`: A NodeJS express server to handle all the interactions and do all the vectorDB management and LLM interactions.
- `collector`: NodeJS express server that process and parses documents from the UI.
- `docker`: Docker instructions and build process + information for building from source.
- `embed`: Submodule specifically for generation & creation of the [web embed widget](https://github.com/Mintplex-Labs/anythingllm-embed).
- `embed`: Submodule for generation & creation of the [web embed widget](https://github.com/Mintplex-Labs/anythingllm-embed).
- `browser-extension`: Submodule for the [chrome browser extension](https://github.com/Mintplex-Labs/anythingllm-extension).

## 🛳 Self Hosting

Expand Down
1 change: 1 addition & 0 deletions browser-extension
Submodule browser-extension added at d9b28c
1 change: 0 additions & 1 deletion embed
Submodule embed deleted from 22a084
7 changes: 7 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const GeneralVectorDatabase = lazy(
() => import("@/pages/GeneralSettings/VectorDatabase")
);
const GeneralSecurity = lazy(() => import("@/pages/GeneralSettings/Security"));
const GeneralBrowserExtension = lazy(
() => import("@/pages/GeneralSettings/BrowserExtensionApiKey")
);
const WorkspaceSettings = lazy(() => import("@/pages/WorkspaceSettings"));
const EmbedConfigSetup = lazy(
() => import("@/pages/GeneralSettings/EmbedConfigs")
Expand Down Expand Up @@ -157,6 +160,10 @@ export default function App() {
path="/settings/api-keys"
element={<AdminRoute Component={GeneralApiKeys} />}
/>
<Route
path="/settings/browser-extension"
element={<ManagerRoute Component={GeneralBrowserExtension} />}
/>
<Route
path="/settings/workspace-chats"
element={<ManagerRoute Component={GeneralChats} />}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/SettingsSidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,12 @@ const SidebarOptions = ({ user = null, t }) => (
flex: true,
roles: ["admin"],
},
{
btnText: t("settings.browser-extension"),
href: paths.settings.browserExtension(),
flex: true,
roles: ["admin", "manager"],
},
]}
/>
<Option
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/de/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const TRANSLATIONS = {
tools: "Werkzeuge",
"experimental-features": "Experimentelle Funktionen",
contact: "Support kontaktieren",
"browser-extension": "Browser-Erweiterung",
},

login: {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const TRANSLATIONS = {
tools: "Tools",
"experimental-features": "Experimental Features",
contact: "Contact Support",
"browser-extension": "Browser Extension",
},

// Page Definitions
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/es/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const TRANSLATIONS = {
tools: "Herramientas",
"experimental-features": "Funciones Experimentales",
contact: "Contactar Soporte",
"browser-extension": "Extensión del navegador",
},

login: {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/fr/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const TRANSLATIONS = {
tools: "Outils",
"experimental-features": "Fonctionnalités Expérimentales",
contact: "Contacter le Support",
"browser-extension": "Extension de navigateur",
},

// Page Definitions
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/he/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const TRANSLATIONS = {
tools: "כלים",
"experimental-features": "תכונות ניסיוניות",
contact: "צור קשר עם התמיכה",
"browser-extension": "תוסף דפדפן",
},

// Page Definitions
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/it/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const TRANSLATIONS = {
tools: "Strumenti",
"experimental-features": "Caratteristiche sperimentali",
contact: "Contatta il Supporto",
"browser-extension": "Estensione del browser",
},

// Page Definitions
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/ko/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const TRANSLATIONS = {
tools: "도구",
"experimental-features": "실험적 기능",
contact: "지원팀 연락",
"browser-extension": "브라우저 확장 프로그램",
},

// Page Definitions
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/pt_BR/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const TRANSLATIONS = {
tools: "Ferramentas",
"experimental-features": "Recursos Experimentais",
contact: "Contato com Suporte",
"browser-extension": "Extensão do navegador",
},

// Page Definitions
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/ru/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const TRANSLATIONS = {
tools: "Инструменты",
"experimental-features": "Экспериментальные функции",
contact: "联系支持Связаться с Поддержкой",
"browser-extension": "Расширение браузера",
},

login: {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locales/zh/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const TRANSLATIONS = {
tools: "工具",
"experimental-features": "实验功能",
contact: "联系支持",
"browser-extension": "浏览器扩展",
},

// Page Definitions
Expand Down
42 changes: 42 additions & 0 deletions frontend/src/models/browserExtensionApiKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { API_BASE } from "@/utils/constants";
import { baseHeaders } from "@/utils/request";

const BrowserExtensionApiKey = {
getAll: async () => {
return await fetch(`${API_BASE}/browser-extension/api-keys`, {
method: "GET",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { success: false, error: e.message, apiKeys: [] };
});
},

generateKey: async () => {
return await fetch(`${API_BASE}/browser-extension/api-keys/new`, {
method: "POST",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { success: false, error: e.message };
});
},

revoke: async (id) => {
return await fetch(`${API_BASE}/browser-extension/api-keys/${id}`, {
method: "DELETE",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return { success: false, error: e.message };
});
},
};

export default BrowserExtensionApiKey;
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useRef, useState } from "react";
import BrowserExtensionApiKey from "@/models/browserExtensionApiKey";
import showToast from "@/utils/toast";
import { Trash, Copy, Check, Plug } from "@phosphor-icons/react";
import { POPUP_BROWSER_EXTENSION_EVENT } from "@/utils/constants";
import { Tooltip } from "react-tooltip";

export default function BrowserExtensionApiKeyRow({
apiKey,
removeApiKey,
connectionString,
isMultiUser,
}) {
const rowRef = useRef(null);
const [copied, setCopied] = useState(false);

const handleRevoke = async () => {
if (
!window.confirm(
`Are you sure you want to revoke this browser extension API key?\nAfter you do this it will no longer be useable.\n\nThis action is irreversible.`
)
)
return false;

const result = await BrowserExtensionApiKey.revoke(apiKey.id);
if (result.success) {
removeApiKey(apiKey.id);
showToast("Browser Extension API Key permanently revoked", "info", {
clear: true,
});
} else {
showToast("Failed to revoke API Key", "error", {
clear: true,
});
}
};

const handleCopy = () => {
navigator.clipboard.writeText(connectionString);
showToast("Connection string copied to clipboard", "success", {
clear: true,
});
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

const handleConnect = () => {
// Sending a message to Chrome extension to pop up the extension window
// This will open the extension window and attempt to connect with the API key
window.postMessage(
{ type: POPUP_BROWSER_EXTENSION_EVENT, apiKey: connectionString },
"*"
);
showToast("Attempting to connect to browser extension...", "info", {
clear: true,
});
};

return (
<tr
ref={rowRef}
className="bg-transparent text-white text-opacity-80 text-sm font-medium"
>
<td scope="row" className="px-6 py-4 whitespace-nowrap flex items-center">
<span className="mr-2 font-mono">{connectionString}</span>
<div className="flex items-center space-x-2">
<button
onClick={handleCopy}
data-tooltip-id={`copy-connection-text-${apiKey.id}`}
data-tooltip-content="Copy connection string"
className="text-white hover:text-white/80 transition-colors duration-200 p-1 rounded"
>
{copied ? (
<Check className="h-5 w-5 text-green-500" />
) : (
<Copy className="h-5 w-5" />
)}
<Tooltip
id={`copy-connection-text-${apiKey.id}`}
place="bottom"
delayShow={300}
className="allm-tooltip !allm-text-xs"
/>
</button>

<button
onClick={handleConnect}
data-tooltip-id={`auto-connection-${apiKey.id}`}
data-tooltip-content="Automatically connect to extension"
className="text-white hover:text-white/80 transition-colors duration-200 p-1 rounded"
>
<Plug className="h-5 w-5" />
<Tooltip
id={`auto-connection-${apiKey.id}`}
place="bottom"
delayShow={300}
className="allm-tooltip !allm-text-xs"
/>
</button>
</div>
</td>
{isMultiUser && (
<td className="px-6 py-4">
{apiKey.user ? apiKey.user.username : "N/A"}
</td>
)}
<td className="px-6 py-4">
{new Date(apiKey.createdAt).toLocaleString()}
</td>
<td className="px-6 py-4">
<button
onClick={handleRevoke}
className="font-medium px-2 py-1 rounded-lg hover:bg-sidebar-gradient text-white hover:text-white/80 hover:bg-opacity-20"
>
<Trash className="h-5 w-5" />
</button>
</td>
</tr>
);
}
Loading

0 comments on commit 29df483

Please sign in to comment.