Skip to content

Commit

Permalink
Add hiding + re-ordering to personas
Browse files Browse the repository at this point in the history
  • Loading branch information
Weves committed Dec 23, 2023
1 parent 8b7d01f commit d9fbd7f
Show file tree
Hide file tree
Showing 22 changed files with 840 additions and 88 deletions.
34 changes: 34 additions & 0 deletions backend/alembic/versions/891cd83c87a8_add_is_visible_to_persona.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Add is_visible to Persona
Revision ID: 891cd83c87a8
Revises: b156fa702355
Create Date: 2023-12-21 11:55:54.132279
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "891cd83c87a8"
down_revision = "b156fa702355"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.add_column(
"persona",
sa.Column("is_visible", sa.Boolean(), nullable=True),
)
op.execute("UPDATE persona SET is_visible = true")
op.alter_column("persona", "is_visible", nullable=False)

op.add_column(
"persona",
sa.Column("display_priority", sa.Integer(), nullable=True),
)


def downgrade() -> None:
op.drop_column("persona", "is_visible")
op.drop_column("persona", "display_priority")
43 changes: 36 additions & 7 deletions backend/danswer/db/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,14 @@ def get_prompt_by_id(

def get_persona_by_id(
persona_id: int,
# if user_id is `None` assume the user is an admin or auth is disabled
user_id: UUID | None,
db_session: Session,
include_deleted: bool = False,
) -> Persona:
stmt = select(Persona).where(
Persona.id == persona_id,
or_(Persona.user_id == user_id, Persona.user_id.is_(None)),
)
stmt = select(Persona).where(Persona.id == persona_id)
if user_id is not None:
stmt = stmt.where(or_(Persona.user_id == user_id, Persona.user_id.is_(None)))

if not include_deleted:
stmt = stmt.where(Persona.deleted.is_(False))
Expand Down Expand Up @@ -534,6 +534,34 @@ def mark_persona_as_deleted(
db_session.commit()


def update_persona_visibility(
persona_id: int,
is_visible: bool,
db_session: Session,
) -> None:
persona = get_persona_by_id(
persona_id=persona_id, user_id=None, db_session=db_session
)
persona.is_visible = is_visible
db_session.commit()


def update_all_personas_display_priority(
display_priority_map: dict[int, int],
db_session: Session,
) -> None:
"""Updates the display priority of all lives Personas"""
personas = get_personas(user_id=None, db_session=db_session)
available_persona_ids = {persona.id for persona in personas}
if available_persona_ids != set(display_priority_map.keys()):
raise ValueError("Invalid persona IDs provided")

for persona in personas:
persona.display_priority = display_priority_map[persona.id]

db_session.commit()


def get_prompts(
user_id: UUID | None,
db_session: Session,
Expand All @@ -553,15 +581,16 @@ def get_prompts(


def get_personas(
# if user_id is `None` assume the user is an admin or auth is disabled
user_id: UUID | None,
db_session: Session,
include_default: bool = True,
include_slack_bot_personas: bool = False,
include_deleted: bool = False,
) -> Sequence[Persona]:
stmt = select(Persona).where(
or_(Persona.user_id == user_id, Persona.user_id.is_(None))
)
stmt = select(Persona)
if user_id is not None:
stmt = stmt.where(or_(Persona.user_id == user_id, Persona.user_id.is_(None)))

if not include_default:
stmt = stmt.where(Persona.default_persona.is_(False))
Expand Down
6 changes: 6 additions & 0 deletions backend/danswer/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,12 @@ class Persona(Base):
# Default personas are configured via backend during deployment
# Treated specially (cannot be user edited etc.)
default_persona: Mapped[bool] = mapped_column(Boolean, default=False)
# controls whether the persona is available to be selected by users
is_visible: Mapped[bool] = mapped_column(Boolean, default=True)
# controls the ordering of personas in the UI
# higher priority personas are displayed first, ties are resolved by the ID,
# where lower value IDs (e.g. created earlier) are displayed first
display_priority: Mapped[int] = mapped_column(Integer, nullable=True, default=None)
deleted: Mapped[bool] = mapped_column(Boolean, default=False)

# These are only defaults, users can select from all if desired
Expand Down
38 changes: 38 additions & 0 deletions backend/danswer/server/features/persona/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fastapi import APIRouter
from fastapi import Depends
from fastapi import HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session

from danswer.auth.users import current_admin_user
Expand All @@ -11,6 +12,8 @@
from danswer.db.chat import get_personas
from danswer.db.chat import get_prompts_by_ids
from danswer.db.chat import mark_persona_as_deleted
from danswer.db.chat import update_all_personas_display_priority
from danswer.db.chat import update_persona_visibility
from danswer.db.chat import upsert_persona
from danswer.db.document_set import get_document_sets_by_ids
from danswer.db.engine import get_session
Expand Down Expand Up @@ -101,6 +104,41 @@ def update_persona(
)


class IsVisibleRequest(BaseModel):
is_visible: bool


@admin_router.patch("/{persona_id}/visible")
def patch_persona_visibility(
persona_id: int,
is_visible_request: IsVisibleRequest,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> None:
update_persona_visibility(
persona_id=persona_id,
is_visible=is_visible_request.is_visible,
db_session=db_session,
)


class DisplayPriorityRequest(BaseModel):
# maps persona id to display priority
display_priority_map: dict[int, int]


@admin_router.put("/display-priority")
def patch_persona_display_priority(
display_priority_request: DisplayPriorityRequest,
_: User | None = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> None:
update_all_personas_display_priority(
display_priority_map=display_priority_request.display_priority_map,
db_session=db_session,
)


@admin_router.delete("/{persona_id}")
def delete_persona(
persona_id: int,
Expand Down
4 changes: 4 additions & 0 deletions backend/danswer/server/features/persona/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class PersonaSnapshot(BaseModel):
id: int
name: str
shared: bool
is_visible: bool
display_priority: int | None
description: str
num_chunks: float | None
llm_relevance_filter: bool
Expand All @@ -41,6 +43,8 @@ def from_model(cls, persona: Persona) -> "PersonaSnapshot":
id=persona.id,
name=persona.name,
shared=persona.user_id is None,
is_visible=persona.is_visible,
display_priority=persona.display_priority,
description=persona.description,
num_chunks=persona.num_chunks,
llm_relevance_filter=persona.llm_relevance_filter,
Expand Down
65 changes: 65 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"lint": "next lint"
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@phosphor-icons/react": "^2.0.8",
"@tremor/react": "^3.9.2",
"@types/js-cookie": "^3.0.3",
Expand Down
61 changes: 12 additions & 49 deletions web/src/app/admin/documents/ScoreEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { PopupSpec } from "@/components/admin/connectors/Popup";
import { useState } from "react";
import { updateBoost } from "./lib";
import { CheckmarkIcon, EditIcon } from "@/components/icons/icons";
import { FiEdit } from "react-icons/fi";
import { EditableValue } from "@/components/EditableValue";

export const ScoreSection = ({
documentId,
Expand All @@ -17,17 +15,14 @@ export const ScoreSection = ({
refresh: () => void;
consistentWidth?: boolean;
}) => {
const [isOpen, setIsOpen] = useState(false);
const [score, setScore] = useState(initialScore.toString());

const onSubmit = async () => {
const numericScore = Number(score);
const onSubmit = async (value: string) => {
const numericScore = Number(value);
if (isNaN(numericScore)) {
setPopup({
message: "Score must be a number",
type: "error",
});
return;
return false;
}

const errorMsg = await updateBoost(documentId, numericScore);
Expand All @@ -36,55 +31,23 @@ export const ScoreSection = ({
message: errorMsg,
type: "error",
});
return false;
} else {
setPopup({
message: "Updated score!",
type: "success",
});
refresh();
setIsOpen(false);
}
};

if (isOpen) {
return (
<div className="my-auto h-full flex">
<input
value={score}
onChange={(e) => {
setScore(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
onSubmit();
}
if (e.key === "Escape") {
setIsOpen(false);
setScore(initialScore.toString());
}
}}
className="border bg-background-strong border-gray-300 rounded py-1 px-1 w-12 h-4 my-auto"
/>
<div onClick={onSubmit} className="cursor-pointer my-auto ml-2">
<CheckmarkIcon size={16} className="text-green-700" />
</div>
</div>
);
}
return true;
};

return (
<div className="h-full flex flex-col">
<div
className="flex my-auto cursor-pointer hover:bg-hover rounded"
onClick={() => setIsOpen(true)}
>
<div className={"flex " + (consistentWidth && " w-6")}>
<div className="ml-auto my-auto">{initialScore}</div>
</div>
<div className="cursor-pointer ml-2 my-auto h-4">
<FiEdit size={16} />
</div>
</div>
</div>
<EditableValue
initialValue={initialScore.toString()}
onSubmit={onSubmit}
consistentWidth={consistentWidth}
/>
);
};
Loading

1 comment on commit d9fbd7f

@vercel
Copy link

@vercel vercel bot commented on d9fbd7f Dec 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.