Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Community hub integration #2555

Merged
merged 45 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
0c617a2
wip hub connection page fe + backend
shatfield4 Oct 29, 2024
3481afb
Merge branch 'master' into 2545-feat-community-hub-integration
shatfield4 Oct 29, 2024
795c87d
lint
shatfield4 Oct 29, 2024
de7866c
implement backend for local hub items + placeholder endpoints to fetc…
shatfield4 Oct 29, 2024
8c56b00
Merge branch 'master' into 2545-feat-community-hub-integration
shatfield4 Oct 29, 2024
1db99ca
fix hebrew translations
shatfield4 Oct 29, 2024
a7757de
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 7, 2024
77b1615
revamp community integration flow
timothycarambat Nov 8, 2024
890fb29
change sidebar
timothycarambat Nov 8, 2024
3f04c71
Auto import if id in URL param
timothycarambat Nov 9, 2024
679c5d0
get user's items + team items from hub + ui improvements to hub settings
shatfield4 Nov 9, 2024
5256b3b
lint
shatfield4 Nov 9, 2024
ac911c0
Merge branch '2545-feat-community-hub-integration' of github.com:Mint…
shatfield4 Nov 9, 2024
e7f9fce
fix merge conflict
shatfield4 Nov 9, 2024
cfce320
refresh hook for community items
timothycarambat Nov 11, 2024
58e73e8
add fallback for user items
shatfield4 Nov 11, 2024
8be4f13
Merge branch '2545-feat-community-hub-integration' of github.com:Mint…
shatfield4 Nov 11, 2024
d5324fc
Disable bundle items by default on all instances
timothycarambat Nov 11, 2024
e2827b2
Merge branch '2545-feat-community-hub-integration' of github.com:Mint…
timothycarambat Nov 11, 2024
78740e4
remove translations (will complete later)
timothycarambat Nov 11, 2024
f4c4b92
loading skeleton
timothycarambat Nov 11, 2024
7e66433
Make community hub endpoints admin only
timothycarambat Nov 11, 2024
3292c71
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 11, 2024
9937089
improve middleware and import flow
timothycarambat Nov 13, 2024
6d55352
Merge branch '2545-feat-community-hub-integration' of github.com:Mint…
timothycarambat Nov 13, 2024
db49983
Merge branch 'master' into 2545-feat-community-hub-integration
shatfield4 Nov 19, 2024
1c0f73f
community hub ui updates
shatfield4 Nov 19, 2024
c3fca12
Merge branch 'master' into 2545-feat-community-hub-integration
shatfield4 Nov 19, 2024
6c4a954
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 21, 2024
7abe3d8
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 21, 2024
539abbb
Adjust importing process
timothycarambat Nov 21, 2024
0ca2ab5
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 21, 2024
a757d99
community hub to dev
timothycarambat Nov 21, 2024
c3a28b6
Merge branch 'master' of github.com:Mintplex-Labs/anything-llm into 2…
timothycarambat Nov 24, 2024
e385037
Add webscraper preload into imported plugins
timothycarambat Nov 25, 2024
bdc3477
add runtime property to plugins
timothycarambat Nov 25, 2024
3fe10db
Fix button status on imported skill change
timothycarambat Nov 25, 2024
b64d573
update documentaion paths
timothycarambat Nov 26, 2024
1e8f399
remove unused import
timothycarambat Nov 26, 2024
2639326
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 26, 2024
1a575a4
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 26, 2024
0f81c50
Merge branch 'master' into 2545-feat-community-hub-integration
timothycarambat Nov 26, 2024
07da923
linting
timothycarambat Nov 26, 2024
fa6cbb2
review loading state
timothycarambat Nov 26, 2024
dcbaee7
Merge branch '2545-feat-community-hub-integration' of github.com:Mint…
timothycarambat Nov 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: ['2670-feat-can-the-font-size-of-the-chat-input-box-be-increased'] # put your current branch to create a build. Core team only.
branches: ['2545-feat-community-hub-integration'] # put your current branch to create a build. Core team only.
paths-ignore:
- '**.md'
- 'cloud-deployments/*'
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ const LiveDocumentSyncManage = lazy(
);
const FineTuningWalkthrough = lazy(() => import("@/pages/FineTuning"));

const CommunityHubTrending = lazy(
() => import("@/pages/GeneralSettings/CommunityHub/Trending")
);
const CommunityHubAuthentication = lazy(
() => import("@/pages/GeneralSettings/CommunityHub/Authentication")
);
const CommunityHubImportItem = lazy(
() => import("@/pages/GeneralSettings/CommunityHub/ImportItem")
);

export default function App() {
return (
<ThemeProvider>
Expand Down Expand Up @@ -207,6 +217,21 @@ export default function App() {
path="/fine-tuning"
element={<AdminRoute Component={FineTuningWalkthrough} />}
/>

<Route
path="/settings/community-hub/trending"
element={<AdminRoute Component={CommunityHubTrending} />}
/>
<Route
path="/settings/community-hub/authentication"
element={
<AdminRoute Component={CommunityHubAuthentication} />
}
/>
<Route
path="/settings/community-hub/import-item"
element={<AdminRoute Component={CommunityHubImportItem} />}
/>
</Routes>
<ToastContainer />
</I18nextProvider>
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/components/SettingsSidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
PencilSimpleLine,
Nut,
Toolbox,
Globe,
} from "@phosphor-icons/react";
import useUser from "@/hooks/useUser";
import { isMobile } from "react-device-detect";
Expand Down Expand Up @@ -291,6 +292,30 @@ const SidebarOptions = ({ user = null, t }) => (
flex={true}
roles={["admin"]}
/>
<Option
btnText="Community Hub"
icon={<Globe className="h-5 w-5 flex-shrink-0" />}
childOptions={[
{
btnText: "Explore Trending",
href: paths.communityHub.trending(),
flex: true,
roles: ["admin"],
},
{
btnText: "Your Account",
href: paths.communityHub.authentication(),
flex: true,
roles: ["admin"],
},
{
btnText: "Import Item",
href: paths.communityHub.importItem(),
flex: true,
roles: ["admin"],
},
]}
/>
<Option
btnText={t("settings.customization")}
icon={<PencilSimpleLine className="h-5 w-5 flex-shrink-0" />}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/WorkspaceChat/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function copyCodeSnippet(uuid) {
}

// Listens and hunts for all data-code-snippet clicks.
function setEventDelegatorForCodeSnippets() {
export function setEventDelegatorForCodeSnippets() {
document?.addEventListener("click", function (e) {
const target = e.target.closest("[data-code-snippet]");
const uuidCode = target?.dataset?.code;
Expand Down
158 changes: 158 additions & 0 deletions frontend/src/models/communityHub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { API_BASE } from "@/utils/constants";
import { baseHeaders } from "@/utils/request";

const CommunityHub = {
/**
* Get an item from the community hub by its import ID.
* @param {string} importId - The import ID of the item.
* @returns {Promise<{error: string | null, item: object | null}>}
*/
getItemFromImportId: async (importId) => {
return await fetch(`${API_BASE}/community-hub/item`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({ importId }),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return {
error: e.message,
item: null,
};
});
},

/**
* Apply an item to the AnythingLLM instance. Used for simple items like slash commands and system prompts.
* @param {string} importId - The import ID of the item.
* @param {object} options - Additional options for applying the item for whatever the item type requires.
* @returns {Promise<{success: boolean, error: string | null}>}
*/
applyItem: async (importId, options = {}) => {
return await fetch(`${API_BASE}/community-hub/apply`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({ importId, options }),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return {
success: false,
error: e.message,
};
});
},

/**
* Import a bundle item from the community hub.
* @param {string} importId - The import ID of the item.
* @returns {Promise<{error: string | null, item: object | null}>}
*/
importBundleItem: async (importId) => {
return await fetch(`${API_BASE}/community-hub/import`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify({ importId }),
})
.then(async (res) => {
const response = await res.json();
if (!res.ok) throw new Error(response?.error ?? res.statusText);
return response;
})
.catch((e) => {
return {
error: e.message,
item: null,
};
});
},

/**
* Update the hub settings (API key, etc.)
* @param {Object} data - The data to update.
* @returns {Promise<{success: boolean, error: string | null}>}
*/
updateSettings: async (data) => {
return await fetch(`${API_BASE}/community-hub/settings`, {
method: "POST",
headers: baseHeaders(),
body: JSON.stringify(data),
})
.then(async (res) => {
const response = await res.json();
if (!res.ok)
throw new Error(response.error || "Failed to update settings");
return { success: true, error: null };
})
.catch((e) => ({
success: false,
error: e.message,
}));
},

/**
* Get the hub settings (API key, etc.)
* @returns {Promise<{connectionKey: string | null, error: string | null}>}
*/
getSettings: async () => {
return await fetch(`${API_BASE}/community-hub/settings`, {
method: "GET",
headers: baseHeaders(),
})
.then(async (res) => {
const response = await res.json();
if (!res.ok)
throw new Error(response.error || "Failed to fetch settings");
return { connectionKey: response.connectionKey, error: null };
})
.catch((e) => ({
connectionKey: null,
error: e.message,
}));
},

/**
* Fetch the explore items from the community hub that are publicly available.
* @returns {Promise<{agentSkills: {items: [], hasMore: boolean, totalCount: number}, systemPrompts: {items: [], hasMore: boolean, totalCount: number}, slashCommands: {items: [], hasMore: boolean, totalCount: number}}>}
*/
fetchExploreItems: async () => {
return await fetch(`${API_BASE}/community-hub/explore`, {
method: "GET",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return {
success: false,
error: e.message,
result: null,
};
});
},

/**
* Fetch the user items from the community hub.
* @returns {Promise<{success: boolean, error: string | null, createdByMe: object, teamItems: object[]}>}
*/
fetchUserItems: async () => {
return await fetch(`${API_BASE}/community-hub/items`, {
method: "GET",
headers: baseHeaders(),
})
.then((res) => res.json())
.catch((e) => {
console.error(e);
return {
success: false,
error: e.message,
createdByMe: {},
teamItems: [],
};
});
},
};

export default CommunityHub;
14 changes: 14 additions & 0 deletions frontend/src/models/experimental/agentPlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ const AgentPlugins = {
return false;
});
},
deletePlugin: async function (hubId) {
return await fetch(`${API_BASE}/experimental/agent-plugins/${hubId}`, {
method: "DELETE",
headers: baseHeaders(),
})
.then((res) => {
if (!res.ok) throw new Error("Could not delete agent plugin config.");
return true;
})
.catch((e) => {
console.error(e);
return false;
});
},
};

export default AgentPlugins;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import System from "@/models/system";
import showToast from "@/utils/toast";
import { Plug } from "@phosphor-icons/react";
import { useEffect, useState } from "react";
import { Gear, Plug } from "@phosphor-icons/react";
import { useEffect, useState, useRef } from "react";
import { sentenceCase } from "text-case";

/**
Expand Down Expand Up @@ -55,6 +55,11 @@ export default function ImportedSkillConfig({
prev.map((s) => (s.hubId === config.hubId ? updatedConfig : s))
);
setConfig(updatedConfig);
showToast(
`Skill ${updatedConfig.active ? "activated" : "deactivated"}.`,
"success",
{ clear: true }
);
}

async function handleSubmit(e) {
Expand Down Expand Up @@ -91,6 +96,7 @@ export default function ImportedSkillConfig({
)
);
showToast("Skill config updated successfully.", "success");
setHasChanges(false);
}

useEffect(() => {
Expand Down Expand Up @@ -119,6 +125,10 @@ export default function ImportedSkillConfig({
<div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
<span className="ml-3 text-sm font-medium"></span>
</label>
<ManageSkillMenu
config={config}
setImportedSkills={setImportedSkills}
/>
</div>
<p className="text-white text-opacity-60 text-xs font-medium py-1.5">
{config.description} by{" "}
Expand Down Expand Up @@ -178,3 +188,64 @@ export default function ImportedSkillConfig({
</>
);
}

function ManageSkillMenu({ config, setImportedSkills }) {
const [open, setOpen] = useState(false);
const menuRef = useRef(null);

async function deleteSkill() {
if (
!window.confirm(
"Are you sure you want to delete this skill? This action cannot be undone."
)
)
return;
const success = await System.experimentalFeatures.agentPlugins.deletePlugin(
config.hubId
);
if (success) {
setImportedSkills((prev) => prev.filter((s) => s.hubId !== config.hubId));
showToast("Skill deleted successfully.", "success");
setOpen(false);
} else {
showToast("Failed to delete skill.", "error");
}
}

useEffect(() => {
const handleClickOutside = (event) => {
if (menuRef.current && !menuRef.current.contains(event.target)) {
setOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

if (!config.hubId) return null;
return (
<div className="relative" ref={menuRef}>
<button
type="button"
onClick={() => setOpen(!open)}
className={`border-none transition duration-200 hover:rotate-90 outline-none ring-none ${open ? "rotate-90" : ""}`}
>
<Gear size={24} weight="bold" />
</button>
{open && (
<div className="absolute w-[100px] -top-1 left-7 mt-1 border-[1.5px] border-white/40 rounded-lg bg-theme-action-menu-bg flex flex-col shadow-[0_4px_14px_rgba(0,0,0,0.25)] text-white z-99 md:z-10">
<button
type="button"
onClick={deleteSkill}
className="border-none flex items-center rounded-lg gap-x-2 hover:bg-theme-action-menu-item-hover py-1.5 px-2 transition-colors duration-200 w-full text-left"
>
<span className="text-sm">Delete Skill</span>
</button>
</div>
)}
</div>
);
}
Loading