Custom Default System Prompt (#4487)
* Add Default System Prompt Management - Introduced a new route for fetching and updating the default system prompt in the backend. - Added a new Admin page for managing the default system prompt, including a form for editing and saving changes. - Updated the SettingsSidebar to include a link to the new Default System Prompt page. - Implemented fetching of available system prompt variables for use in the prompt editor. - Enhanced the ChatSettings and ChatPromptSettings components to support the new default system prompt functionality. This commit lays the groundwork for improved management of system prompts across workspaces. * Remove validation for system prompt in ChatSettings component * Add comment for system prompt in workspaces model * linting, simplify logic for default assumption * dev build --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
parent
de11a06622
commit
5716ac5ed5
2
.github/workflows/dev-build.yaml
vendored
2
.github/workflows/dev-build.yaml
vendored
@ -6,7 +6,7 @@ concurrency:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['4595-refactor-pwa'] # put your current branch to create a build. Core team only.
|
||||
branches: ['3906-global-default-system-prompt'] # put your current branch to create a build. Core team only.
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'cloud-deployments/*'
|
||||
|
||||
@ -93,6 +93,9 @@ const SystemPromptVariables = lazy(
|
||||
const MobileConnections = lazy(
|
||||
() => import("@/pages/GeneralSettings/MobileConnections")
|
||||
);
|
||||
const DefaultSystemPrompt = lazy(
|
||||
() => import("@/pages/Admin/DefaultSystemPrompt")
|
||||
);
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@ -215,6 +218,10 @@ export default function App() {
|
||||
path="/settings/branding"
|
||||
element={<ManagerRoute Component={BrandingSettings} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/default-system-prompt"
|
||||
element={<AdminRoute Component={DefaultSystemPrompt} />}
|
||||
/>
|
||||
<Route
|
||||
path="/settings/chat"
|
||||
element={<ManagerRoute Component={ChatSettings} />}
|
||||
|
||||
@ -285,6 +285,12 @@ const SidebarOptions = ({ user = null, t }) => (
|
||||
href: paths.settings.invites(),
|
||||
roles: ["admin", "manager"],
|
||||
},
|
||||
{
|
||||
btnText: "Default System Prompt",
|
||||
href: paths.settings.defaultSystemPrompt(),
|
||||
flex: true,
|
||||
roles: ["admin"],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Option
|
||||
|
||||
@ -346,6 +346,39 @@ const System = {
|
||||
);
|
||||
return { appName: customAppName, error: null };
|
||||
},
|
||||
/**
|
||||
* Fetches the default system prompt from the server.
|
||||
* @returns {Promise<{defaultSystemPrompt: string, saneDefaultSystemPrompt: string}>}
|
||||
*/
|
||||
fetchDefaultSystemPrompt: async function () {
|
||||
return await fetch(`${API_BASE}/system/default-system-prompt`, {
|
||||
method: "GET",
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => ({
|
||||
defaultSystemPrompt: res.defaultSystemPrompt,
|
||||
saneDefaultSystemPrompt: res.saneDefaultSystemPrompt,
|
||||
}))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return { defaultSystemPrompt: "", saneDefaultSystemPrompt: "" };
|
||||
});
|
||||
},
|
||||
updateDefaultSystemPrompt: async function (defaultSystemPrompt) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/system/default-system-prompt`, {
|
||||
method: "POST",
|
||||
headers: baseHeaders(),
|
||||
body: JSON.stringify({ defaultSystemPrompt }),
|
||||
});
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { success: false, message: e.message };
|
||||
}
|
||||
},
|
||||
fetchLogo: async function () {
|
||||
const url = new URL(`${fullApiUrl()}/system/logo`);
|
||||
url.searchParams.append(
|
||||
|
||||
270
frontend/src/pages/Admin/DefaultSystemPrompt/index.jsx
Normal file
270
frontend/src/pages/Admin/DefaultSystemPrompt/index.jsx
Normal file
@ -0,0 +1,270 @@
|
||||
import SettingsSidebar from "@/components/SettingsSidebar";
|
||||
import { useEffect, useState, Fragment } from "react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import System from "@/models/system";
|
||||
import showToast from "@/utils/toast";
|
||||
import * as Skeleton from "react-loading-skeleton";
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
import Highlighter from "react-highlight-words";
|
||||
import SystemPromptVariable from "@/models/systemPromptVariable";
|
||||
import { Link } from "react-router-dom";
|
||||
import paths from "@/utils/paths";
|
||||
|
||||
export default function DefaultSystemPrompt() {
|
||||
const [systemPromptForm, setSystemPromptForm] = useState({
|
||||
value: "",
|
||||
default: "",
|
||||
isDirty: false,
|
||||
isSubmitting: false,
|
||||
isLoading: true,
|
||||
isEditing: false,
|
||||
});
|
||||
const [saneDefaultSystemPrompt, setSaneDefaultSystemPrompt] = useState("");
|
||||
const [availableVariables, setAvailableVariables] = useState([]);
|
||||
useEffect(() => {
|
||||
async function setupVariableHighlighting() {
|
||||
const { variables } = await SystemPromptVariable.getAll();
|
||||
setAvailableVariables(variables);
|
||||
}
|
||||
setupVariableHighlighting();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDefaultSystemPrompt() {
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
isLoading: true,
|
||||
}));
|
||||
const { defaultSystemPrompt, saneDefaultSystemPrompt } =
|
||||
await System.fetchDefaultSystemPrompt();
|
||||
setSaneDefaultSystemPrompt(saneDefaultSystemPrompt);
|
||||
if (!defaultSystemPrompt)
|
||||
return setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
}));
|
||||
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
default: defaultSystemPrompt,
|
||||
value: defaultSystemPrompt,
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
fetchDefaultSystemPrompt();
|
||||
}, []);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const value = e.target.value;
|
||||
const isDirty = value !== systemPromptForm.default;
|
||||
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
value,
|
||||
isDirty,
|
||||
isSubmitting: false,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
isSubmitting: true,
|
||||
}));
|
||||
const newSystemPrompt = systemPromptForm.value.trim();
|
||||
await System.updateDefaultSystemPrompt(newSystemPrompt)
|
||||
.then(({ success, message }) => {
|
||||
if (!success) throw new Error(message);
|
||||
|
||||
// If the user has set the default system prompt to the sane default, reset the value to the sane default.
|
||||
if (
|
||||
!newSystemPrompt ||
|
||||
newSystemPrompt.trim() === saneDefaultSystemPrompt
|
||||
) {
|
||||
return setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
value: saneDefaultSystemPrompt,
|
||||
}));
|
||||
}
|
||||
|
||||
showToast("Default system prompt updated successfully.", "success");
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
default: newSystemPrompt,
|
||||
isDirty: false,
|
||||
isSubmitting: false,
|
||||
}));
|
||||
})
|
||||
.catch((error) => {
|
||||
showToast(
|
||||
`Failed to update default system prompt: ${error.message}`,
|
||||
"error"
|
||||
);
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
isSubmitting: false,
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
|
||||
<SettingsSidebar />
|
||||
<div
|
||||
style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
|
||||
className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll p-4 md:p-0"
|
||||
>
|
||||
<div className="flex flex-col w-full px-1 md:pl-6 md:pr-[50px] md:py-6 py-16">
|
||||
<div className="w-full flex flex-col gap-y-1 pb-6 border-white/10 border-b-2">
|
||||
<div className="items-center flex gap-x-4">
|
||||
<p className="text-lg leading-6 font-bold text-theme-text-primary">
|
||||
Default System Prompt
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs leading-[18px] font-base text-theme-text-secondary">
|
||||
This is the default system prompt that will be used for new
|
||||
workspaces.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{systemPromptForm.isLoading ? (
|
||||
<div className="mt-8 flex flex-col gap-y-4">
|
||||
<Skeleton.default
|
||||
height={20}
|
||||
width={160}
|
||||
highlightColor="var(--theme-bg-primary)"
|
||||
baseColor="var(--theme-bg-secondary)"
|
||||
/>
|
||||
<Skeleton.default
|
||||
height={120}
|
||||
width="100%"
|
||||
highlightColor="var(--theme-bg-primary)"
|
||||
baseColor="var(--theme-bg-secondary)"
|
||||
className="rounded-lg"
|
||||
/>
|
||||
<Skeleton.default
|
||||
height={36}
|
||||
width={140}
|
||||
highlightColor="var(--theme-bg-primary)"
|
||||
baseColor="var(--theme-bg-secondary)"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
<label
|
||||
htmlFor="default-system-prompt"
|
||||
className=" text-base font-bold text-white"
|
||||
>
|
||||
System Prompt
|
||||
</label>
|
||||
<div className="space-y-1">
|
||||
<p className="text-white text-opacity-60 text-xs font-medium">
|
||||
A system prompt provides instructions that shape the AI’s
|
||||
responses and behavior. This prompt will be automatically
|
||||
applied to all newly created workspaces. To change the
|
||||
system prompt of a{" "}
|
||||
<span className="font-bold">specific workspace</span>,
|
||||
edit the prompt in the{" "}
|
||||
<span className="font-bold">workspace settings</span>. To
|
||||
restore the system prompt to our sane default, leave this
|
||||
field empty and save changes.
|
||||
</p>
|
||||
<p className="text-white text-opacity-60 text-xs font-medium mb-2">
|
||||
You can insert{" "}
|
||||
<Link
|
||||
to={paths.settings.systemPromptVariables()}
|
||||
className="text-primary-button"
|
||||
>
|
||||
system prompt variables
|
||||
</Link>{" "}
|
||||
like:{" "}
|
||||
{availableVariables.slice(0, 3).map((v, i) => (
|
||||
<Fragment key={v.key}>
|
||||
<span className="bg-theme-settings-input-bg px-1 py-0.5 rounded">
|
||||
{`{${v.key}}`}
|
||||
</span>
|
||||
{i < availableVariables.length - 1 && ", "}
|
||||
</Fragment>
|
||||
))}
|
||||
{availableVariables.length > 3 && (
|
||||
<Link
|
||||
to={paths.settings.systemPromptVariables()}
|
||||
className="text-primary-button"
|
||||
>
|
||||
+{availableVariables.length - 3} more...
|
||||
</Link>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{systemPromptForm.isEditing ? (
|
||||
<textarea
|
||||
autoFocus={true}
|
||||
value={systemPromptForm.value}
|
||||
onChange={handleChange}
|
||||
onBlur={() =>
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
isEditing: false,
|
||||
}))
|
||||
}
|
||||
placeholder={
|
||||
systemPromptForm.isLoading
|
||||
? "Loading..."
|
||||
: "You are an AI assistant that can answer questions and help with tasks."
|
||||
}
|
||||
rows={5}
|
||||
style={{
|
||||
resize: "vertical",
|
||||
overflowY: "scroll",
|
||||
minHeight: "150px",
|
||||
}}
|
||||
className="w-full border-none bg-theme-settings-input-bg placeholder:text-theme-settings-input-placeholder text-white text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block p-2.5"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
onClick={() =>
|
||||
setSystemPromptForm((prev) => ({
|
||||
...prev,
|
||||
isEditing: true,
|
||||
}))
|
||||
}
|
||||
style={{
|
||||
resize: "vertical",
|
||||
overflowY: "scroll",
|
||||
minHeight: "150px",
|
||||
}}
|
||||
className="w-full border-none bg-theme-settings-input-bg text-white text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block p-2.5 cursor-text"
|
||||
>
|
||||
<Highlighter
|
||||
className="whitespace-pre-wrap"
|
||||
highlightClassName="bg-cta-button p-0.5 rounded-md"
|
||||
searchWords={availableVariables.map(
|
||||
(v) => `{${v.key}}`
|
||||
)}
|
||||
autoEscape={true}
|
||||
caseSensitive={true}
|
||||
textToHighlight={systemPromptForm.value || ""}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
disabled={
|
||||
!systemPromptForm.isDirty || systemPromptForm.isSubmitting
|
||||
}
|
||||
className={`enabled:hover:bg-secondary enabled:hover:text-white rounded-lg bg-primary-button w-fit py-2 px-4 font-semibold text-xs disabled:opacity-20 disabled:cursor-not-allowed`}
|
||||
type="submit"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useRef, Fragment } from "react";
|
||||
import { chatPrompt } from "@/utils/chat";
|
||||
import { getWorkspaceSystemPrompt } from "@/utils/chat";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SystemPromptVariable from "@/models/systemPromptVariable";
|
||||
import Highlighter from "react-highlight-words";
|
||||
@ -8,47 +8,75 @@ import paths from "@/utils/paths";
|
||||
import ChatPromptHistory from "./ChatPromptHistory";
|
||||
import PublishEntityModal from "@/components/CommunityHub/PublishEntityModal";
|
||||
import { useModal } from "@/hooks/useModal";
|
||||
import System from "@/models/system";
|
||||
|
||||
// TODO: Move to backend and have user-language sensitive default prompt
|
||||
const DEFAULT_PROMPT =
|
||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.";
|
||||
|
||||
export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
export default function ChatPromptSettings({
|
||||
workspace,
|
||||
setHasChanges,
|
||||
hasChanges,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [availableVariables, setAvailableVariables] = useState([]);
|
||||
const [prompt, setPrompt] = useState(chatPrompt(workspace));
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// Prompt state
|
||||
const initialPrompt = getWorkspaceSystemPrompt(workspace);
|
||||
const [prompt, setPrompt] = useState(initialPrompt);
|
||||
const [savedPrompt, setSavedPrompt] = useState(initialPrompt);
|
||||
const [defaultSystemPrompt, setDefaultSystemPrompt] = useState("");
|
||||
|
||||
// UI state
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [showPromptHistory, setShowPromptHistory] = useState(false);
|
||||
const [availableVariables, setAvailableVariables] = useState([]);
|
||||
|
||||
// Refs
|
||||
const promptRef = useRef(null);
|
||||
const promptHistoryRef = useRef(null);
|
||||
const historyButtonRef = useRef(null);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// Modals
|
||||
const {
|
||||
isOpen: showPublishModal,
|
||||
closeModal: closePublishModal,
|
||||
openModal: openPublishModal,
|
||||
} = useModal();
|
||||
const [currentPrompt, setCurrentPrompt] = useState(chatPrompt(workspace));
|
||||
|
||||
// Derived state
|
||||
const isDirty = prompt !== savedPrompt;
|
||||
const hasBeenModified = savedPrompt?.trim() !== initialPrompt?.trim();
|
||||
const showPublishButton =
|
||||
!isEditing && prompt?.trim().length >= 10 && (isDirty || hasBeenModified);
|
||||
|
||||
// Load variables and handle focus on mount
|
||||
useEffect(() => {
|
||||
async function setupVariableHighlighting() {
|
||||
const { variables } = await SystemPromptVariable.getAll();
|
||||
setAvailableVariables(variables);
|
||||
}
|
||||
setupVariableHighlighting();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get("action") === "focus-system-prompt")
|
||||
setIsEditing(true);
|
||||
}, [searchParams]);
|
||||
|
||||
// Update saved prompt when parent clears hasChanges
|
||||
useEffect(() => {
|
||||
if (!hasChanges) setSavedPrompt(prompt);
|
||||
}, [hasChanges, prompt]);
|
||||
|
||||
// Auto-focus textarea when editing
|
||||
useEffect(() => {
|
||||
if (isEditing && promptRef.current) {
|
||||
promptRef.current.focus();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
useEffect(() => {
|
||||
System.fetchDefaultSystemPrompt().then(({ defaultSystemPrompt }) =>
|
||||
setDefaultSystemPrompt(defaultSystemPrompt)
|
||||
);
|
||||
}, []);
|
||||
|
||||
// Handle click outside for history panel
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (
|
||||
@ -61,22 +89,27 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const handleRestore = (prompt) => {
|
||||
setPrompt(prompt);
|
||||
const handleRestoreFromHistory = (historicalPrompt) => {
|
||||
setPrompt(historicalPrompt);
|
||||
setShowPromptHistory(false);
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const handlePublishClick = (prompt) => {
|
||||
setCurrentPrompt(prompt);
|
||||
setShowPromptHistory(false);
|
||||
const handlePublishFromHistory = (historicalPrompt) => {
|
||||
openPublishModal();
|
||||
setShowPromptHistory(false);
|
||||
setTimeout(() => setPrompt(historicalPrompt), 0);
|
||||
};
|
||||
|
||||
// Restore to default system prompt, if no default system prompt is set
|
||||
const handleRestoreToDefaultSystemPrompt = () => {
|
||||
System.fetchDefaultSystemPrompt().then(({ defaultSystemPrompt }) => {
|
||||
setPrompt(defaultSystemPrompt);
|
||||
setHasChanges(true);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -85,11 +118,9 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
ref={promptHistoryRef}
|
||||
workspaceSlug={workspace.slug}
|
||||
show={showPromptHistory}
|
||||
onRestore={handleRestore}
|
||||
onPublishClick={handlePublishClick}
|
||||
onClose={() => {
|
||||
setShowPromptHistory(false);
|
||||
}}
|
||||
onRestore={handleRestoreFromHistory}
|
||||
onPublishClick={handlePublishFromHistory}
|
||||
onClose={() => setShowPromptHistory(false)}
|
||||
/>
|
||||
<div>
|
||||
<div className="flex flex-col">
|
||||
@ -129,7 +160,7 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="openAiPrompt" defaultValue={prompt} />
|
||||
<input type="hidden" name="openAiPrompt" value={prompt} />
|
||||
<div className="relative w-full flex flex-col items-end">
|
||||
<button
|
||||
ref={historyButtonRef}
|
||||
@ -143,11 +174,6 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
{showPromptHistory ? "Hide History" : "View History"}
|
||||
</button>
|
||||
<div className="relative w-full">
|
||||
<span
|
||||
className={`${!!prompt ? "hidden" : "block"} text-sm pointer-events-none absolute top-2 left-0 p-2.5 w-full h-full !text-theme-settings-input-placeholder opacity-60`}
|
||||
>
|
||||
{DEFAULT_PROMPT}
|
||||
</span>
|
||||
{isEditing ? (
|
||||
<textarea
|
||||
ref={promptRef}
|
||||
@ -199,28 +225,19 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full flex flex-row items-center justify-between pt-2">
|
||||
{prompt !== DEFAULT_PROMPT && (
|
||||
<>
|
||||
{prompt !== defaultSystemPrompt && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRestore(DEFAULT_PROMPT)}
|
||||
onClick={handleRestoreToDefaultSystemPrompt}
|
||||
className="text-theme-text-primary hover:text-white light:hover:text-black text-xs font-medium"
|
||||
>
|
||||
Clear
|
||||
Restore to Default
|
||||
</button>
|
||||
<PublishPromptCTA
|
||||
hidden={
|
||||
isEditing ||
|
||||
prompt === DEFAULT_PROMPT ||
|
||||
prompt?.trim().length < 10
|
||||
}
|
||||
onClick={() => {
|
||||
setCurrentPrompt(prompt);
|
||||
openPublishModal();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<PublishPromptCTA
|
||||
hidden={!showPublishButton}
|
||||
onClick={openPublishModal}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -228,7 +245,7 @@ export default function ChatPromptSettings({ workspace, setHasChanges }) {
|
||||
show={showPublishModal}
|
||||
onClose={closePublishModal}
|
||||
entityType="system-prompt"
|
||||
entity={currentPrompt}
|
||||
entity={prompt}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -26,22 +26,24 @@ export default function ChatSettings({ workspace }) {
|
||||
}, []);
|
||||
|
||||
const handleUpdate = async (e) => {
|
||||
setSaving(true);
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
const data = {};
|
||||
const form = new FormData(formEl.current);
|
||||
for (var [key, value] of form.entries()) data[key] = castToType(key, value);
|
||||
|
||||
const { workspace: updatedWorkspace, message } = await Workspace.update(
|
||||
workspace.slug,
|
||||
data
|
||||
);
|
||||
if (!!updatedWorkspace) {
|
||||
if (updatedWorkspace) {
|
||||
showToast("Workspace updated!", "success", { clear: true });
|
||||
setHasChanges(false);
|
||||
} else {
|
||||
showToast(`Error: ${message}`, "error", { clear: true });
|
||||
// Keep hasChanges true on error so user can retry
|
||||
}
|
||||
setSaving(false);
|
||||
setHasChanges(false);
|
||||
};
|
||||
|
||||
if (!workspace) return null;
|
||||
@ -76,6 +78,7 @@ export default function ChatSettings({ workspace }) {
|
||||
<ChatPromptSettings
|
||||
workspace={workspace}
|
||||
setHasChanges={setHasChanges}
|
||||
hasChanges={hasChanges}
|
||||
/>
|
||||
<ChatQueryRefusalResponse
|
||||
workspace={workspace}
|
||||
|
||||
@ -179,7 +179,7 @@ export default function handleChat(
|
||||
}
|
||||
}
|
||||
|
||||
export function chatPrompt(workspace) {
|
||||
export function getWorkspaceSystemPrompt(workspace) {
|
||||
return (
|
||||
workspace?.openAiPrompt ??
|
||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed."
|
||||
|
||||
@ -120,6 +120,9 @@ export default {
|
||||
audioPreference: () => {
|
||||
return "/settings/audio-preference";
|
||||
},
|
||||
defaultSystemPrompt: () => {
|
||||
return "/settings/default-system-prompt";
|
||||
},
|
||||
embedder: {
|
||||
modelPreference: () => "/settings/embedding-preference",
|
||||
chunkingPreference: () => "/settings/text-splitter-preference",
|
||||
|
||||
@ -734,6 +734,57 @@ function systemEndpoints(app) {
|
||||
}
|
||||
}
|
||||
);
|
||||
app.get(
|
||||
"/system/default-system-prompt",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
||||
async (_, response) => {
|
||||
try {
|
||||
const defaultSystemPrompt = await SystemSettings.get({
|
||||
label: "default_system_prompt",
|
||||
});
|
||||
|
||||
response.status(200).json({
|
||||
success: true,
|
||||
defaultSystemPrompt:
|
||||
defaultSystemPrompt?.value ||
|
||||
SystemSettings.saneDefaultSystemPrompt,
|
||||
saneDefaultSystemPrompt: SystemSettings.saneDefaultSystemPrompt,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching default system prompt:", error);
|
||||
response
|
||||
.status(500)
|
||||
.json({ success: false, message: "Internal server error" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/system/default-system-prompt",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { defaultSystemPrompt } = reqBody(request);
|
||||
const { success, error } = await SystemSettings.updateSettings({
|
||||
default_system_prompt: defaultSystemPrompt,
|
||||
});
|
||||
if (!success)
|
||||
throw new Error(
|
||||
error.message || "Failed to update default system prompt."
|
||||
);
|
||||
response.status(200).json({
|
||||
success: true,
|
||||
message: "Default system prompt updated successfully.",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating default system prompt:", error);
|
||||
response.status(500).json({
|
||||
success: false,
|
||||
message: error.message || "Internal server error",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.delete(
|
||||
"/system/remove-pfp",
|
||||
|
||||
@ -17,6 +17,9 @@ function isNullOrNaN(value) {
|
||||
}
|
||||
|
||||
const SystemSettings = {
|
||||
/** A default system prompt that is used when no other system prompt is set or available to the function caller. */
|
||||
saneDefaultSystemPrompt:
|
||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.",
|
||||
protectedFields: ["multi_user_mode", "hub_api_key"],
|
||||
publicFields: [
|
||||
"footer_data",
|
||||
@ -47,6 +50,7 @@ const SystemSettings = {
|
||||
"disabled_agent_skills",
|
||||
"agent_sql_connections",
|
||||
"custom_app_name",
|
||||
"default_system_prompt",
|
||||
|
||||
// Meta page customization
|
||||
"meta_page_title",
|
||||
@ -192,6 +196,12 @@ const SystemSettings = {
|
||||
if (!apiKey) return null;
|
||||
return String(apiKey);
|
||||
},
|
||||
default_system_prompt: (prompt) => {
|
||||
if (typeof prompt !== "string" || !prompt) return null;
|
||||
if (prompt.trim() === SystemSettings.saneDefaultSystemPrompt)
|
||||
return SystemSettings.saneDefaultSystemPrompt;
|
||||
return String(prompt.trim());
|
||||
},
|
||||
},
|
||||
currentSettings: async function () {
|
||||
const { hasVectorCachedFiles } = require("../utils/files");
|
||||
|
||||
@ -6,6 +6,7 @@ const { ROLES } = require("../utils/middleware/multiUserProtected");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const { User } = require("./user");
|
||||
const { PromptHistory } = require("./promptHistory");
|
||||
const { SystemSettings } = require("./systemSettings");
|
||||
|
||||
function isNullOrNaN(value) {
|
||||
if (value === null) return true;
|
||||
@ -32,8 +33,7 @@ function isNullOrNaN(value) {
|
||||
*/
|
||||
|
||||
const Workspace = {
|
||||
defaultPrompt:
|
||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.",
|
||||
defaultPrompt: SystemSettings.saneDefaultSystemPrompt,
|
||||
|
||||
// Used for generic updates so we can validate keys in request body
|
||||
// commented fields are not writable, but are available on the db object
|
||||
@ -193,6 +193,14 @@ const Workspace = {
|
||||
slug = this.slugify(`${name}-${slugSeed}`, { lower: true });
|
||||
}
|
||||
|
||||
// Get the default system prompt
|
||||
const defaultSystemPrompt = await SystemSettings.get({
|
||||
label: "default_system_prompt",
|
||||
});
|
||||
if (!!defaultSystemPrompt?.value)
|
||||
additionalFields.openAiPrompt = defaultSystemPrompt.value;
|
||||
else additionalFields.openAiPrompt = this.defaultPrompt;
|
||||
|
||||
try {
|
||||
const workspace = await prisma.workspaces.create({
|
||||
data: {
|
||||
|
||||
@ -132,6 +132,7 @@ model workspaces {
|
||||
openAiTemp Float?
|
||||
openAiHistory Int @default(20)
|
||||
lastUpdatedAt DateTime @default(now())
|
||||
// THIS IS THE SYSTEM PROMPT FOR THE WORKSPACE
|
||||
openAiPrompt String?
|
||||
similarityThreshold Float? @default(0.25)
|
||||
chatProvider String?
|
||||
|
||||
@ -89,9 +89,9 @@ async function recentChatHistory({
|
||||
* @returns {Promise<string>} - the base prompt
|
||||
*/
|
||||
async function chatPrompt(workspace, user = null) {
|
||||
const { SystemSettings } = require("../../models/systemSettings");
|
||||
const basePrompt =
|
||||
workspace?.openAiPrompt ??
|
||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.";
|
||||
workspace?.openAiPrompt ?? SystemSettings.saneDefaultSystemPrompt;
|
||||
return await SystemPromptVariables.expandSystemPromptVariables(
|
||||
basePrompt,
|
||||
user?.id,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
const { WorkspaceChats } = require("../../../models/workspaceChats");
|
||||
const { EmbedChats } = require("../../../models/embedChats");
|
||||
const { safeJsonParse } = require("../../http");
|
||||
const { SystemSettings } = require("../../../models/systemSettings");
|
||||
|
||||
async function convertToCSV(preparedData) {
|
||||
const headers = new Set(["id", "workspace", "prompt", "response", "sent_at"]);
|
||||
@ -146,8 +147,8 @@ async function prepareChatsForExport(format = "jsonl", chatType = "workspace") {
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
chat.workspace?.openAiPrompt ||
|
||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.",
|
||||
chat.workspace?.openAiPrompt ??
|
||||
SystemSettings.saneDefaultSystemPrompt,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -223,8 +224,6 @@ async function exportChatsAsType(format = "jsonl", chatType = "workspace") {
|
||||
};
|
||||
}
|
||||
|
||||
const STANDARD_PROMPT =
|
||||
"Given the following conversation, relevant context, and a follow up question, reply with an answer to the current question the user is asking. Return only your response to the question given the above information following the users instructions as needed.";
|
||||
function buildSystemPrompt(chat, prompt = null) {
|
||||
const sources = safeJsonParse(chat.response)?.sources || [];
|
||||
const contextTexts = sources.map((source) => source.text);
|
||||
@ -237,7 +236,7 @@ function buildSystemPrompt(chat, prompt = null) {
|
||||
})
|
||||
.join("")
|
||||
: "";
|
||||
return `${prompt ?? STANDARD_PROMPT}${context}`;
|
||||
return `${prompt ?? SystemSettings.saneDefaultSystemPrompt}${context}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user