delete input prompts (#3380)

* delete input prompts

* nit

* remove vestigial test

* nit
This commit is contained in:
pablonyx
2024-12-10 13:36:40 -08:00
committed by GitHub
parent 48c10271c2
commit 878a189011
29 changed files with 68 additions and 1449 deletions

View File

@@ -0,0 +1,57 @@
"""delete_input_prompts
Revision ID: bf7a81109301
Revises: f7a894b06d02
Create Date: 2024-12-09 12:00:49.884228
"""
from alembic import op
import sqlalchemy as sa
import fastapi_users_db_sqlalchemy
# revision identifiers, used by Alembic.
revision = "bf7a81109301"
down_revision = "f7a894b06d02"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.drop_table("inputprompt__user")
op.drop_table("inputprompt")
def downgrade() -> None:
op.create_table(
"inputprompt",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("prompt", sa.String(), nullable=False),
sa.Column("content", sa.String(), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False),
sa.Column("is_public", sa.Boolean(), nullable=False),
sa.Column(
"user_id",
fastapi_users_db_sqlalchemy.generics.GUID(),
nullable=True,
),
sa.ForeignKeyConstraint(
["user_id"],
["user.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"inputprompt__user",
sa.Column("input_prompt_id", sa.Integer(), nullable=False),
sa.Column("user_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["input_prompt_id"],
["inputprompt.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["inputprompt.id"],
),
sa.PrimaryKeyConstraint("input_prompt_id", "user_id"),
)

View File

@@ -3,7 +3,6 @@ import os
PROMPTS_YAML = "./danswer/seeding/prompts.yaml"
PERSONAS_YAML = "./danswer/seeding/personas.yaml"
INPUT_PROMPT_YAML = "./danswer/seeding/input_prompts.yaml"
NUM_RETURNED_HITS = 50
# Used for LLM filtering and reranking

View File

@@ -1,202 +0,0 @@
from uuid import UUID
from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from danswer.db.models import InputPrompt
from danswer.db.models import User
from danswer.server.features.input_prompt.models import InputPromptSnapshot
from danswer.server.manage.models import UserInfo
from danswer.utils.logger import setup_logger
logger = setup_logger()
def insert_input_prompt_if_not_exists(
user: User | None,
input_prompt_id: int | None,
prompt: str,
content: str,
active: bool,
is_public: bool,
db_session: Session,
commit: bool = True,
) -> InputPrompt:
if input_prompt_id is not None:
input_prompt = (
db_session.query(InputPrompt).filter_by(id=input_prompt_id).first()
)
else:
query = db_session.query(InputPrompt).filter(InputPrompt.prompt == prompt)
if user:
query = query.filter(InputPrompt.user_id == user.id)
else:
query = query.filter(InputPrompt.user_id.is_(None))
input_prompt = query.first()
if input_prompt is None:
input_prompt = InputPrompt(
id=input_prompt_id,
prompt=prompt,
content=content,
active=active,
is_public=is_public or user is None,
user_id=user.id if user else None,
)
db_session.add(input_prompt)
if commit:
db_session.commit()
return input_prompt
def insert_input_prompt(
prompt: str,
content: str,
is_public: bool,
user: User | None,
db_session: Session,
) -> InputPrompt:
input_prompt = InputPrompt(
prompt=prompt,
content=content,
active=True,
is_public=is_public or user is None,
user_id=user.id if user is not None else None,
)
db_session.add(input_prompt)
db_session.commit()
return input_prompt
def update_input_prompt(
user: User | None,
input_prompt_id: int,
prompt: str,
content: str,
active: bool,
db_session: Session,
) -> InputPrompt:
input_prompt = db_session.scalar(
select(InputPrompt).where(InputPrompt.id == input_prompt_id)
)
if input_prompt is None:
raise ValueError(f"No input prompt with id {input_prompt_id}")
if not validate_user_prompt_authorization(user, input_prompt):
raise HTTPException(status_code=401, detail="You don't own this prompt")
input_prompt.prompt = prompt
input_prompt.content = content
input_prompt.active = active
db_session.commit()
return input_prompt
def validate_user_prompt_authorization(
user: User | None, input_prompt: InputPrompt
) -> bool:
prompt = InputPromptSnapshot.from_model(input_prompt=input_prompt)
if prompt.user_id is not None:
if user is None:
return False
user_details = UserInfo.from_model(user)
if str(user_details.id) != str(prompt.user_id):
return False
return True
def remove_public_input_prompt(input_prompt_id: int, db_session: Session) -> None:
input_prompt = db_session.scalar(
select(InputPrompt).where(InputPrompt.id == input_prompt_id)
)
if input_prompt is None:
raise ValueError(f"No input prompt with id {input_prompt_id}")
if not input_prompt.is_public:
raise HTTPException(status_code=400, detail="This prompt is not public")
db_session.delete(input_prompt)
db_session.commit()
def remove_input_prompt(
user: User | None, input_prompt_id: int, db_session: Session
) -> None:
input_prompt = db_session.scalar(
select(InputPrompt).where(InputPrompt.id == input_prompt_id)
)
if input_prompt is None:
raise ValueError(f"No input prompt with id {input_prompt_id}")
if input_prompt.is_public:
raise HTTPException(
status_code=400, detail="Cannot delete public prompts with this method"
)
if not validate_user_prompt_authorization(user, input_prompt):
raise HTTPException(status_code=401, detail="You do not own this prompt")
db_session.delete(input_prompt)
db_session.commit()
def fetch_input_prompt_by_id(
id: int, user_id: UUID | None, db_session: Session
) -> InputPrompt:
query = select(InputPrompt).where(InputPrompt.id == id)
if user_id:
query = query.where(
(InputPrompt.user_id == user_id) | (InputPrompt.user_id is None)
)
else:
# If no user_id is provided, only fetch prompts without a user_id (aka public)
query = query.where(InputPrompt.user_id == None) # noqa
result = db_session.scalar(query)
if result is None:
raise HTTPException(422, "No input prompt found")
return result
def fetch_public_input_prompts(
db_session: Session,
) -> list[InputPrompt]:
query = select(InputPrompt).where(InputPrompt.is_public)
return list(db_session.scalars(query).all())
def fetch_input_prompts_by_user(
db_session: Session,
user_id: UUID | None,
active: bool | None = None,
include_public: bool = False,
) -> list[InputPrompt]:
query = select(InputPrompt)
if user_id is not None:
if include_public:
query = query.where(
(InputPrompt.user_id == user_id) | InputPrompt.is_public
)
else:
query = query.where(InputPrompt.user_id == user_id)
elif include_public:
query = query.where(InputPrompt.is_public)
if active is not None:
query = query.where(InputPrompt.active == active)
return list(db_session.scalars(query).all())

View File

@@ -159,9 +159,6 @@ class User(SQLAlchemyBaseUserTableUUID, Base):
)
prompts: Mapped[list["Prompt"]] = relationship("Prompt", back_populates="user")
input_prompts: Mapped[list["InputPrompt"]] = relationship(
"InputPrompt", back_populates="user"
)
# Personas owned by this user
personas: Mapped[list["Persona"]] = relationship("Persona", back_populates="user")
@@ -178,31 +175,6 @@ class User(SQLAlchemyBaseUserTableUUID, Base):
)
class InputPrompt(Base):
__tablename__ = "inputprompt"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
prompt: Mapped[str] = mapped_column(String)
content: Mapped[str] = mapped_column(String)
active: Mapped[bool] = mapped_column(Boolean)
user: Mapped[User | None] = relationship("User", back_populates="input_prompts")
is_public: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
user_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="CASCADE"), nullable=True
)
class InputPrompt__User(Base):
__tablename__ = "inputprompt__user"
input_prompt_id: Mapped[int] = mapped_column(
ForeignKey("inputprompt.id"), primary_key=True
)
user_id: Mapped[UUID | None] = mapped_column(
ForeignKey("inputprompt.id"), primary_key=True
)
class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base):
pass

View File

@@ -54,10 +54,6 @@ from danswer.server.documents.document import router as document_router
from danswer.server.documents.indexing import router as indexing_router
from danswer.server.features.document_set.api import router as document_set_router
from danswer.server.features.folder.api import router as folder_router
from danswer.server.features.input_prompt.api import (
admin_router as admin_input_prompt_router,
)
from danswer.server.features.input_prompt.api import basic_router as input_prompt_router
from danswer.server.features.notifications.api import router as notification_router
from danswer.server.features.persona.api import admin_router as admin_persona_router
from danswer.server.features.persona.api import basic_router as persona_router
@@ -258,8 +254,6 @@ def get_application() -> FastAPI:
)
include_router_with_global_prefix_prepended(application, persona_router)
include_router_with_global_prefix_prepended(application, admin_persona_router)
include_router_with_global_prefix_prepended(application, input_prompt_router)
include_router_with_global_prefix_prepended(application, admin_input_prompt_router)
include_router_with_global_prefix_prepended(application, notification_router)
include_router_with_global_prefix_prepended(application, prompt_router)
include_router_with_global_prefix_prepended(application, tool_router)

View File

@@ -1,24 +0,0 @@
input_prompts:
- id: -5
prompt: "Elaborate"
content: "Elaborate on the above, give me a more in depth explanation."
active: true
is_public: true
- id: -4
prompt: "Reword"
content: "Help me rewrite the following politely and concisely for professional communication:\n"
active: true
is_public: true
- id: -3
prompt: "Email"
content: "Write a professional email for me including a subject line, signature, etc. Template the parts that need editing with [ ]. The email should cover the following points:\n"
active: true
is_public: true
- id: -2
prompt: "Debug"
content: "Provide step-by-step troubleshooting instructions for the following issue:\n"
active: true
is_public: true

View File

@@ -1,13 +1,11 @@
import yaml
from sqlalchemy.orm import Session
from danswer.configs.chat_configs import INPUT_PROMPT_YAML
from danswer.configs.chat_configs import MAX_CHUNKS_FED_TO_CHAT
from danswer.configs.chat_configs import PERSONAS_YAML
from danswer.configs.chat_configs import PROMPTS_YAML
from danswer.context.search.enums import RecencyBiasSetting
from danswer.db.document_set import get_or_create_document_set_by_name
from danswer.db.input_prompt import insert_input_prompt_if_not_exists
from danswer.db.models import DocumentSet as DocumentSetDBModel
from danswer.db.models import Persona
from danswer.db.models import Prompt as PromptDBModel
@@ -140,35 +138,10 @@ def load_personas_from_yaml(
)
def load_input_prompts_from_yaml(
db_session: Session, input_prompts_yaml: str = INPUT_PROMPT_YAML
) -> None:
with open(input_prompts_yaml, "r") as file:
data = yaml.safe_load(file)
all_input_prompts = data.get("input_prompts", [])
for input_prompt in all_input_prompts:
# If these prompts are deleted (which is a hard delete in the DB), on server startup
# they will be recreated, but the user can always just deactivate them, just a light inconvenience
insert_input_prompt_if_not_exists(
user=None,
input_prompt_id=input_prompt.get("id"),
prompt=input_prompt["prompt"],
content=input_prompt["content"],
is_public=input_prompt["is_public"],
active=input_prompt.get("active", True),
db_session=db_session,
commit=True,
)
def load_chat_yamls(
db_session: Session,
prompt_yaml: str = PROMPTS_YAML,
personas_yaml: str = PERSONAS_YAML,
input_prompts_yaml: str = INPUT_PROMPT_YAML,
) -> None:
load_prompts_from_yaml(db_session, prompt_yaml)
load_personas_from_yaml(db_session, personas_yaml)
load_input_prompts_from_yaml(db_session, input_prompts_yaml)

View File

@@ -1,134 +0,0 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from sqlalchemy.orm import Session
from danswer.auth.users import current_admin_user
from danswer.auth.users import current_user
from danswer.db.engine import get_session
from danswer.db.input_prompt import fetch_input_prompt_by_id
from danswer.db.input_prompt import fetch_input_prompts_by_user
from danswer.db.input_prompt import fetch_public_input_prompts
from danswer.db.input_prompt import insert_input_prompt
from danswer.db.input_prompt import remove_input_prompt
from danswer.db.input_prompt import remove_public_input_prompt
from danswer.db.input_prompt import update_input_prompt
from danswer.db.models import User
from danswer.server.features.input_prompt.models import CreateInputPromptRequest
from danswer.server.features.input_prompt.models import InputPromptSnapshot
from danswer.server.features.input_prompt.models import UpdateInputPromptRequest
from danswer.utils.logger import setup_logger
logger = setup_logger()
basic_router = APIRouter(prefix="/input_prompt")
admin_router = APIRouter(prefix="/admin/input_prompt")
@basic_router.get("")
def list_input_prompts(
user: User | None = Depends(current_user),
include_public: bool = False,
db_session: Session = Depends(get_session),
) -> list[InputPromptSnapshot]:
user_prompts = fetch_input_prompts_by_user(
user_id=user.id if user is not None else None,
db_session=db_session,
include_public=include_public,
)
return [InputPromptSnapshot.from_model(prompt) for prompt in user_prompts]
@basic_router.get("/{input_prompt_id}")
def get_input_prompt(
input_prompt_id: int,
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> InputPromptSnapshot:
input_prompt = fetch_input_prompt_by_id(
id=input_prompt_id,
user_id=user.id if user is not None else None,
db_session=db_session,
)
return InputPromptSnapshot.from_model(input_prompt=input_prompt)
@basic_router.post("")
def create_input_prompt(
create_input_prompt_request: CreateInputPromptRequest,
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> InputPromptSnapshot:
input_prompt = insert_input_prompt(
prompt=create_input_prompt_request.prompt,
content=create_input_prompt_request.content,
is_public=create_input_prompt_request.is_public,
user=user,
db_session=db_session,
)
return InputPromptSnapshot.from_model(input_prompt)
@basic_router.patch("/{input_prompt_id}")
def patch_input_prompt(
input_prompt_id: int,
update_input_prompt_request: UpdateInputPromptRequest,
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> InputPromptSnapshot:
try:
updated_input_prompt = update_input_prompt(
user=user,
input_prompt_id=input_prompt_id,
prompt=update_input_prompt_request.prompt,
content=update_input_prompt_request.content,
active=update_input_prompt_request.active,
db_session=db_session,
)
except ValueError as e:
error_msg = "Error occurred while updated input prompt"
logger.warn(f"{error_msg}. Stack trace: {e}")
raise HTTPException(status_code=404, detail=error_msg)
return InputPromptSnapshot.from_model(updated_input_prompt)
@basic_router.delete("/{input_prompt_id}")
def delete_input_prompt(
input_prompt_id: int,
user: User | None = Depends(current_user),
db_session: Session = Depends(get_session),
) -> None:
try:
remove_input_prompt(user, input_prompt_id, db_session)
except ValueError as e:
error_msg = "Error occurred while deleting input prompt"
logger.warn(f"{error_msg}. Stack trace: {e}")
raise HTTPException(status_code=404, detail=error_msg)
@admin_router.delete("/{input_prompt_id}")
def delete_public_input_prompt(
input_prompt_id: int,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> None:
try:
remove_public_input_prompt(input_prompt_id, db_session)
except ValueError as e:
error_msg = "Error occurred while deleting input prompt"
logger.warn(f"{error_msg}. Stack trace: {e}")
raise HTTPException(status_code=404, detail=error_msg)
@admin_router.get("")
def list_public_input_prompts(
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> list[InputPromptSnapshot]:
user_prompts = fetch_public_input_prompts(
db_session=db_session,
)
return [InputPromptSnapshot.from_model(prompt) for prompt in user_prompts]

View File

@@ -1,47 +0,0 @@
from uuid import UUID
from pydantic import BaseModel
from danswer.db.models import InputPrompt
from danswer.utils.logger import setup_logger
logger = setup_logger()
class CreateInputPromptRequest(BaseModel):
prompt: str
content: str
is_public: bool
class UpdateInputPromptRequest(BaseModel):
prompt: str
content: str
active: bool
class InputPromptResponse(BaseModel):
id: int
prompt: str
content: str
active: bool
class InputPromptSnapshot(BaseModel):
id: int
prompt: str
content: str
active: bool
user_id: UUID | None
is_public: bool
@classmethod
def from_model(cls, input_prompt: InputPrompt) -> "InputPromptSnapshot":
return InputPromptSnapshot(
id=input_prompt.id,
prompt=input_prompt.prompt,
content=input_prompt.content,
active=input_prompt.active,
user_id=input_prompt.user_id,
is_public=input_prompt.is_public,
)

View File

@@ -27,13 +27,6 @@ def test_limited(reset: None) -> None:
)
assert response.status_code == 200
# test basic endpoints
response = requests.get(
f"{API_SERVER_URL}/input_prompt",
headers=api_key.headers,
)
assert response.status_code == 403
# test admin endpoints
response = requests.get(
f"{API_SERVER_URL}/admin/api-key",

View File

@@ -1,46 +0,0 @@
import useSWR from "swr";
import { InputPrompt } from "./interfaces";
const fetcher = (url: string) => fetch(url).then((res) => res.json());
export const useAdminInputPrompts = () => {
const { data, error, mutate } = useSWR<InputPrompt[]>(
`/api/admin/input_prompt`,
fetcher
);
return {
data,
error,
isLoading: !error && !data,
refreshInputPrompts: mutate,
};
};
export const useInputPrompts = (includePublic: boolean = false) => {
const { data, error, mutate } = useSWR<InputPrompt[]>(
`/api/input_prompt${includePublic ? "?include_public=true" : ""}`,
fetcher
);
return {
data,
error,
isLoading: !error && !data,
refreshInputPrompts: mutate,
};
};
export const useInputPrompt = (id: number) => {
const { data, error, mutate } = useSWR<InputPrompt>(
`/api/input_prompt/${id}`,
fetcher
);
return {
data,
error,
isLoading: !error && !data,
refreshInputPrompt: mutate,
};
};

View File

@@ -1,31 +0,0 @@
export interface InputPrompt {
id: number;
prompt: string;
content: string;
active: boolean;
is_public: string;
}
export interface EditPromptModalProps {
onClose: () => void;
promptId: number;
editInputPrompt: (
promptId: number,
values: CreateInputPromptRequest
) => Promise<void>;
}
export interface CreateInputPromptRequest {
prompt: string;
content: string;
}
export interface AddPromptModalProps {
onClose: () => void;
onSubmit: (promptData: CreateInputPromptRequest) => void;
}
export interface PromptData {
id: number;
prompt: string;
content: string;
}

View File

@@ -1,69 +0,0 @@
import React from "react";
import { Formik, Form } from "formik";
import * as Yup from "yup";
import { Button } from "@/components/ui/button";
import { BookstackIcon } from "@/components/icons/icons";
import { AddPromptModalProps } from "../interfaces";
import { TextFormField } from "@/components/admin/connectors/Field";
import { Modal } from "@/components/Modal";
const AddPromptSchema = Yup.object().shape({
title: Yup.string().required("Title is required"),
prompt: Yup.string().required("Prompt is required"),
});
const AddPromptModal = ({ onClose, onSubmit }: AddPromptModalProps) => {
return (
<Modal onOutsideClick={onClose} width="w-full max-w-3xl">
<Formik
initialValues={{
title: "",
prompt: "",
}}
validationSchema={AddPromptSchema}
onSubmit={(values, { setSubmitting }) => {
onSubmit({
prompt: values.title,
content: values.prompt,
});
setSubmitting(false);
onClose();
}}
>
{({ isSubmitting, setFieldValue }) => (
<Form>
<h2 className="w-full text-2xl gap-x-2 text-emphasis font-bold mb-3 flex items-center">
<BookstackIcon size={20} />
Add prompt
</h2>
<TextFormField
label="Title"
name="title"
placeholder="Title (e.g. 'Reword')"
/>
<TextFormField
isTextArea
label="Prompt"
name="prompt"
placeholder="Enter a prompt (e.g. 'help me rewrite the following politely and concisely for professional communication')"
/>
<Button
type="submit"
className="w-full"
disabled={isSubmitting}
variant="submit"
>
Add prompt
</Button>
</Form>
)}
</Formik>
</Modal>
);
};
export default AddPromptModal;

View File

@@ -1,138 +0,0 @@
import React from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
import { Modal } from "@/components/Modal";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { useInputPrompt } from "../hooks";
import { EditPromptModalProps } from "../interfaces";
const EditPromptSchema = Yup.object().shape({
prompt: Yup.string().required("Title is required"),
content: Yup.string().required("Content is required"),
active: Yup.boolean(),
});
const EditPromptModal = ({
onClose,
promptId,
editInputPrompt,
}: EditPromptModalProps) => {
const {
data: promptData,
error,
refreshInputPrompt,
} = useInputPrompt(promptId);
if (error)
return (
<Modal onOutsideClick={onClose} width="max-w-xl">
<p>Failed to load prompt data</p>
</Modal>
);
if (!promptData)
return (
<Modal onOutsideClick={onClose} width="w-full max-w-xl">
<p>Loading...</p>
</Modal>
);
return (
<Modal onOutsideClick={onClose} width="w-full max-w-xl">
<Formik
initialValues={{
prompt: promptData.prompt,
content: promptData.content,
active: promptData.active,
}}
validationSchema={EditPromptSchema}
onSubmit={(values) => {
editInputPrompt(promptId, values);
refreshInputPrompt();
}}
>
{({ isSubmitting, values }) => (
<Form className="items-stretch">
<h2 className="text-2xl text-emphasis font-bold mb-3 flex items-center">
<svg
className="w-6 h-6 mr-2"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
</svg>
Edit prompt
</h2>
<div className="space-y-4">
<div>
<label
htmlFor="prompt"
className="block text-sm font-medium mb-1"
>
Title
</label>
<Field
as={Textarea}
id="prompt"
name="prompt"
placeholder="Title (e.g. 'Draft email')"
/>
<ErrorMessage
name="prompt"
component="div"
className="text-red-500 text-sm mt-1"
/>
</div>
<div>
<label
htmlFor="content"
className="block text-sm font-medium mb-1"
>
Content
</label>
<Field
as={Textarea}
id="content"
name="content"
placeholder="Enter prompt content (e.g. 'Write a professional-sounding email about the following content')"
rows={4}
/>
<ErrorMessage
name="content"
component="div"
className="text-red-500 text-sm mt-1"
/>
</div>
<div>
<label className="flex items-center">
<Field type="checkbox" name="active" className="mr-2" />
Active prompt
</label>
</div>
</div>
<div className="mt-6">
<Button
type="submit"
disabled={
isSubmitting ||
(values.prompt === promptData.prompt &&
values.content === promptData.content &&
values.active === promptData.active)
}
>
{isSubmitting ? "Updating..." : "Update prompt"}
</Button>
</div>
</Form>
)}
</Formik>
</Modal>
);
};
export default EditPromptModal;

View File

@@ -1,32 +0,0 @@
"use client";
import { AdminPageTitle } from "@/components/admin/Title";
import { ClosedBookIcon } from "@/components/icons/icons";
import { useAdminInputPrompts } from "./hooks";
import { PromptSection } from "./promptSection";
const Page = () => {
const {
data: promptLibrary,
error: promptLibraryError,
isLoading: promptLibraryIsLoading,
refreshInputPrompts: refreshPrompts,
} = useAdminInputPrompts();
return (
<div className="container mx-auto">
<AdminPageTitle
icon={<ClosedBookIcon size={32} />}
title="Prompt Library"
/>
<PromptSection
promptLibrary={promptLibrary || []}
isLoading={promptLibraryIsLoading}
error={promptLibraryError}
refreshPrompts={refreshPrompts}
isPublic={true}
/>
</div>
);
};
export default Page;

View File

@@ -1,249 +0,0 @@
"use client";
import { EditIcon, TrashIcon } from "@/components/icons/icons";
import { PopupSpec } from "@/components/admin/connectors/Popup";
import { MagnifyingGlass } from "@phosphor-icons/react";
import { useState } from "react";
import {
Table,
TableHead,
TableRow,
TableBody,
TableCell,
} from "@/components/ui/table";
import { FilterDropdown } from "@/components/search/filtering/FilterDropdown";
import { FiTag } from "react-icons/fi";
import { PageSelector } from "@/components/PageSelector";
import { InputPrompt } from "./interfaces";
import { DeleteEntityModal } from "@/components/modals/DeleteEntityModal";
import { TableHeader } from "@/components/ui/table";
const CategoryBubble = ({
name,
onDelete,
}: {
name: string;
onDelete?: () => void;
}) => (
<span
className={`
inline-block
px-2
py-1
mr-1
mb-1
text-xs
font-semibold
text-emphasis
bg-hover
rounded-full
items-center
w-fit
${onDelete ? "cursor-pointer" : ""}
`}
onClick={onDelete}
>
{name}
{onDelete && (
<button
className="ml-1 text-subtle hover:text-emphasis"
aria-label="Remove category"
>
&times;
</button>
)}
</span>
);
const NUM_RESULTS_PER_PAGE = 10;
export const PromptLibraryTable = ({
promptLibrary,
refresh,
setPopup,
handleEdit,
isPublic,
}: {
promptLibrary: InputPrompt[];
refresh: () => void;
setPopup: (popup: PopupSpec | null) => void;
handleEdit: (promptId: number) => void;
isPublic: boolean;
}) => {
const [query, setQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [selectedStatus, setSelectedStatus] = useState<string[]>([]);
const columns = [
{ name: "Prompt", key: "prompt" },
{ name: "Content", key: "content" },
{ name: "Status", key: "status" },
{ name: "", key: "edit" },
{ name: "", key: "delete" },
];
const filteredPromptLibrary = promptLibrary.filter((item) => {
const cleanedQuery = query.toLowerCase();
const searchMatch =
item.prompt.toLowerCase().includes(cleanedQuery) ||
item.content.toLowerCase().includes(cleanedQuery);
const statusMatch =
selectedStatus.length === 0 ||
(selectedStatus.includes("Active") && item.active) ||
(selectedStatus.includes("Inactive") && !item.active);
return searchMatch && statusMatch;
});
const totalPages = Math.ceil(
filteredPromptLibrary.length / NUM_RESULTS_PER_PAGE
);
const startIndex = (currentPage - 1) * NUM_RESULTS_PER_PAGE;
const endIndex = startIndex + NUM_RESULTS_PER_PAGE;
const paginatedPromptLibrary = filteredPromptLibrary.slice(
startIndex,
endIndex
);
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
const handleDelete = async (id: number) => {
const response = await fetch(
`/api${isPublic ? "/admin" : ""}/input_prompt/${id}`,
{
method: "DELETE",
}
);
if (!response.ok) {
setPopup({ message: "Failed to delete input prompt", type: "error" });
}
refresh();
setConfirmDeletionId(null);
};
const handleStatusSelect = (status: string) => {
setSelectedStatus((prev) => {
if (prev.includes(status)) {
return prev.filter((s) => s !== status);
}
return [...prev, status];
});
};
const [confirmDeletionId, setConfirmDeletionId] = useState<number | null>(
null
);
return (
<div className="justify-center py-2">
{confirmDeletionId != null && (
<DeleteEntityModal
onClose={() => setConfirmDeletionId(null)}
onSubmit={() => handleDelete(confirmDeletionId)}
entityType="prompt"
entityName={
paginatedPromptLibrary.find(
(prompt) => prompt.id === confirmDeletionId
)?.prompt ?? ""
}
/>
)}
<div className="flex items-center w-full border-2 border-border rounded-lg px-4 py-2 focus-within:border-accent">
<MagnifyingGlass />
<input
className="flex-grow ml-2 bg-transparent outline-none placeholder-subtle"
placeholder="Find prompts..."
value={query}
onChange={(event) => {
setQuery(event.target.value);
setCurrentPage(1);
}}
/>
</div>
<div className="my-4 border-b border-border">
<FilterDropdown
options={[
{ key: "Active", display: "Active" },
{ key: "Inactive", display: "Inactive" },
]}
selected={selectedStatus}
handleSelect={(option) => handleStatusSelect(option.key)}
icon={<FiTag size={16} />}
defaultDisplay="All Statuses"
/>
<div className="flex flex-col items-stretch w-full flex-wrap pb-4 mt-3">
{selectedStatus.map((status) => (
<CategoryBubble
key={status}
name={status}
onDelete={() => handleStatusSelect(status)}
/>
))}
</div>
</div>
<div className="mx-auto overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead key={column.key}>{column.name}</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{paginatedPromptLibrary.length > 0 ? (
paginatedPromptLibrary
.filter((prompt) => !(!isPublic && prompt.is_public))
.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.prompt}</TableCell>
<TableCell
className="
max-w-xs
overflow-hidden
text-ellipsis
break-words
"
>
{item.content}
</TableCell>
<TableCell>{item.active ? "Active" : "Inactive"}</TableCell>
<TableCell>
<button
className="cursor-pointer"
onClick={() => setConfirmDeletionId(item.id)}
>
<TrashIcon size={20} />
</button>
</TableCell>
<TableCell>
<button onClick={() => handleEdit(item.id)}>
<EditIcon size={12} />
</button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6}>No matching prompts found...</TableCell>
</TableRow>
)}
</TableBody>
</Table>
{paginatedPromptLibrary.length > 0 && (
<div className="mt-4 flex justify-center">
<PageSelector
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
shouldScroll={true}
/>
</div>
)}
</div>
</div>
);
};

View File

@@ -1,150 +0,0 @@
"use client";
import { usePopup } from "@/components/admin/connectors/Popup";
import { ThreeDotsLoader } from "@/components/Loading";
import { ErrorCallout } from "@/components/ErrorCallout";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import Text from "@/components/ui/text";
import { useState } from "react";
import AddPromptModal from "./modals/AddPromptModal";
import EditPromptModal from "./modals/EditPromptModal";
import { PromptLibraryTable } from "./promptLibrary";
import { CreateInputPromptRequest, InputPrompt } from "./interfaces";
export const PromptSection = ({
promptLibrary,
isLoading,
error,
refreshPrompts,
centering = false,
isPublic,
}: {
promptLibrary: InputPrompt[];
isLoading: boolean;
error: any;
refreshPrompts: () => void;
centering?: boolean;
isPublic: boolean;
}) => {
const { popup, setPopup } = usePopup();
const [newPrompt, setNewPrompt] = useState(false);
const [newPromptId, setNewPromptId] = useState<number | null>(null);
const createInputPrompt = async (
promptData: CreateInputPromptRequest
): Promise<InputPrompt> => {
const response = await fetch("/api/input_prompt", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ...promptData, is_public: isPublic }),
});
if (!response.ok) {
setPopup({ message: "Failed to create input prompt", type: "error" });
}
refreshPrompts();
return response.json();
};
const editInputPrompt = async (
promptId: number,
values: CreateInputPromptRequest
) => {
try {
const response = await fetch(`/api/input_prompt/${promptId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
if (!response.ok) {
setPopup({ message: "Failed to update prompt!", type: "error" });
}
setNewPromptId(null);
refreshPrompts();
} catch (err) {
setPopup({ message: `Failed to update prompt: ${err}`, type: "error" });
}
};
if (isLoading) {
return <ThreeDotsLoader />;
}
if (error || !promptLibrary) {
return (
<ErrorCallout
errorTitle="Error loading standard answers"
errorMsg={error?.info?.message || error?.message?.info?.detail}
/>
);
}
const handleEdit = (promptId: number) => {
setNewPromptId(promptId);
};
return (
<div
className={`w-full ${
centering ? "flex-col flex justify-center" : ""
} mb-8`}
>
{popup}
{newPrompt && (
<AddPromptModal
onSubmit={createInputPrompt}
onClose={() => setNewPrompt(false)}
/>
)}
{newPromptId && (
<EditPromptModal
promptId={newPromptId}
editInputPrompt={editInputPrompt}
onClose={() => setNewPromptId(null)}
/>
)}
<div className={centering ? "max-w-sm mx-auto" : ""}>
<Text className="mb-2 my-auto">
Create prompts that can be accessed with the <i>`/`</i> shortcut in
Danswer Chat.{" "}
{isPublic
? "Prompts created here will be accessible to all users."
: "Prompts created here will be available only to you."}
</Text>
</div>
<div className="mb-2"></div>
<Button
onClick={() => setNewPrompt(true)}
className={centering ? "mx-auto" : ""}
variant="navigate"
size="sm"
>
New Prompt
</Button>
<Separator />
<div>
<PromptLibraryTable
isPublic={isPublic}
promptLibrary={promptLibrary}
setPopup={setPopup}
refresh={refreshPrompts}
handleEdit={handleEdit}
/>
</div>
</div>
);
};

View File

@@ -1,51 +0,0 @@
"use client";
import SidebarWrapper from "../SidebarWrapper";
import { ChatSession } from "@/app/chat/interfaces";
import { Folder } from "@/app/chat/folders/interfaces";
import { User } from "@/lib/types";
import { AssistantsPageTitle } from "../AssistantsPageTitle";
import { useInputPrompts } from "@/app/admin/prompt-library/hooks";
import { PromptSection } from "@/app/admin/prompt-library/promptSection";
export default function WrappedPrompts({
chatSessions,
initiallyToggled,
folders,
openedFolders,
}: {
chatSessions: ChatSession[];
folders: Folder[];
initiallyToggled: boolean;
openedFolders?: { [key: number]: boolean };
}) {
const {
data: promptLibrary,
error: promptLibraryError,
isLoading: promptLibraryIsLoading,
refreshInputPrompts: refreshPrompts,
} = useInputPrompts(false);
return (
<SidebarWrapper
size="lg"
page="chat"
initiallyToggled={initiallyToggled}
chatSessions={chatSessions}
folders={folders}
openedFolders={openedFolders}
>
<div className="mx-auto w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar">
<AssistantsPageTitle>Prompt Gallery</AssistantsPageTitle>
<PromptSection
promptLibrary={promptLibrary || []}
isLoading={promptLibraryIsLoading}
error={promptLibraryError}
refreshPrompts={refreshPrompts}
isPublic={false}
centering
/>
</div>
</SidebarWrapper>
);
}

View File

@@ -135,7 +135,6 @@ export function ChatPage({
llmProviders,
folders,
openedFolders,
userInputPrompts,
defaultAssistantId,
shouldShowWelcomeModal,
refreshChatSessions,
@@ -2744,7 +2743,6 @@ export function ChatPage({
chatState={currentSessionChatState}
stopGenerating={stopGenerating}
openModelSettings={() => setSettingsToggled(true)}
inputPrompts={userInputPrompts}
showDocs={() => setDocumentSelection(true)}
selectedDocuments={selectedDocuments}
// assistant stuff

View File

@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useRef, useState } from "react";
import { FiPlusCircle, FiPlus, FiInfo, FiX, FiSearch } from "react-icons/fi";
import { ChatInputOption } from "./ChatInputOption";
import { Persona } from "@/app/admin/assistants/interfaces";
import { InputPrompt } from "@/app/admin/prompt-library/interfaces";
import { FilterManager, LlmOverrideManager } from "@/lib/hooks";
import { SelectedFilterDisplay } from "./SelectedFilterDisplay";
import { useChatContext } from "@/components/context/ChatContext";
@@ -58,7 +58,6 @@ interface ChatInputBarProps {
llmOverrideManager: LlmOverrideManager;
chatState: ChatState;
alternativeAssistant: Persona | null;
inputPrompts: InputPrompt[];
// assistants
selectedAssistant: Persona;
setSelectedAssistant: (assistant: Persona) => void;
@@ -98,7 +97,6 @@ export function ChatInputBar({
textAreaRef,
alternativeAssistant,
chatSessionId,
inputPrompts,
toggleFilters,
}: ChatInputBarProps) {
useEffect(() => {
@@ -137,7 +135,6 @@ export function ChatInputBar({
const suggestionsRef = useRef<HTMLDivElement | null>(null);
const [showSuggestions, setShowSuggestions] = useState(false);
const [showPrompts, setShowPrompts] = useState(false);
const interactionsRef = useRef<HTMLDivElement | null>(null);
@@ -146,19 +143,6 @@ export function ChatInputBar({
setTabbingIconIndex(0);
};
const hidePrompts = () => {
setTimeout(() => {
setShowPrompts(false);
}, 50);
setTabbingIconIndex(0);
};
const updateInputPrompt = (prompt: InputPrompt) => {
hidePrompts();
setMessage(`${prompt.content}`);
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
@@ -168,7 +152,6 @@ export function ChatInputBar({
!interactionsRef.current.contains(event.target as Node))
) {
hideSuggestions();
hidePrompts();
}
};
document.addEventListener("mousedown", handleClickOutside);
@@ -198,24 +181,10 @@ export function ChatInputBar({
}
};
const handlePromptInput = (text: string) => {
if (!text.startsWith("/")) {
hidePrompts();
} else {
const promptMatch = text.match(/(?:\s|^)\/(\w*)$/);
if (promptMatch) {
setShowPrompts(true);
} else {
hidePrompts();
}
}
};
const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const text = event.target.value;
setMessage(text);
handleAssistantInput(text);
handlePromptInput(text);
};
const assistantTagOptions = assistantOptions.filter((assistant) =>
@@ -227,40 +196,18 @@ export function ChatInputBar({
)
);
const filteredPrompts = inputPrompts.filter(
(prompt) =>
prompt.active &&
prompt.prompt.toLowerCase().startsWith(
message
.slice(message.lastIndexOf("/") + 1)
.split(/\s/)[0]
.toLowerCase()
)
);
const [tabbingIconIndex, setTabbingIconIndex] = useState(0);
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
((showSuggestions && assistantTagOptions.length > 0) || showPrompts) &&
showSuggestions &&
assistantTagOptions.length > 0 &&
(e.key === "Tab" || e.key == "Enter")
) {
e.preventDefault();
if (
(tabbingIconIndex == assistantTagOptions.length && showSuggestions) ||
(tabbingIconIndex == filteredPrompts.length && showPrompts)
) {
if (showPrompts) {
window.open("/prompts", "_self");
} else {
if (tabbingIconIndex == assistantTagOptions.length && showSuggestions) {
window.open("/assistants/new", "_self");
}
} else {
if (showPrompts) {
const uppity =
filteredPrompts[tabbingIconIndex >= 0 ? tabbingIconIndex : 0];
updateInputPrompt(uppity);
} else {
const option =
assistantTagOptions[tabbingIconIndex >= 0 ? tabbingIconIndex : 0];
@@ -268,8 +215,7 @@ export function ChatInputBar({
updatedTaggedAssistant(option);
}
}
}
if (!showPrompts && !showSuggestions) {
if (!showSuggestions) {
return;
}
@@ -277,10 +223,7 @@ export function ChatInputBar({
e.preventDefault();
setTabbingIconIndex((tabbingIconIndex) =>
Math.min(
tabbingIconIndex + 1,
showPrompts ? filteredPrompts.length : assistantTagOptions.length
)
Math.min(tabbingIconIndex + 1, assistantTagOptions.length)
);
} else if (e.key === "ArrowUp") {
e.preventDefault();
@@ -341,48 +284,6 @@ export function ChatInputBar({
</div>
)}
{showPrompts && (
<div
ref={suggestionsRef}
className="text-sm absolute inset-x-0 top-0 w-full transform -translate-y-full"
>
<div className="rounded-lg py-1.5 bg-white border border-border-medium overflow-hidden shadow-lg mx-2 px-1.5 mt-2 rounded z-10">
{filteredPrompts.map(
(currentPrompt: InputPrompt, index: number) => (
<button
key={index}
className={`px-2 ${
tabbingIconIndex == index && "bg-hover"
} rounded content-start flex gap-x-1 py-1.5 w-full hover:bg-hover cursor-pointer`}
onClick={() => {
updateInputPrompt(currentPrompt);
}}
>
<p className="font-bold">{currentPrompt.prompt}:</p>
<p className="text-left flex-grow mr-auto line-clamp-1">
{currentPrompt.id == selectedAssistant.id &&
"(default) "}
{currentPrompt.content?.trim()}
</p>
</button>
)
)}
<a
key={filteredPrompts.length}
target="_self"
className={`${
tabbingIconIndex == filteredPrompts.length && "bg-hover"
} px-3 flex gap-x-1 py-2 w-full items-center hover:bg-hover-light cursor-pointer"`}
href="/prompts"
>
<FiPlus size={17} />
<p>Create a new prompt</p>
</a>
</div>
</div>
)}
{/* <div>
<SelectedFilterDisplay filterManager={filterManager} />
</div> */}
@@ -534,7 +435,6 @@ export function ChatInputBar({
onKeyDown={(event) => {
if (
event.key === "Enter" &&
!showPrompts &&
!showSuggestions &&
!event.shiftKey &&
!(event.nativeEvent as any).isComposing

View File

@@ -31,7 +31,6 @@ export default async function Page(props: {
openedFolders,
defaultAssistantId,
shouldShowWelcomeModal,
userInputPrompts,
ccPairs,
} = data;
@@ -53,7 +52,6 @@ export default async function Page(props: {
llmProviders,
folders,
openedFolders,
userInputPrompts,
shouldShowWelcomeModal,
defaultAssistantId,
}}

View File

@@ -163,15 +163,6 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
Manage Assistants
</p>
</Link>
<Link
href="/prompts"
className="w-full p-2 bg-white border-border border rounded items-center hover:bg-background-history-sidebar-button-hover cursor-pointer transition-all duration-150 flex gap-x-2"
>
<ClosedBookIcon className="h-4 w-4 my-auto text-text-history-sidebar-button" />
<p className="my-auto flex items-center text-sm ">
Manage Prompts
</p>
</Link>
</div>
)}
<div className="border-b border-divider-history-sidebar-bar pb-4 mx-3" />

View File

@@ -1,28 +0,0 @@
import { fetchChatData } from "@/lib/chat/fetchChatData";
import { unstable_noStore as noStore } from "next/cache";
import { redirect } from "next/navigation";
import WrappedPrompts from "../assistants/mine/WrappedInputPrompts";
export default async function GalleryPage(props: {
searchParams: Promise<{ [key: string]: string }>;
}) {
const searchParams = await props.searchParams;
noStore();
const data = await fetchChatData(searchParams);
if ("redirect" in data) {
redirect(data.redirect);
}
const { chatSessions, folders, openedFolders, toggleSidebar } = data;
return (
<WrappedPrompts
initiallyToggled={toggleSidebar}
chatSessions={chatSessions}
folders={folders}
openedFolders={openedFolders}
/>
);
}

View File

@@ -169,18 +169,6 @@ export function ClientLayout({
),
link: "/admin/tools",
},
{
name: (
<div className="flex">
<ClosedBookIcon
className="text-icon-settings-sidebar"
size={18}
/>
<div className="ml-1">Prompt Library</div>
</div>
),
link: "/admin/prompt-library",
},
]
: []),
...(enableEnterprise

View File

@@ -9,11 +9,8 @@ import {
ValidSources,
} from "@/lib/types";
import { ChatSession } from "@/app/chat/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
import { Folder } from "@/app/chat/folders/interfaces";
import { InputPrompt } from "@/app/admin/prompt-library/interfaces";
import { personaComparator } from "@/app/admin/assistants/lib";
interface ChatContextProps {
chatSessions: ChatSession[];
@@ -26,7 +23,6 @@ interface ChatContextProps {
llmProviders: LLMProviderDescriptor[];
folders: Folder[];
openedFolders: Record<string, boolean>;
userInputPrompts: InputPrompt[];
shouldShowWelcomeModal?: boolean;
shouldDisplaySourcesIncompleteModal?: boolean;
defaultAssistantId?: number;

View File

@@ -13,7 +13,6 @@ import {
} from "@/lib/types";
import { ChatSession } from "@/app/chat/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { InputPrompt } from "@/app/admin/prompt-library/interfaces";
import { FullEmbeddingModelResponse } from "@/components/embedding/interfaces";
import { Settings } from "@/app/admin/settings/interfaces";
import { fetchLLMProvidersSS } from "@/lib/llm/fetchLLMs";
@@ -42,7 +41,6 @@ interface FetchChatDataResult {
toggleSidebar: boolean;
finalDocumentSidebarInitialWidth?: number;
shouldShowWelcomeModal: boolean;
userInputPrompts: InputPrompt[];
}
export async function fetchChatData(searchParams: {
@@ -58,7 +56,6 @@ export async function fetchChatData(searchParams: {
fetchSS("/query/valid-tags"),
fetchLLMProvidersSS(),
fetchSS("/folder"),
fetchSS("/input_prompt?include_public=true"),
];
let results: (
@@ -87,7 +84,6 @@ export async function fetchChatData(searchParams: {
const tagsResponse = results[5] as Response | null;
const llmProviders = (results[6] || []) as LLMProviderDescriptor[];
const foldersResponse = results[7] as Response | null;
const userInputPromptsResponse = results[8] as Response | null;
const authDisabled = authTypeMetadata?.authType === "disabled";
if (!authDisabled && !user) {
@@ -142,15 +138,6 @@ export async function fetchChatData(searchParams: {
);
}
let userInputPrompts: InputPrompt[] = [];
if (userInputPromptsResponse?.ok) {
userInputPrompts = await userInputPromptsResponse.json();
} else {
console.log(
`Failed to fetch user input prompts - ${userInputPromptsResponse?.status}`
);
}
let tags: Tag[] = [];
if (tagsResponse?.ok) {
tags = (await tagsResponse.json()).tags;
@@ -212,6 +199,5 @@ export async function fetchChatData(searchParams: {
finalDocumentSidebarInitialWidth,
toggleSidebar,
shouldShowWelcomeModal,
userInputPrompts,
};
}

View File

@@ -13,7 +13,6 @@ import {
} from "@/lib/types";
import { ChatSession } from "@/app/chat/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { InputPrompt } from "@/app/admin/prompt-library/interfaces";
import { fetchLLMProvidersSS } from "@/lib/llm/fetchLLMs";
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
import { Folder } from "@/app/chat/folders/interfaces";
@@ -44,7 +43,6 @@ interface FetchChatDataResult {
finalDocumentSidebarInitialWidth?: number;
shouldShowWelcomeModal?: boolean;
shouldDisplaySourcesIncompleteModal?: boolean;
userInputPrompts?: InputPrompt[];
}
type FetchOption =
@@ -55,8 +53,7 @@ type FetchOption =
| "assistants"
| "tags"
| "llmProviders"
| "folders"
| "userInputPrompts";
| "folders";
/*
NOTE: currently unused, but leaving here for future use.
@@ -76,7 +73,6 @@ export async function fetchSomeChatData(
tags: () => fetchSS("/query/valid-tags"),
llmProviders: fetchLLMProvidersSS,
folders: () => fetchSS("/folder"),
userInputPrompts: () => fetchSS("/input_prompt?include_public=true"),
};
// Always fetch auth type metadata
@@ -152,11 +148,6 @@ export async function fetchSomeChatData(
? ((await result.json()) as { folders: Folder[] }).folders
: [];
break;
case "userInputPrompts":
result.userInputPrompts = result?.ok
? ((await result.json()) as InputPrompt[])
: [];
break;
}
}

View File

@@ -1,16 +0,0 @@
import { test, expect } from "@chromatic-com/playwright";
test(
"Admin - Custom Assistants - Prompt Library",
{
tag: "@admin",
},
async ({ page }, testInfo) => {
// Test simple loading
await page.goto("http://localhost:3000/admin/prompt-library");
await expect(page.locator("h1.text-3xl")).toHaveText("Prompt Library");
await expect(page.locator("p.text-sm")).toHaveText(
/^Create prompts that can be accessed/
);
}
);