Publish slash commands to hub (#4019)

* implement ui for publish system prompt to hub

* rework ui + add backend to upload to hub

* add success modal view + publish menu item + translations

* normalize translations

* refactor PublishEntityModal + add hook
for hub auth

* normalize translations

* fix ui for success screen

* refactor, auth checks, UI/UX, and naming conventions to be more clear

* move components to CommunityHub folder + small ui tweak

* wip publish agent flows to community hub

* rework translations/implement uploading agent flows

* normalize/restructure translations

* rename component/add jsdoc for consistency

* fix en translation

* remove comments/duplicate function from merge conf

* update styles of publish button in flow builder

* resolve ssr icon issue

* implement uploading slash commands to hub

* normalize translations

* show command, non editable

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2025-06-24 16:19:50 -07:00 committed by GitHub
parent 2d210aa90a
commit 91e498229c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 945 additions and 82 deletions

View File

@ -0,0 +1,256 @@
import { useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import CommunityHub from "@/models/communityHub";
import showToast from "@/utils/toast";
import paths from "@/utils/paths";
import { X } from "@phosphor-icons/react";
export default function SlashCommands({ entity }) {
const { t } = useTranslation();
const formRef = useRef(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [tags, setTags] = useState([]);
const [tagInput, setTagInput] = useState("");
const [visibility, setVisibility] = useState("public");
const [isSuccess, setIsSuccess] = useState(false);
const [itemId, setItemId] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmitting(true);
try {
const form = new FormData(formRef.current);
const data = {
name: form.get("name"),
description: form.get("description"),
command: entity.command,
prompt: form.get("prompt"),
tags: tags,
visibility: visibility,
};
const { success, error, itemId } =
await CommunityHub.createSlashCommand(data);
if (!success) throw new Error(error);
setItemId(itemId);
setIsSuccess(true);
} catch (error) {
console.error("Failed to publish slash command:", error);
showToast(`Failed to publish slash command: ${error.message}`, "error", {
clear: true,
});
} finally {
setIsSubmitting(false);
}
};
const handleKeyDown = (e) => {
if (e.key === "Enter" || e.key === ",") {
e.preventDefault();
const value = tagInput.trim();
if (value.length > 20) return;
if (value && !tags.includes(value)) {
setTags((prevTags) => [...prevTags, value].slice(0, 5)); // Limit to 5 tags
setTagInput("");
}
}
};
const removeTag = (tagToRemove) => {
setTags(tags.filter((tag) => tag !== tagToRemove));
};
if (isSuccess) {
return (
<div className="p-6 -mt-12 w-[400px]">
<div className="flex flex-col items-center justify-center gap-y-2">
<h3 className="text-lg font-semibold text-theme-text-primary">
{t("community_hub.publish.slash_command.success_title")}
</h3>
<p className="text-lg text-theme-text-primary text-center max-w-2xl">
{t("community_hub.publish.slash_command.success_description")}
</p>
<p className="text-theme-text-secondary text-center text-sm">
{t("community_hub.publish.slash_command.success_thank_you")}
</p>
<a
href={paths.communityHub.viewItem("slash-command", itemId)}
target="_blank"
rel="noreferrer"
className="w-[265px] bg-theme-bg-secondary hover:bg-theme-sidebar-item-hover text-theme-text-primary py-2 px-4 rounded-lg transition-colors mt-4 text-sm font-semibold text-center"
>
{t("community_hub.publish.slash_command.view_on_hub")}
</a>
</div>
</div>
);
}
return (
<>
<div className="w-full flex gap-x-2 items-center mb-3 -mt-8">
<h3 className="text-xl font-semibold text-theme-text-primary px-6 py-3 flex items-center gap-x-2">
{t("community_hub.publish.slash_command.modal_title")}
<code className="bg-theme-bg-secondary rounded-lg px-1 text-theme-text-secondary text-lg font-mono">
{entity.command}
</code>
</h3>
</div>
<form ref={formRef} className="flex" onSubmit={handleSubmit}>
<div className="w-1/2 p-6 pt-0 space-y-4">
<div>
<label className="block text-sm font-semibold text-theme-text-primary mb-1">
{t("community_hub.publish.slash_command.name_label")}
</label>
<div className="text-xs text-theme-text-secondary mb-2">
{t("community_hub.publish.slash_command.name_description")}
</div>
<input
type="text"
name="name"
required
minLength={3}
maxLength={300}
defaultValue={entity.name}
placeholder={t(
"community_hub.publish.slash_command.name_placeholder"
)}
className="w-full bg-theme-bg-secondary rounded-lg p-2 text-theme-text-primary text-sm focus:outline-primary-button active:outline-primary-button outline-none placeholder:text-theme-text-placeholder"
/>
</div>
<div>
<label className="block text-sm font-semibold text-theme-text-primary mb-1">
{t("community_hub.publish.slash_command.description_label")}
</label>
<div className="text-xs text-white/60 mb-2">
{t("community_hub.publish.slash_command.description_description")}
</div>
<textarea
name="description"
required
minLength={10}
maxLength={1000}
defaultValue={entity.description}
placeholder={t(
"community_hub.publish.slash_command.description_placeholder"
)}
className="w-full bg-theme-bg-secondary rounded-lg p-2 text-white text-sm focus:outline-primary-button active:outline-primary-button outline-none min-h-[80px] placeholder:text-theme-text-placeholder"
/>
</div>
<div>
<label className="block text-sm font-semibold text-white mb-1">
{t("community_hub.publish.slash_command.tags_label")}
</label>
<div className="text-xs text-white/60 mb-2">
{t("community_hub.publish.slash_command.tags_description")}
</div>
<div className="flex flex-wrap gap-2 p-2 bg-theme-bg-secondary rounded-lg min-h-[42px]">
{tags.map((tag, index) => (
<span
key={index}
className="flex items-center gap-1 px-2 py-1 text-sm text-theme-text-primary bg-white/10 light:bg-black/10 rounded-md"
>
{tag}
<button
type="button"
onClick={() => removeTag(tag)}
className="border-none text-theme-text-primary hover:text-theme-text-secondary cursor-pointer"
>
<X size={14} />
</button>
</span>
))}
<input
type="text"
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={t(
"community_hub.publish.slash_command.tags_placeholder"
)}
className="flex-1 min-w-[200px] border-none text-sm bg-transparent text-theme-text-primary placeholder:text-theme-text-placeholder p-0 h-[24px] focus:outline-none"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-white mb-1">
{t("community_hub.publish.slash_command.visibility_label")}
</label>
<div className="text-xs text-white/60 mb-2">
{visibility === "public"
? t("community_hub.publish.slash_command.public_description")
: t("community_hub.publish.slash_command.private_description")}
</div>
<div className="w-fit h-[42px] bg-theme-bg-secondary rounded-lg p-0.5">
<div className="flex items-center" role="group">
<input
type="radio"
id="public"
name="visibility"
value="public"
className="peer/public hidden"
defaultChecked
onChange={(e) => setVisibility(e.target.value)}
/>
<input
type="radio"
id="private"
name="visibility"
value="private"
className="peer/private hidden"
onChange={(e) => setVisibility(e.target.value)}
/>
<label
htmlFor="public"
className="h-[36px] px-4 rounded-lg text-sm font-medium transition-all duration-200 cursor-pointer text-theme-text-primary hover:text-theme-text-secondary peer-checked/public:bg-theme-sidebar-item-hover peer-checked/public:text-theme-primary-button flex items-center justify-center"
>
Public
</label>
<label
htmlFor="private"
className="h-[36px] px-4 rounded-lg text-sm font-medium transition-all duration-200 cursor-pointer text-theme-text-primary hover:text-theme-text-secondary peer-checked/private:bg-theme-sidebar-item-hover peer-checked/private:text-theme-primary-button flex items-center justify-center"
>
Private
</label>
</div>
</div>
</div>
</div>
<div className="w-1/2 p-6 pt-0 space-y-4">
<div>
<label className="block text-sm font-semibold text-white mb-1">
{t("community_hub.publish.slash_command.prompt_label")}
</label>
<div className="text-xs text-white/60 mb-2">
{t("community_hub.publish.slash_command.prompt_description")}
</div>
<textarea
name="prompt"
required
minLength={10}
defaultValue={entity.prompt}
placeholder={t(
"community_hub.publish.slash_command.prompt_placeholder"
)}
className="w-full bg-theme-bg-secondary rounded-lg p-2 text-white text-sm focus:outline-primary-button active:outline-primary-button outline-none min-h-[300px] placeholder:text-theme-text-placeholder"
/>
</div>
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-cta-button hover:opacity-80 text-theme-text-primary font-medium py-2 px-4 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting
? t("community_hub.publish.slash_command.submitting")
: t("community_hub.publish.slash_command.publish_button")}
</button>
</div>
</form>
</>
);
}

View File

@ -4,6 +4,7 @@ import UnauthenticatedHubModal from "@/components/CommunityHub/UnauthenticatedHu
import SystemPrompts from "./SystemPrompts";
import ModalWrapper from "@/components/ModalWrapper";
import AgentFlows from "./AgentFlows";
import SlashCommands from "./SlashCommands";
export default function PublishEntityModal({
show,
@ -22,6 +23,8 @@ export default function PublishEntityModal({
return <SystemPrompts entity={entity} />;
case "agent-flow":
return <AgentFlows entity={entity} />;
case "slash-command":
return <SlashCommands entity={entity} />;
default:
return null;
}

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";
import { useIsAgentSessionActive } from "@/utils/chat/agent";
import AddPresetModal from "./AddPresetModal";
import EditPresetModal from "./EditPresetModal";
@ -8,6 +8,7 @@ import { DotsThree, Plus } from "@phosphor-icons/react";
import showToast from "@/utils/toast";
import { useSearchParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import PublishEntityModal from "@/components/CommunityHub/PublishEntityModal";
export const CMD_REGEX = new RegExp(/[^a-zA-Z0-9_-]/g);
export default function SlashPresets({ setShowing, sendCommand, promptRef }) {
@ -23,8 +24,14 @@ export default function SlashPresets({ setShowing, sendCommand, promptRef }) {
openModal: openEditModal,
closeModal: closeEditModal,
} = useModal();
const {
isOpen: isPublishModalOpen,
openModal: openPublishModal,
closeModal: closePublishModal,
} = useModal();
const [presets, setPresets] = useState([]);
const [selectedPreset, setSelectedPreset] = useState(null);
const [presetToPublish, setPresetToPublish] = useState(null);
const [searchParams] = useSearchParams();
useEffect(() => {
@ -94,36 +101,30 @@ export default function SlashPresets({ setShowing, sendCommand, promptRef }) {
setSelectedPreset(null);
};
const handlePublishPreset = (preset) => {
setPresetToPublish({
name: preset.command.slice(1),
description: preset.description,
command: preset.command,
prompt: preset.prompt,
});
openPublishModal();
};
return (
<>
{presets.map((preset) => (
<button
<PresetItem
key={preset.id}
onClick={() => {
preset={preset}
onUse={() => {
setShowing(false);
sendCommand(`${preset.command} `, false);
promptRef?.current?.focus();
}}
className="border-none w-full hover:cursor-pointer hover:bg-theme-action-menu-item-hover px-2 py-2 rounded-xl flex flex-row justify-start"
>
<div className="w-full flex-col text-left flex pointer-events-none">
<div className="text-theme-text-primary text-sm font-bold">
{preset.command}
</div>
<div className="text-theme-text-secondary text-sm">
{preset.description}
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
handleEditPreset(preset);
}}
className="border-none text-theme-text-primary text-sm p-1 hover:cursor-pointer hover:bg-theme-action-menu-item-hover rounded-full mt-1"
>
<DotsThree size={24} weight="bold" />
</button>
</button>
onEdit={handleEditPreset}
onPublish={handlePublishPreset}
/>
))}
<button
onClick={openAddModal}
@ -150,6 +151,94 @@ export default function SlashPresets({ setShowing, sendCommand, promptRef }) {
preset={selectedPreset}
/>
)}
<PublishEntityModal
show={isPublishModalOpen}
onClose={closePublishModal}
entityType="slash-command"
entity={presetToPublish}
/>
</>
);
}
function PresetItem({ preset, onUse, onEdit, onPublish }) {
const [showMenu, setShowMenu] = useState(false);
const menuRef = useRef(null);
const menuButtonRef = useRef(null);
useEffect(() => {
const handleClickOutside = (event) => {
if (
showMenu &&
menuRef.current &&
!menuRef.current.contains(event.target) &&
menuButtonRef.current &&
!menuButtonRef.current.contains(event.target)
) {
setShowMenu(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [showMenu]);
return (
<button
onClick={onUse}
className="border-none w-full hover:cursor-pointer hover:bg-theme-action-menu-item-hover px-2 py-2 rounded-xl flex flex-row justify-start items-center relative"
>
<div className="flex-col text-left flex pointer-events-none flex-1 min-w-0">
<div className="text-theme-text-primary text-sm font-bold truncate">
{preset.command}
</div>
<div className="text-theme-text-secondary text-sm truncate">
{preset.description}
</div>
</div>
<button
ref={menuButtonRef}
type="button"
tabIndex={-1}
onClick={(e) => {
e.stopPropagation();
setShowMenu(!showMenu);
}}
className="border-none text-theme-text-primary text-sm p-1 hover:cursor-pointer hover:bg-theme-action-menu-item-hover rounded-full ml-2 flex-shrink-0 z-20"
aria-label="More actions"
>
<DotsThree size={24} weight="bold" />
</button>
{showMenu && (
<div
ref={menuRef}
className="absolute right-0 top-10 bg-theme-bg-popup-menu rounded-lg z-50 min-w-[160px] shadow-lg border border-theme-modal-border flex flex-col"
>
<button
type="button"
className="px-[10px] py-[6px] text-sm text-white hover:bg-theme-sidebar-item-hover rounded-t-lg cursor-pointer border-none w-full text-left whitespace-nowrap"
onClick={(e) => {
e.stopPropagation();
setShowMenu(false);
onEdit(preset);
}}
>
Edit
</button>
<button
type="button"
className="px-[10px] py-[6px] text-sm text-white hover:bg-theme-sidebar-item-hover rounded-b-lg cursor-pointer border-none w-full text-left whitespace-nowrap"
onClick={(e) => {
e.stopPropagation();
setShowMenu(false);
onPublish(preset);
}}
>
Publish
</button>
</div>
)}
</button>
);
}

View File

@ -982,6 +982,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -1021,6 +1021,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -995,70 +995,84 @@ const TRANSLATIONS = {
community_hub: {
publish: {
system_prompt: {
success_title: "Erfolg!",
success_description:
"Ihr Systemprompt ist jetzt im Community Hub verfügbar!",
success_thank_you:
"Herzlichen Dank für Ihre Beteiligung an der Community!",
view_on_hub: "Im Community Hub anzeigen",
modal_title: "Systemprompt veröffentlichen",
name_label: "Name",
name_description: "Dies ist der Anzeigename Ihres Systemprompts.",
name_placeholder: "Mein Systemprompt",
description_label: "Beschreibung",
description_description:
"Hier können Sie erklären, welchem Zweck Ihr Systemprompt dient.",
tags_label: "Tags",
tags_description:
"Tags erleichtern die Schuche nach Ihrem Prompt. Sie können bis zu 5 Tags vergeben, jedes maximal 20 Zeichen.",
tags_placeholder: "Geben Sie Tags ein und bestätigen Sie mit Enter",
visibility_label: "Sichtbarkeit",
public_description:
"Öffentliche Systemprompts sind für jeden sichtbar.",
private_description: "Private Systemprompts können nur Sie sehen.",
publish_button: "Im Community Hub veröffentlichen",
submitting: "Wird veröffentlicht",
submit: "Veröffentlichen",
prompt_label: "Prompt",
prompt_description:
"Hier geben Sie den Slash-Befehl ein, der das LLM steuert.",
prompt_placeholder: "Systemprompt hier eingeben...",
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
submit: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
agent_flow: {
public_description:
"Öffentliche Agentenflüsse sind für jeden sichtbar.",
private_description: "Private Agentenflüsse können nur Sie sehen.",
success_title: "Erfolg!",
success_description:
"Ihr Agentenfluss ist jetzt im Community Hub verfügbar!",
success_thank_you:
"Herzlichen Dank für Ihre Beteiligung an der Community!",
view_on_hub: "Im Community Hub anzeigen",
modal_title: "Agentenfluss veröffentlichen",
name_label: "Name",
name_description: "Dies ist der Anzeigename Ihres Agentenflusses.",
name_placeholder: "Mein Agentenfluss",
description_label: "Beschreibung",
description_description:
"Hier können Sie erklären, welchem Zweck Ihr Agentenfluss dient.",
tags_label: "Tags",
tags_description:
"Tags erleichtern die Schuche nach Ihrem Agentenfluss. Sie können bis zu 5 Tags vergeben, jedes maximal 20 Zeichen.",
tags_placeholder: "Geben Sie Tags ein und bestätigen Sie mit Enter",
visibility_label: "Sichtbarkeit",
publish_button: "Im Community Hub veröffentlichen",
submitting: "Wird veröffentlicht",
submit: "Veröffentlichen",
privacy_note: "Private Agentenflüsse können nur Sie sehen.",
public_description: null,
private_description: null,
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
publish_button: null,
submitting: null,
submit: null,
privacy_note: null,
},
generic: {
unauthenticated: {
title: "Anmeldung erforderlich",
description:
"Vor der Veröffentlichung müssen Sie sich bei AnythingLLM Community Hub anmelden.",
button: "Mit Community Hub verbinden",
title: null,
description: null,
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -1084,6 +1084,37 @@ const TRANSLATIONS = {
privacy_note:
"Agent flows are always uploaded as private to protect any sensitive data. You can change the visibility in the Community Hub after publishing. Please verify your flow does not contain any sensitive or private information before publishing.",
},
slash_command: {
success_title: "Success!",
success_description:
"Your Slash Command has been published to the Community Hub!",
success_thank_you: "Thank you for sharing to the Community!",
view_on_hub: "View on Community Hub",
modal_title: "Publish Slash Command",
name_label: "Name",
name_description: "This is the display name of your slash command.",
name_placeholder: "My Slash Command",
description_label: "Description",
description_description:
"This is the description of your slash command. Use this to describe the purpose of your slash command.",
command_label: "Command",
command_description:
"This is the slash command that users will type to trigger this preset.",
command_placeholder: "my-command",
tags_label: "Tags",
tags_description:
"Tags are used to label your slash command for easier searching. You can add multiple tags. Max 5 tags. Max 20 characters per tag.",
tags_placeholder: "Type and press Enter to add tags",
visibility_label: "Visibility",
public_description: "Public slash commands are visible to everyone.",
private_description: "Private slash commands are only visible to you.",
publish_button: "Publish to Community Hub",
submitting: "Publishing...",
prompt_label: "Prompt",
prompt_description:
"This is the prompt that will be used when the slash command is triggered.",
prompt_placeholder: "Enter your prompt here...",
},
generic: {
unauthenticated: {
title: "Authentication Required",

View File

@ -986,6 +986,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -974,6 +974,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -982,6 +982,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -967,6 +967,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -980,6 +980,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -1016,6 +1016,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -967,6 +967,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -1038,6 +1038,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -977,6 +977,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -1019,6 +1019,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -1022,6 +1022,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -977,6 +977,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -976,6 +976,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -978,6 +978,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -979,6 +979,32 @@ const TRANSLATIONS = {
button: null,
},
},
slash_command: {
success_title: null,
success_description: null,
success_thank_you: null,
view_on_hub: null,
modal_title: null,
name_label: null,
name_description: null,
name_placeholder: null,
description_label: null,
description_description: null,
command_label: null,
command_description: null,
command_placeholder: null,
tags_label: null,
tags_description: null,
tags_placeholder: null,
visibility_label: null,
public_description: null,
private_description: null,
publish_button: null,
submitting: null,
prompt_label: null,
prompt_description: null,
prompt_placeholder: null,
},
},
},
};

View File

@ -187,7 +187,6 @@ const CommunityHub = {
* @param {Object} data - The agent flow data
* @returns {Promise<{success: boolean, error: string | null}>}
*/
createAgentFlow: async (data) => {
return await fetch(`${API_BASE}/community-hub/agent-flow/create`, {
method: "POST",
@ -200,6 +199,35 @@ const CommunityHub = {
return { success: true, error: null, itemId: response.item?.id };
});
},
/**
* Create a new slash command in the community hub
* @param {Object} data - The slash command data
* @param {string} data.name - The name of the command
* @param {string} data.description - The description of the command
* @param {string} data.command - The actual command text
* @param {string} data.prompt - The prompt for the command
* @param {string[]} data.tags - Array of tags
* @param {string} data.visibility - Either 'public' or 'private'
* @returns {Promise<{success: boolean, error: string | null}>}
*/
createSlashCommand: async (data) => {
return await fetch(`${API_BASE}/community-hub/slash-command/create`, {
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 create slash command");
return { success: true, error: null, itemId: response.item?.id };
})
.catch((e) => ({
success: false,
error: e.message,
}));
},
};
export default CommunityHub;

View File

@ -9,7 +9,7 @@ const CommunityHub = {
process.env.NODE_ENV === "development"
? "http://127.0.0.1:5001/anythingllm-hub/us-central1/external/v1"
: "https://hub.external.anythingllm.com/v1",
supportedStaticItemTypes: ["system-prompt", "agent-flow"],
supportedStaticItemTypes: ["system-prompt", "agent-flow", "slash-command"],
/**
* Validate an import ID and return the entity type and ID.