Publish system prompts to hub (#3976)
* 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 --------- Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
parent
7cef25822c
commit
96b532a0f4
@ -0,0 +1,242 @@
|
||||
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/dist/ssr";
|
||||
|
||||
export default function SystemPrompts({ 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"),
|
||||
prompt: form.get("prompt"),
|
||||
tags: tags,
|
||||
visibility: visibility,
|
||||
};
|
||||
|
||||
const { success, error, itemId } =
|
||||
await CommunityHub.createSystemPrompt(data);
|
||||
if (!success) throw new Error(error);
|
||||
setItemId(itemId);
|
||||
setIsSuccess(true);
|
||||
} catch (error) {
|
||||
console.error("Failed to publish prompt:", error);
|
||||
showToast(`Failed to publish prompt: ${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("chat.prompt.publish.success_title")}
|
||||
</h3>
|
||||
<p className="text-lg text-theme-text-primary text-center max-w-2xl">
|
||||
{t("chat.prompt.publish.success_description")}
|
||||
</p>
|
||||
<p className="text-theme-text-secondary text-center text-sm">
|
||||
{t("chat.prompt.publish.success_thank_you")}
|
||||
</p>
|
||||
<a
|
||||
href={paths.communityHub.viewItem("system-prompt", 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("chat.prompt.publish.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">
|
||||
{t(`chat.prompt.publish.modal_title`)}
|
||||
</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("chat.prompt.publish.name_label")}
|
||||
</label>
|
||||
<div className="text-xs text-theme-text-secondary mb-2">
|
||||
{t("chat.prompt.publish.name_description")}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
required
|
||||
minLength={3}
|
||||
maxLength={300}
|
||||
placeholder={t("chat.prompt.publish.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("chat.prompt.publish.description_label")}
|
||||
</label>
|
||||
<div className="text-xs text-white/60 mb-2">
|
||||
{t("chat.prompt.publish.description_description")}
|
||||
</div>
|
||||
<textarea
|
||||
name="description"
|
||||
required
|
||||
minLength={10}
|
||||
maxLength={1000}
|
||||
placeholder={t("chat.prompt.publish.description_description")}
|
||||
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("chat.prompt.publish.tags_label")}
|
||||
</label>
|
||||
<div className="text-xs text-white/60 mb-2">
|
||||
{t("chat.prompt.publish.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("chat.prompt.publish.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("chat.prompt.publish.visibility_label")}
|
||||
</label>
|
||||
<div className="text-xs text-white/60 mb-2">
|
||||
{visibility === "public"
|
||||
? t("chat.prompt.publish.public_description")
|
||||
: t("chat.prompt.publish.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("chat.prompt.publish.prompt_label")}
|
||||
</label>
|
||||
<div className="text-xs text-white/60 mb-2">
|
||||
{t("chat.prompt.publish.prompt_description")}
|
||||
</div>
|
||||
<textarea
|
||||
name="prompt"
|
||||
required
|
||||
minLength={10}
|
||||
defaultValue={entity}
|
||||
placeholder={t("chat.prompt.publish.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("chat.prompt.publish.publishing")
|
||||
: t("chat.prompt.publish.publish_button")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { useCommunityHubAuth } from "@/hooks/useCommunityHubAuth";
|
||||
import UnauthenticatedHubModal from "@/components/CommunityHub/UnauthenticatedHubModal";
|
||||
import SystemPrompts from "./SystemPrompts";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
|
||||
export default function PublishEntityModal({
|
||||
show,
|
||||
onClose,
|
||||
entityType,
|
||||
entity,
|
||||
}) {
|
||||
const { isAuthenticated, loading } = useCommunityHubAuth();
|
||||
if (!show || loading) return null;
|
||||
if (!isAuthenticated)
|
||||
return <UnauthenticatedHubModal show={show} onClose={onClose} />;
|
||||
|
||||
const renderEntityForm = () => {
|
||||
switch (entityType) {
|
||||
case "system-prompt":
|
||||
return <SystemPrompts entity={entity} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper isOpen={show}>
|
||||
<div className="relative max-w-[900px] bg-theme-bg-primary rounded-lg shadow border border-theme-modal-border">
|
||||
<div className="relative p-6">
|
||||
<button
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
className="absolute top-4 right-4 transition-all duration-300 bg-transparent rounded-lg text-sm p-1 inline-flex items-center hover:bg-theme-modal-border hover:border-theme-modal-border hover:border-opacity-50 border-transparent border"
|
||||
>
|
||||
<X size={18} weight="bold" className="text-white" />
|
||||
</button>
|
||||
</div>
|
||||
{renderEntityForm()}
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import paths from "@/utils/paths";
|
||||
import { Link } from "react-router-dom";
|
||||
import ModalWrapper from "@/components/ModalWrapper";
|
||||
|
||||
export default function UnauthenticatedHubModal({ show, onClose }) {
|
||||
const { t } = useTranslation();
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<ModalWrapper isOpen={show}>
|
||||
<div className="relative w-[400px] max-w-full bg-theme-bg-primary rounded-lg shadow border border-theme-modal-border">
|
||||
<div className="p-6">
|
||||
<button
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
className="absolute top-4 right-4 transition-all duration-300 bg-transparent rounded-lg text-sm p-1 inline-flex items-center hover:bg-theme-modal-border hover:border-theme-modal-border hover:border-opacity-50 border-transparent border"
|
||||
>
|
||||
<X size={18} weight="bold" className="text-white" />
|
||||
</button>
|
||||
<div className="flex flex-col items-center justify-center gap-y-4">
|
||||
<h3 className="text-lg font-semibold text-white">
|
||||
{t("chat.prompt.publish.unauthenticated.title")}
|
||||
</h3>
|
||||
<p className="text-lg text-white text-center max-w-[300px]">
|
||||
{t("chat.prompt.publish.unauthenticated.description")}
|
||||
</p>
|
||||
<Link
|
||||
to={paths.communityHub.authentication()}
|
||||
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("chat.prompt.publish.unauthenticated.button")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
30
frontend/src/hooks/useCommunityHubAuth.js
Normal file
30
frontend/src/hooks/useCommunityHubAuth.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import CommunityHub from "@/models/communityHub";
|
||||
|
||||
/**
|
||||
* Hook to check if the user is authenticated with the community hub by checking
|
||||
* the user defined connection key in the settings.
|
||||
* @returns {{isAuthenticated: boolean, loading: boolean}} An object containing the authentication status and loading state.
|
||||
*/
|
||||
export function useCommunityHubAuth() {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function checkCommunityHubAuth() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { connectionKey } = await CommunityHub.getSettings();
|
||||
setIsAuthenticated(!!connectionKey);
|
||||
} catch (error) {
|
||||
console.error("Error checking hub auth:", error);
|
||||
setIsAuthenticated(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
checkCommunityHubAuth();
|
||||
}, []);
|
||||
|
||||
return { isAuthenticated, loading };
|
||||
}
|
||||
@ -17,6 +17,7 @@
|
||||
--theme-bg-chat-input: #27282a;
|
||||
--theme-text-primary: #ffffff;
|
||||
--theme-text-secondary: rgba(255, 255, 255, 0.6);
|
||||
--theme-placeholder: #57585a;
|
||||
--theme-sidebar-item-default: rgba(255, 255, 255, 0.1);
|
||||
--theme-sidebar-item-selected: rgba(255, 255, 255, 0.3);
|
||||
--theme-sidebar-item-hover: #3f3f42;
|
||||
@ -116,6 +117,7 @@
|
||||
--theme-bg-chat-input: #eaeaea;
|
||||
--theme-text-primary: #0e0f0f;
|
||||
--theme-text-secondary: #7a7d7e;
|
||||
--theme-placeholder: #9ca3af;
|
||||
--theme-sidebar-item-default: #ffffff;
|
||||
--theme-sidebar-item-selected: #ffffff;
|
||||
--theme-sidebar-item-hover: #c8efff;
|
||||
|
||||
@ -240,6 +240,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -241,6 +241,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -233,6 +233,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -335,11 +335,45 @@ const TRANSLATIONS = {
|
||||
noHistory: "No system prompt history available",
|
||||
restore: "Restore",
|
||||
delete: "Delete",
|
||||
publish: "Publish to Community Hub",
|
||||
deleteConfirm: "Are you sure you want to delete this history item?",
|
||||
clearAllConfirm:
|
||||
"Are you sure you want to clear all history? This action cannot be undone.",
|
||||
expand: "Expand",
|
||||
},
|
||||
publish: {
|
||||
public_description: "Public system prompts are visible to everyone.",
|
||||
private_description: "Private system prompts are only visible to you.",
|
||||
success_title: "Success!",
|
||||
success_description:
|
||||
"Your System Prompt 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 System Prompt",
|
||||
name_label: "Name",
|
||||
name_description: "This is the display name of your system prompt.",
|
||||
name_placeholder: "My System Prompt",
|
||||
description_label: "Description",
|
||||
description_description:
|
||||
"This is the description of your system prompt. Use this to describe the purpose of your system prompt.",
|
||||
tags_label: "Tags",
|
||||
tags_description:
|
||||
"Tags are used to label your system prompt 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",
|
||||
prompt_label: "Prompt",
|
||||
prompt_description:
|
||||
"This is the actual slash command that will be used to guide the LLM.",
|
||||
prompt_placeholder: "Enter your system prompt here...",
|
||||
publish_button: "Publish to Community Hub",
|
||||
publishing: "Publishing...",
|
||||
unauthenticated: {
|
||||
title: "Authentication Required",
|
||||
description:
|
||||
"You need to authenticate with the AnythingLLM Community Hub before publishing prompts.",
|
||||
button: "Connect to Community Hub",
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
title: "Query mode refusal response",
|
||||
|
||||
@ -235,6 +235,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -231,6 +231,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -236,6 +236,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -229,6 +229,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -234,6 +234,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -240,6 +240,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -229,6 +229,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -325,6 +325,35 @@ const TRANSLATIONS = {
|
||||
clearAllConfirm:
|
||||
"Vai tiešām vēlaties nodzēst visu vēsturi? Šo darbību nevar atsaukt.",
|
||||
expand: "Paplašināt",
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -233,6 +233,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -322,6 +322,35 @@ const TRANSLATIONS = {
|
||||
clearAllConfirm:
|
||||
"Tem certeza que deseja limpar todo o histórico? Esta ação é irreversível.",
|
||||
expand: "Expandir",
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -242,6 +242,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -233,6 +233,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -232,6 +232,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -312,6 +312,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: "您确定要删除此历史记录吗?",
|
||||
clearAllConfirm: "您确定要清除所有历史记录吗?此操作无法撤消。",
|
||||
expand: "展开",
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -230,6 +230,35 @@ const TRANSLATIONS = {
|
||||
deleteConfirm: null,
|
||||
clearAllConfirm: null,
|
||||
expand: null,
|
||||
publish: null,
|
||||
},
|
||||
publish: {
|
||||
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,
|
||||
prompt_label: null,
|
||||
prompt_description: null,
|
||||
prompt_placeholder: null,
|
||||
publish_button: null,
|
||||
publishing: null,
|
||||
unauthenticated: {
|
||||
title: null,
|
||||
description: null,
|
||||
button: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
refusal: {
|
||||
|
||||
@ -153,6 +153,34 @@ const CommunityHub = {
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new system prompt in the community hub
|
||||
* @param {Object} data - The system prompt data
|
||||
* @param {string} data.name - The name of the prompt
|
||||
* @param {string} data.description - The description of the prompt
|
||||
* @param {string} data.prompt - The actual system prompt text
|
||||
* @param {string[]} data.tags - Array of tags
|
||||
* @param {string} data.visibility - Either 'public' or 'private'
|
||||
* @returns {Promise<{success: boolean, error: string | null}>}
|
||||
*/
|
||||
createSystemPrompt: async (data) => {
|
||||
return await fetch(`${API_BASE}/community-hub/system-prompt/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 system prompt");
|
||||
return { success: true, error: null, itemId: response.item?.id };
|
||||
})
|
||||
.catch((e) => ({
|
||||
success: false,
|
||||
error: e.message,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
export default CommunityHub;
|
||||
|
||||
@ -14,6 +14,7 @@ export default function PromptHistoryItem({
|
||||
user,
|
||||
onRestore,
|
||||
setHistory,
|
||||
onPublishClick,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
@ -82,11 +83,21 @@ export default function PromptHistoryItem({
|
||||
{showMenu && (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="absolute right-0 top-6 bg-theme-bg-popup-menu rounded-lg z-50"
|
||||
className="absolute right-0 top-6 bg-theme-bg-popup-menu rounded-lg z-50 min-w-[200px]"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="px-[10px] py-[6px] text-sm text-white hover:bg-theme-hover cursor-pointer border-none"
|
||||
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={() => {
|
||||
setShowMenu(false);
|
||||
onPublishClick(prompt);
|
||||
}}
|
||||
>
|
||||
{t("chat.prompt.history.publish")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-[10px] py-[6px] text-sm text-white hover:bg-red-500/60 light:hover:bg-red-300/80 rounded-b-lg cursor-pointer border-none w-full text-left whitespace-nowrap"
|
||||
onClick={() => {
|
||||
setShowMenu(false);
|
||||
deleteHistory(id);
|
||||
|
||||
@ -7,7 +7,7 @@ import * as Skeleton from "react-loading-skeleton";
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
|
||||
export default forwardRef(function ChatPromptHistory(
|
||||
{ show, workspaceSlug, onRestore, onClose },
|
||||
{ show, workspaceSlug, onRestore, onClose, onPublishClick },
|
||||
ref
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
@ -92,6 +92,7 @@ export default forwardRef(function ChatPromptHistory(
|
||||
id={item.id}
|
||||
{...item}
|
||||
onRestore={() => onRestore(item.prompt)}
|
||||
onPublishClick={onPublishClick}
|
||||
setHistory={setHistory}
|
||||
/>
|
||||
))
|
||||
|
||||
@ -6,6 +6,8 @@ import Highlighter from "react-highlight-words";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
import ChatPromptHistory from "./ChatPromptHistory";
|
||||
import PublishEntityModal from "@/components/CommunityHub/PublishEntityModal";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
|
||||
// TODO: Move to backend and have user-language sensitive default prompt
|
||||
const DEFAULT_PROMPT =
|
||||
@ -21,13 +23,12 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
const promptHistoryRef = useRef(null);
|
||||
const historyButtonRef = useRef(null);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const handleRestore = (prompt) => {
|
||||
setPrompt(prompt);
|
||||
setShowPromptHistory(false);
|
||||
setHasChanges(true);
|
||||
// TODO: Autosave on restore
|
||||
};
|
||||
const {
|
||||
isOpen: showPublishModal,
|
||||
closeModal: closePublishModal,
|
||||
openModal: openPublishModal,
|
||||
} = useModal();
|
||||
const [currentPrompt, setCurrentPrompt] = useState(chatPrompt(workspace));
|
||||
|
||||
useEffect(() => {
|
||||
async function setupVariableHighlighting() {
|
||||
@ -66,6 +67,18 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleRestore = (prompt) => {
|
||||
setPrompt(prompt);
|
||||
setShowPromptHistory(false);
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const handlePublishClick = (prompt) => {
|
||||
setCurrentPrompt(prompt);
|
||||
setShowPromptHistory(false);
|
||||
openPublishModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChatPromptHistory
|
||||
@ -73,6 +86,7 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
workspaceSlug={workspace.slug}
|
||||
show={showPromptHistory}
|
||||
onRestore={handleRestore}
|
||||
onPublishClick={handlePublishClick}
|
||||
onClose={() => {
|
||||
setShowPromptHistory(false);
|
||||
}}
|
||||
@ -120,7 +134,7 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
<button
|
||||
ref={historyButtonRef}
|
||||
type="button"
|
||||
className="text-theme-text-secondary hover:text-white light:hover:text-black text-sm font-medium"
|
||||
className="text-theme-text-secondary hover:text-white light:hover:text-black text-xs font-medium"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setShowPromptHistory(!showPromptHistory);
|
||||
@ -186,17 +200,49 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
</div>
|
||||
<div className="w-full flex flex-row items-center justify-between pt-2">
|
||||
{prompt !== DEFAULT_PROMPT && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRestore(DEFAULT_PROMPT)}
|
||||
className="text-theme-text-primary hover:text-white light:hover:text-black text-sm font-medium"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRestore(DEFAULT_PROMPT)}
|
||||
className="text-theme-text-primary hover:text-white light:hover:text-black text-xs font-medium"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<PublishPromptCTA
|
||||
hidden={
|
||||
isEditing ||
|
||||
prompt === DEFAULT_PROMPT ||
|
||||
prompt?.trim().length < 10
|
||||
}
|
||||
onClick={() => {
|
||||
setCurrentPrompt(prompt);
|
||||
openPublishModal();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PublishEntityModal
|
||||
show={showPublishModal}
|
||||
onClose={closePublishModal}
|
||||
entityType="system-prompt"
|
||||
entity={currentPrompt}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PublishPromptCTA({ hidden = false, onClick }) {
|
||||
if (hidden) return null;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="border-none text-primary-button hover:text-white light:hover:text-black text-xs font-medium"
|
||||
>
|
||||
Publish to Community Hub
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@ -184,6 +184,9 @@ export default {
|
||||
viewMoreOfType: function (type) {
|
||||
return `${this.website()}/list/${type}`;
|
||||
},
|
||||
viewItem: function (type, id) {
|
||||
return `${this.website()}/i/${type}/${id}`;
|
||||
},
|
||||
trending: () => {
|
||||
return `/settings/community-hub/trending`;
|
||||
},
|
||||
|
||||
@ -61,6 +61,7 @@ export default {
|
||||
text: {
|
||||
primary: 'var(--theme-text-primary)',
|
||||
secondary: 'var(--theme-text-secondary)',
|
||||
placeholder: 'var(--theme-placeholder)',
|
||||
},
|
||||
sidebar: {
|
||||
item: {
|
||||
|
||||
@ -181,6 +181,39 @@ function communityHubEndpoints(app) {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/community-hub/:communityHubItemType/create",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { communityHubItemType } = request.params;
|
||||
const { connectionKey } = await SystemSettings.hubSettings();
|
||||
if (!connectionKey)
|
||||
throw new Error("Community Hub connection key not found");
|
||||
|
||||
const data = reqBody(request);
|
||||
const { success, error, itemId } = await CommunityHub.createStaticItem(
|
||||
communityHubItemType,
|
||||
data,
|
||||
connectionKey
|
||||
);
|
||||
if (!success) throw new Error(error);
|
||||
|
||||
await EventLogs.logEvent(
|
||||
"community_hub_publish",
|
||||
{ itemType: communityHubItemType },
|
||||
response.locals?.user?.id
|
||||
);
|
||||
response
|
||||
.status(200)
|
||||
.json({ success: true, error: null, item: { id: itemId } });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
response.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { communityHubEndpoints };
|
||||
|
||||
@ -9,6 +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"],
|
||||
|
||||
/**
|
||||
* Validate an import ID and return the entity type and ID.
|
||||
@ -172,6 +173,41 @@ const CommunityHub = {
|
||||
return { createdByMe: {}, teamItems: [] };
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new item in the community hub - Only supports STATIC items for now.
|
||||
* @param {string} itemType - The type of item to create
|
||||
* @param {object} data - The item data
|
||||
* @param {string} connectionKey - The hub connection key
|
||||
* @returns {Promise<{success: boolean, error: string | null}>}
|
||||
*/
|
||||
createStaticItem: async function (itemType, data, connectionKey) {
|
||||
if (!connectionKey)
|
||||
return { success: false, error: "Connection key is required" };
|
||||
if (!this.supportedStaticItemTypes.includes(itemType))
|
||||
return { success: false, error: "Unsupported item type" };
|
||||
|
||||
// If the item has specical considerations or preprocessing, we can delegate that below before sending the request.
|
||||
// eg: Agent flow files and such.
|
||||
|
||||
return await fetch(`${this.apiBase}/${itemType}/create`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${connectionKey}`,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((result) => {
|
||||
if (!!result.error) throw new Error(result.error || "Unknown error");
|
||||
return { success: true, error: null, itemId: result.item.id };
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Error creating ${itemType}:`, error);
|
||||
return { success: false, error: error.message };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { CommunityHub };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user