mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-04-11 21:39:31 +02:00
update persona defaults (#3042)
* evaluate None to default * fix usage report pagination * update persona defaults * update user preferences * k * validate * update typing * nit * formating nits * fallback to all assistants * update ux + spacing * udpate refresh logic * minor update to refresh * nit * touchup * update starter message * update default live assistant logic --------- Co-authored-by: Yuhong Sun <yuhongsun96@gmail.com>
This commit is contained in:
parent
43d8daa5bc
commit
07a1b49b4f
@ -0,0 +1,46 @@
|
||||
"""remove description from starter messages
|
||||
|
||||
Revision ID: b72ed7a5db0e
|
||||
Revises: 33cb72ea4d80
|
||||
Create Date: 2024-11-03 15:55:28.944408
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "b72ed7a5db0e"
|
||||
down_revision = "33cb72ea4d80"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute(
|
||||
sa.text(
|
||||
"""
|
||||
UPDATE persona
|
||||
SET starter_messages = (
|
||||
SELECT jsonb_agg(elem - 'description')
|
||||
FROM jsonb_array_elements(starter_messages) elem
|
||||
)
|
||||
WHERE starter_messages IS NOT NULL
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
sa.text(
|
||||
"""
|
||||
UPDATE persona
|
||||
SET starter_messages = (
|
||||
SELECT jsonb_agg(elem || '{"description": ""}')
|
||||
FROM jsonb_array_elements(starter_messages) elem
|
||||
)
|
||||
WHERE starter_messages IS NOT NULL
|
||||
"""
|
||||
)
|
||||
)
|
@ -0,0 +1,29 @@
|
||||
"""add recent assistants
|
||||
|
||||
Revision ID: c0fd6e4da83a
|
||||
Revises: b72ed7a5db0e
|
||||
Create Date: 2024-11-03 17:28:54.916618
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c0fd6e4da83a"
|
||||
down_revision = "b72ed7a5db0e"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"user",
|
||||
sa.Column(
|
||||
"recent_assistants", postgresql.JSONB(), server_default="[]", nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("user", "recent_assistants")
|
@ -42,18 +42,14 @@ personas:
|
||||
display_priority: 1
|
||||
is_visible: true
|
||||
starter_messages:
|
||||
- name: "General Information"
|
||||
description: "Ask about available information"
|
||||
message: "Hello! I'm interested in learning more about the information available here. Could you give me an overview of the types of data or documents that might be accessible?"
|
||||
- name: "Specific Topic Search"
|
||||
description: "Search for specific information"
|
||||
message: "Hi! I'd like to learn more about a specific topic. Could you help me find relevant documents and information?"
|
||||
- name: "Recent Updates"
|
||||
description: "Inquire about latest additions"
|
||||
message: "Hello! I'm curious about any recent updates or additions to the knowledge base. Can you tell me what new information has been added lately?"
|
||||
- name: "Cross-referencing Information"
|
||||
description: "Connect information from different sources"
|
||||
message: "Hi! I'm working on a project that requires connecting information from multiple sources. How can I effectively cross-reference data across different documents or categories?"
|
||||
- name: "Give me an overview of what's here"
|
||||
message: "Sample some documents and tell me what you find."
|
||||
- name: "Use AI to solve a work related problem"
|
||||
message: "Ask me what problem I would like to solve, then search the knowledge base to help me find a solution."
|
||||
- name: "Find updates on a topic of interest"
|
||||
message: "Once I provide a topic, retrieve related documents and tell me when there was last activity on the topic if available."
|
||||
- name: "Surface contradictions"
|
||||
message: "Have me choose a subject. Once I have provided it, check against the knowledge base and point out any inconsistencies. For all your following responses, focus on identifying contradictions."
|
||||
|
||||
- id: 1
|
||||
name: "General"
|
||||
@ -71,18 +67,14 @@ personas:
|
||||
display_priority: 0
|
||||
is_visible: true
|
||||
starter_messages:
|
||||
- name: "Open Discussion"
|
||||
description: "Start an open-ended conversation"
|
||||
message: "Hi! Can you help me write a professional email?"
|
||||
- name: "Problem Solving"
|
||||
description: "Get help with a challenge"
|
||||
message: "Hello! I need help managing my daily tasks better. Do you have any simple tips?"
|
||||
- name: "Learn Something New"
|
||||
description: "Explore a new topic"
|
||||
message: "Hi! Could you explain what project management is in simple terms?"
|
||||
- name: "Creative Brainstorming"
|
||||
description: "Generate creative ideas"
|
||||
message: "Hello! I need to brainstorm some team building activities. Do you have any fun suggestions?"
|
||||
- name: "Summarize a document"
|
||||
message: "If I have provided a document please summarize it for me. If not, please ask me to upload a document either by dragging it into the input bar or clicking the +file icon."
|
||||
- name: "Help me with coding"
|
||||
message: 'Write me a "Hello World" script in 5 random languages to show off the functionality.'
|
||||
- name: "Draft a professional email"
|
||||
message: "Help me craft a professional email. Let's establish the context and the anticipated outcomes of the email before proposing a draft."
|
||||
- name: "Learn something new"
|
||||
message: "What is the difference between a Gantt chart, a Burndown chart and a Kanban board?"
|
||||
|
||||
- id: 2
|
||||
name: "Paraphrase"
|
||||
@ -101,16 +93,12 @@ personas:
|
||||
is_visible: false
|
||||
starter_messages:
|
||||
- name: "Document Search"
|
||||
description: "Find exact information"
|
||||
message: "Hi! Could you help me find information about our team structure and reporting lines from our internal documents?"
|
||||
- name: "Process Verification"
|
||||
description: "Find exact quotes"
|
||||
message: "Hello! I need to understand our project approval process. Could you find the exact steps from our documentation?"
|
||||
- name: "Technical Documentation"
|
||||
description: "Search technical details"
|
||||
message: "Hi there! I'm looking for information about our deployment procedures. Can you find the specific steps from our technical guides?"
|
||||
- name: "Policy Reference"
|
||||
description: "Check official policies"
|
||||
message: "Hello! Could you help me find our official guidelines about client communication? I need the exact wording from our documentation."
|
||||
|
||||
- id: 3
|
||||
@ -130,15 +118,11 @@ personas:
|
||||
display_priority: 3
|
||||
is_visible: true
|
||||
starter_messages:
|
||||
- name: "Landscape"
|
||||
description: "Generate a landscape image"
|
||||
message: "Create an image of a serene mountain lake at sunset, with snow-capped peaks reflected in the calm water and a small wooden cabin on the shore."
|
||||
- name: "Character"
|
||||
description: "Generate a character image"
|
||||
message: "Generate an image of a futuristic robot with glowing blue eyes, sleek metallic body, and intricate circuitry visible through transparent panels on its chest and arms."
|
||||
- name: "Abstract"
|
||||
description: "Create an abstract image"
|
||||
message: "Create an abstract image representing the concept of time, using swirling clock hands, fragmented hourglasses, and streaks of light to convey the passage of moments and eras."
|
||||
- name: "Urban Scene"
|
||||
description: "Generate an urban landscape"
|
||||
message: "Generate an image of a bustling futuristic cityscape at night, with towering skyscrapers, flying vehicles, holographic advertisements, and a mix of neon and bioluminescent lighting."
|
||||
- name: "Create visuals for a presentation"
|
||||
message: "Generate someone presenting a graph which clearly demonstrates an upwards trajectory."
|
||||
- name: "Find inspiration for a marketing campaign"
|
||||
message: "Generate an image of two happy individuals sipping on a soda drink in a glass bottle."
|
||||
- name: "Visualize a product design"
|
||||
message: "I want to add a search bar to my Iphone app. Generate me generic examples of how other apps implement this."
|
||||
- name: "Generate a humorous image response"
|
||||
message: "My teammate just made a silly mistake and I want to respond with a facepalm. Can you generate me one?"
|
||||
|
@ -135,6 +135,9 @@ class User(SQLAlchemyBaseUserTableUUID, Base):
|
||||
hidden_assistants: Mapped[list[int]] = mapped_column(
|
||||
postgresql.JSONB(), nullable=False, default=[]
|
||||
)
|
||||
recent_assistants: Mapped[list[dict]] = mapped_column(
|
||||
postgresql.JSONB(), nullable=False, default=list, server_default="[]"
|
||||
)
|
||||
|
||||
oidc_expiry: Mapped[datetime.datetime] = mapped_column(
|
||||
TIMESTAMPAware(timezone=True), nullable=True
|
||||
@ -1321,7 +1324,6 @@ class StarterMessage(TypedDict):
|
||||
in Postgres"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
message: str
|
||||
|
||||
|
||||
|
@ -42,7 +42,7 @@ class UserPreferences(BaseModel):
|
||||
chosen_assistants: list[int] | None = None
|
||||
hidden_assistants: list[int] = []
|
||||
visible_assistants: list[int] = []
|
||||
|
||||
recent_assistants: list[int] | None = None
|
||||
default_model: str | None = None
|
||||
|
||||
|
||||
|
@ -518,6 +518,59 @@ class ChosenDefaultModelRequest(BaseModel):
|
||||
default_model: str | None = None
|
||||
|
||||
|
||||
class RecentAssistantsRequest(BaseModel):
|
||||
current_assistant: int
|
||||
|
||||
|
||||
def update_recent_assistants(
|
||||
recent_assistants: list[int] | None, current_assistant: int
|
||||
) -> list[int]:
|
||||
if recent_assistants is None:
|
||||
recent_assistants = []
|
||||
else:
|
||||
recent_assistants = [x for x in recent_assistants if x != current_assistant]
|
||||
|
||||
# Add current assistant to start of list
|
||||
recent_assistants.insert(0, current_assistant)
|
||||
|
||||
# Keep only the 5 most recent assistants
|
||||
recent_assistants = recent_assistants[:5]
|
||||
return recent_assistants
|
||||
|
||||
|
||||
@router.patch("/user/recent-assistants")
|
||||
def update_user_recent_assistants(
|
||||
request: RecentAssistantsRequest,
|
||||
user: User | None = Depends(current_user),
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> None:
|
||||
if user is None:
|
||||
if AUTH_TYPE == AuthType.DISABLED:
|
||||
store = get_kv_store()
|
||||
no_auth_user = fetch_no_auth_user(store)
|
||||
preferences = no_auth_user.preferences
|
||||
recent_assistants = preferences.recent_assistants
|
||||
updated_preferences = update_recent_assistants(
|
||||
recent_assistants, request.current_assistant
|
||||
)
|
||||
preferences.recent_assistants = updated_preferences
|
||||
set_no_auth_user_preferences(store, preferences)
|
||||
return
|
||||
else:
|
||||
raise RuntimeError("This should never happen")
|
||||
|
||||
recent_assistants = UserInfo.from_model(user).preferences.recent_assistants
|
||||
updated_recent_assistants = update_recent_assistants(
|
||||
recent_assistants, request.current_assistant
|
||||
)
|
||||
db_session.execute(
|
||||
update(User)
|
||||
.where(User.id == user.id) # type: ignore
|
||||
.values(recent_assistants=updated_recent_assistants)
|
||||
)
|
||||
db_session.commit()
|
||||
|
||||
|
||||
@router.patch("/user/default-model")
|
||||
def update_user_default_model(
|
||||
request: ChosenDefaultModelRequest,
|
||||
|
@ -3,7 +3,7 @@ kind: Deployment
|
||||
metadata:
|
||||
name: celery-worker-indexing
|
||||
spec:
|
||||
replicas: 3
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: celery-worker-indexing
|
||||
|
@ -3,7 +3,7 @@ kind: Deployment
|
||||
metadata:
|
||||
name: celery-worker-light
|
||||
spec:
|
||||
replicas: 2
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: celery-worker-light
|
||||
|
@ -246,9 +246,6 @@ export function AssistantEditor({
|
||||
name: Yup.string().required(
|
||||
"Each starter message must have a name"
|
||||
),
|
||||
description: Yup.string().required(
|
||||
"Each starter message must have a description"
|
||||
),
|
||||
message: Yup.string().required(
|
||||
"Each starter message must have a message"
|
||||
),
|
||||
@ -1053,36 +1050,6 @@ export function AssistantEditor({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label small>Description</Label>
|
||||
<SubLabel>
|
||||
A description which tells the user
|
||||
what they might want to use this
|
||||
Starter Message for. For example
|
||||
"to a client about a new
|
||||
feature"
|
||||
</SubLabel>
|
||||
<Field
|
||||
name={`starter_messages.${index}.description`}
|
||||
className={`
|
||||
border
|
||||
border-border
|
||||
bg-background
|
||||
rounded
|
||||
w-full
|
||||
py-2
|
||||
px-3
|
||||
mr-4
|
||||
`}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<ErrorMessage
|
||||
name={`starter_messages[${index}].description`}
|
||||
component="div"
|
||||
className="text-error text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
<Label small>Message</Label>
|
||||
<SubLabel>
|
||||
|
@ -3,7 +3,6 @@ import { DocumentSet, MinimalUserSnapshot } from "@/lib/types";
|
||||
|
||||
export interface StarterMessage {
|
||||
name: string;
|
||||
description: string | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
|
@ -191,19 +191,15 @@ const DocumentSetTable = ({
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{documentSet.is_up_to_date ? (
|
||||
<Badge size="md" variant="success" icon={FiCheckCircle}>
|
||||
<Badge variant="success" icon={FiCheckCircle}>
|
||||
Up to Date
|
||||
</Badge>
|
||||
) : documentSet.cc_pair_descriptors.length > 0 ? (
|
||||
<Badge size="md" variant="in_progress" icon={FiClock}>
|
||||
<Badge variant="in_progress" icon={FiClock}>
|
||||
Syncing
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
size="md"
|
||||
variant="destructive"
|
||||
icon={FiAlertTriangle}
|
||||
>
|
||||
<Badge variant="destructive" icon={FiAlertTriangle}>
|
||||
Deleting
|
||||
</Badge>
|
||||
)}
|
||||
@ -211,7 +207,6 @@ const DocumentSetTable = ({
|
||||
<TableCell>
|
||||
{documentSet.is_public ? (
|
||||
<Badge
|
||||
size="md"
|
||||
variant={isEditable ? "success" : "default"}
|
||||
icon={FiUnlock}
|
||||
>
|
||||
@ -219,7 +214,6 @@ const DocumentSetTable = ({
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
size="md"
|
||||
variant={isEditable ? "in_progress" : "outline"}
|
||||
icon={FiLock}
|
||||
>
|
||||
|
@ -200,8 +200,8 @@ function ConnectorRow({
|
||||
router.push(`/admin/connector/${ccPairsIndexingStatus.cc_pair_id}`);
|
||||
}}
|
||||
>
|
||||
<TableCell className="!w-[300px]">
|
||||
<p className="w-[200px] xl:w-[400px] inline-block ellipsis truncate">
|
||||
<TableCell className="">
|
||||
<p className="lg:w-[200px] xl:w-[400px] inline-block ellipsis truncate">
|
||||
{ccPairsIndexingStatus.name}
|
||||
</p>
|
||||
</TableCell>
|
||||
|
@ -1,46 +1,42 @@
|
||||
import { Persona } from "../admin/assistants/interfaces";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import { useState } from "react";
|
||||
import { DisplayAssistantCard } from "@/components/assistants/AssistantCards";
|
||||
import { DisplayAssistantCard } from "@/components/assistants/AssistantDescriptionCard";
|
||||
|
||||
export function ChatIntro({ selectedPersona }: { selectedPersona: Persona }) {
|
||||
const [hoveredAssistant, setHoveredAssistant] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mobile:w-[90%] mobile:px-4 w-message-xs 2xl:w-message-sm 3xl:w-message">
|
||||
<div className="relative flex w-fit mx-auto justify-center">
|
||||
<div className="absolute z-10 -left-20 top-1/2 -translate-y-1/2">
|
||||
<div className="relative">
|
||||
<div
|
||||
onMouseEnter={() => setHoveredAssistant(true)}
|
||||
onMouseLeave={() => setHoveredAssistant(false)}
|
||||
className="p-4 scale-[.8] cursor-pointer border-dashed rounded-full flex border border-border border-2 border-dashed"
|
||||
style={{
|
||||
borderStyle: "dashed",
|
||||
borderWidth: "1.5px",
|
||||
borderSpacing: "4px",
|
||||
}}
|
||||
>
|
||||
<AssistantIcon
|
||||
disableToolip
|
||||
size={"large"}
|
||||
assistant={selectedPersona}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute right-full mr-2 w-[300px] top-0">
|
||||
{hoveredAssistant && (
|
||||
<DisplayAssistantCard selectedPersona={selectedPersona} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-6">
|
||||
<div className="relative flex w-fit mx-auto justify-center">
|
||||
<div className="absolute z-10 -left-20 top-1/2 -translate-y-1/2">
|
||||
<div className="relative">
|
||||
<div
|
||||
onMouseEnter={() => setHoveredAssistant(true)}
|
||||
onMouseLeave={() => setHoveredAssistant(false)}
|
||||
className="p-4 scale-[.7] cursor-pointer border-dashed rounded-full flex border border-gray-300 border-2 border-dashed"
|
||||
>
|
||||
<AssistantIcon
|
||||
disableToolip
|
||||
size="large"
|
||||
assistant={selectedPersona}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute right-full mr-1 w-[300px] top-0">
|
||||
{hoveredAssistant && (
|
||||
<DisplayAssistantCard selectedPersona={selectedPersona} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-3xl line-clamp-2 text-text-800 font-base font-semibold text-strong">
|
||||
{selectedPersona?.name || "How can I help you today?"}
|
||||
</div>
|
||||
<div className="text-2xl text-black font-semibold text-center">
|
||||
{selectedPersona.name}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<p className="text-base text-black font-normal text-center">
|
||||
{selectedPersona.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ import { ShareChatSessionModal } from "./modal/ShareChatSessionModal";
|
||||
import { FiArrowDown } from "react-icons/fi";
|
||||
import { ChatIntro } from "./ChatIntro";
|
||||
import { AIMessage, HumanMessage } from "./message/Messages";
|
||||
import { StarterMessage } from "./StarterMessage";
|
||||
import { StarterMessages } from "../../components/assistants/StarterMessage";
|
||||
import {
|
||||
AnswerPiecePacket,
|
||||
DanswerDocument,
|
||||
@ -104,6 +104,14 @@ import BlurBackground from "./shared_chat_search/BlurBackground";
|
||||
import { NoAssistantModal } from "@/components/modals/NoAssistantModal";
|
||||
import { useAssistants } from "@/components/context/AssistantsContext";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
} from "@/components/ui/card";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import AssistantBanner from "../../components/assistants/AssistantBanner";
|
||||
|
||||
const TEMP_USER_MESSAGE_ID = -1;
|
||||
const TEMP_ASSISTANT_MESSAGE_ID = -2;
|
||||
@ -140,7 +148,7 @@ export function ChatPage({
|
||||
!shouldShowWelcomeModal
|
||||
);
|
||||
|
||||
const { user, isAdmin, isLoadingUser } = useUser();
|
||||
const { user, isAdmin, isLoadingUser, refreshUser } = useUser();
|
||||
|
||||
const existingChatIdRaw = searchParams.get("chatId");
|
||||
const [sendOnLoad, setSendOnLoad] = useState<string | null>(
|
||||
@ -233,9 +241,17 @@ export function ChatPage({
|
||||
const [alternativeAssistant, setAlternativeAssistant] =
|
||||
useState<Persona | null>(null);
|
||||
|
||||
const {
|
||||
visibleAssistants: assistants,
|
||||
recentAssistants,
|
||||
assistants: allAssistants,
|
||||
refreshRecentAssistants,
|
||||
} = useAssistants();
|
||||
|
||||
const liveAssistant =
|
||||
alternativeAssistant ||
|
||||
selectedAssistant ||
|
||||
recentAssistants[0] ||
|
||||
finalAssistants[0] ||
|
||||
availableAssistants[0];
|
||||
|
||||
@ -737,7 +753,7 @@ export function ChatPage({
|
||||
setMaxTokens(maxTokens);
|
||||
}
|
||||
}
|
||||
|
||||
refreshRecentAssistants(liveAssistant?.id);
|
||||
fetchMaxTokens();
|
||||
}, [liveAssistant]);
|
||||
|
||||
@ -2005,48 +2021,35 @@ export function ChatPage({
|
||||
!isFetchingChatMessages &&
|
||||
currentSessionChatState == "input" &&
|
||||
!loadingError && (
|
||||
<div className="h-full flex flex-col justify-center items-center">
|
||||
<div className="h-full mt-12 flex flex-col justify-center items-center">
|
||||
<ChatIntro selectedPersona={liveAssistant} />
|
||||
|
||||
<div
|
||||
key={-4}
|
||||
className={`
|
||||
mx-auto
|
||||
px-4
|
||||
w-full
|
||||
max-w-[750px]
|
||||
flex
|
||||
flex-wrap
|
||||
justify-center
|
||||
mt-2
|
||||
h-40
|
||||
items-start
|
||||
mb-6`}
|
||||
>
|
||||
{currentPersona?.starter_messages &&
|
||||
currentPersona.starter_messages.length >
|
||||
0 && (
|
||||
<>
|
||||
<Separator className="mx-2" />
|
||||
<StarterMessages
|
||||
currentPersona={currentPersona}
|
||||
onSubmit={(messageOverride) =>
|
||||
onSubmit({
|
||||
messageOverride,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{currentPersona.starter_messages
|
||||
.slice(0, 4)
|
||||
.map((starterMessage, i) => (
|
||||
<div key={i} className="w-1/2">
|
||||
<StarterMessage
|
||||
starterMessage={starterMessage}
|
||||
onClick={() =>
|
||||
onSubmit({
|
||||
messageOverride:
|
||||
starterMessage.message,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!isFetchingChatMessages &&
|
||||
currentSessionChatState == "input" &&
|
||||
!loadingError &&
|
||||
allAssistants.length > 1 && (
|
||||
<div className="mx-auto px-4 w-full max-w-[750px] flex flex-col items-center">
|
||||
<Separator className="mx-2 w-full my-12" />
|
||||
<div className="text-sm text-black font-medium mb-4">
|
||||
Recent Assistants
|
||||
</div>
|
||||
<AssistantBanner
|
||||
recentAssistants={recentAssistants}
|
||||
liveAssistant={liveAssistant}
|
||||
allAssistants={allAssistants}
|
||||
onAssistantChange={onAssistantChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -1,29 +0,0 @@
|
||||
import { StarterMessage as StarterMessageType } from "../admin/assistants/interfaces";
|
||||
|
||||
export function StarterMessage({
|
||||
starterMessage,
|
||||
onClick,
|
||||
}: {
|
||||
starterMessage: StarterMessageType;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="mb-4 mx-2 group relative overflow-hidden rounded-xl border border-border bg-gradient-to-br from-white to-background p-4 shadow-sm transition-all duration-300 hover:shadow-md hover:scale-[1.005] cursor-pointer"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-100 to-purple-100 opacity-0 group-hover:opacity-20 transition-opacity duration-300" />
|
||||
<h3
|
||||
className="text-base flex items-center font-medium text-text-800 group-hover:text-text-900 transition-colors duration-300
|
||||
line-clamp-2 gap-x-2 overflow-hidden"
|
||||
>
|
||||
{starterMessage.name}
|
||||
</h3>
|
||||
<div className={`overflow-hidden transition-all duration-300 max-h-20}`}>
|
||||
<p className="text-sm text-text-600 mt-2">
|
||||
{starterMessage.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
118
web/src/components/assistants/AssistantBanner.tsx
Normal file
118
web/src/components/assistants/AssistantBanner.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import { Persona } from "../../app/admin/assistants/interfaces";
|
||||
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
export default function AssistantBanner({
|
||||
recentAssistants,
|
||||
liveAssistant,
|
||||
allAssistants,
|
||||
onAssistantChange,
|
||||
}: {
|
||||
recentAssistants: Persona[];
|
||||
liveAssistant: Persona | undefined;
|
||||
allAssistants: Persona[];
|
||||
onAssistantChange: (assistant: Persona) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex mx-auto mt-2 gap-4 ">
|
||||
{recentAssistants
|
||||
// First filter out the current assistant
|
||||
.filter((assistant) => assistant.id !== liveAssistant?.id)
|
||||
// Combine with visible assistants to get up to 4 total
|
||||
.concat(
|
||||
allAssistants.filter(
|
||||
(assistant) =>
|
||||
// Exclude current assistant
|
||||
assistant.id !== liveAssistant?.id &&
|
||||
// Exclude assistants already in recentAssistants
|
||||
!recentAssistants.some((recent) => recent.id === assistant.id)
|
||||
)
|
||||
)
|
||||
// Take first 4
|
||||
.slice(0, 4)
|
||||
.map((assistant) => (
|
||||
<TooltipProvider key={assistant.id}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="flex w-36 mx-3 py-1.5 scale-[1.] rounded-full border border-background-150 justify-center items-center gap-x-2 py-1 px-3 hover:bg-background-125 transition-colors cursor-pointer"
|
||||
onClick={() => onAssistantChange(assistant)}
|
||||
>
|
||||
<AssistantIcon
|
||||
disableToolip
|
||||
size="xs"
|
||||
assistant={assistant}
|
||||
/>
|
||||
<span className="font-semibold text-text-800 text-xs truncate max-w-[120px]">
|
||||
{assistant.name}
|
||||
</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent backgroundColor="bg-background">
|
||||
<AssistantCard assistant={assistant} />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AssistantCard({ assistant }: { assistant: Persona }) {
|
||||
return (
|
||||
<div className="p-6 backdrop-blur-sm rounded-lg max-w-md w-full mx-auto">
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="mb-auto mt-2">
|
||||
<AssistantIcon disableToolip size="small" assistant={assistant} />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h2 className="text-lg font-semibold text-gray-800">
|
||||
{assistant.name}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600">{assistant.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{assistant.tools.length > 0 ||
|
||||
assistant.llm_relevance_filter ||
|
||||
assistant.llm_filter_extraction ? (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-base font-medium text-gray-800">Capabilities</h3>
|
||||
<ul className="space-y-2">
|
||||
{assistant.tools.map((tool, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center text-sm text-gray-700"
|
||||
>
|
||||
<span className="mr-2 text-gray-500">•</span>
|
||||
{tool.display_name}
|
||||
</li>
|
||||
))}
|
||||
{assistant.llm_relevance_filter && (
|
||||
<li className="flex items-center text-sm text-gray-700">
|
||||
<span className="mr-2 text-gray-500">•</span>
|
||||
Advanced Relevance Filtering
|
||||
</li>
|
||||
)}
|
||||
{assistant.llm_filter_extraction && (
|
||||
<li className="flex items-center text-sm text-gray-700">
|
||||
<span className="mr-2 text-gray-500">•</span>
|
||||
Smart Information Extraction
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-600 italic">
|
||||
No specific capabilities listed for this assistant.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { AssistantTools } from "@/app/assistants/ToolsDisplay";
|
||||
import { Bubble } from "@/components/Bubble";
|
||||
@ -115,63 +113,3 @@ export function DraggableAssistantCard(props: {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DisplayAssistantCard({
|
||||
selectedPersona,
|
||||
}: {
|
||||
selectedPersona: Persona;
|
||||
}) {
|
||||
return (
|
||||
<div className="p-4 bg-white/90 backdrop-blur-sm rounded-lg shadow-md border border-border/50 max-w-md w-full mx-auto transition-all duration-300 ease-in-out hover:shadow-lg">
|
||||
<div className="flex items-center mb-3">
|
||||
<AssistantIcon
|
||||
disableToolip
|
||||
size="medium"
|
||||
assistant={selectedPersona}
|
||||
/>
|
||||
<h2 className="ml-3 text-xl font-semibold text-text-900">
|
||||
{selectedPersona.name}
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-text-600 mb-3 leading-relaxed">
|
||||
{selectedPersona.description}
|
||||
</p>
|
||||
{selectedPersona.tools.length > 0 ||
|
||||
selectedPersona.llm_relevance_filter ||
|
||||
selectedPersona.llm_filter_extraction ? (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-base font-medium text-text-900">Capabilities:</h3>
|
||||
<ul className="space-y-.5">
|
||||
{/* display all tools */}
|
||||
{selectedPersona.tools.map((tool, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center text-sm text-text-700"
|
||||
>
|
||||
<span className="mr-2 text-text-500 opacity-70">•</span>{" "}
|
||||
{tool.display_name}
|
||||
</li>
|
||||
))}
|
||||
{/* Built in capabilities */}
|
||||
{selectedPersona.llm_relevance_filter && (
|
||||
<li className="flex items-center text-sm text-text-700">
|
||||
<span className="mr-2 text-text-500 opacity-70">•</span>{" "}
|
||||
Advanced Relevance Filtering
|
||||
</li>
|
||||
)}
|
||||
{selectedPersona.llm_filter_extraction && (
|
||||
<li className="flex items-center text-sm text-text-700">
|
||||
<span className="mr-2 text-text-500 opacity-70">•</span> Smart
|
||||
Information Extraction
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-text-600 italic">
|
||||
No specific capabilities listed for this assistant.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
63
web/src/components/assistants/AssistantDescriptionCard.tsx
Normal file
63
web/src/components/assistants/AssistantDescriptionCard.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import React from "react";
|
||||
|
||||
export function DisplayAssistantCard({
|
||||
selectedPersona,
|
||||
}: {
|
||||
selectedPersona: Persona;
|
||||
}) {
|
||||
return (
|
||||
<div className="p-4 bg-white/90 backdrop-blur-sm rounded-lg shadow-md border border-border/50 max-w-md w-full mx-auto transition-all duration-300 ease-in-out hover:shadow-lg">
|
||||
<div className="flex items-center mb-3">
|
||||
<AssistantIcon
|
||||
disableToolip
|
||||
size="medium"
|
||||
assistant={selectedPersona}
|
||||
/>
|
||||
<h2 className="ml-3 text-xl font-semibold text-text-900">
|
||||
{selectedPersona.name}
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-text-600 mb-3 leading-relaxed">
|
||||
{selectedPersona.description}
|
||||
</p>
|
||||
{selectedPersona.tools.length > 0 ||
|
||||
selectedPersona.llm_relevance_filter ||
|
||||
selectedPersona.llm_filter_extraction ? (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-base font-medium text-text-900">Capabilities:</h3>
|
||||
<ul className="space-y-.5">
|
||||
{/* display all tools */}
|
||||
{selectedPersona.tools.map((tool, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-center text-sm text-text-700"
|
||||
>
|
||||
<span className="mr-2 text-text-500 opacity-70">•</span>{" "}
|
||||
{tool.display_name}
|
||||
</li>
|
||||
))}
|
||||
{/* Built in capabilities */}
|
||||
{selectedPersona.llm_relevance_filter && (
|
||||
<li className="flex items-center text-sm text-text-700">
|
||||
<span className="mr-2 text-text-500 opacity-70">•</span>{" "}
|
||||
Advanced Relevance Filtering
|
||||
</li>
|
||||
)}
|
||||
{selectedPersona.llm_filter_extraction && (
|
||||
<li className="flex items-center text-sm text-text-700">
|
||||
<span className="mr-2 text-text-500 opacity-70">•</span> Smart
|
||||
Information Extraction
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-text-600 italic">
|
||||
No specific capabilities listed for this assistant.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -25,7 +25,7 @@ export function AssistantIcon({
|
||||
disableToolip,
|
||||
}: {
|
||||
assistant: Persona;
|
||||
size?: "small" | "medium" | "large" | "header";
|
||||
size?: "xs" | "small" | "medium" | "large" | "header";
|
||||
border?: boolean;
|
||||
disableToolip?: boolean;
|
||||
}) {
|
||||
@ -52,7 +52,9 @@ export function AssistantIcon({
|
||||
? "w-14 h-14"
|
||||
: size === "medium"
|
||||
? "w-8 h-8"
|
||||
: "w-6 h-6"
|
||||
: size === "xs"
|
||||
? "w-4 h-4"
|
||||
: "w-6 h-6"
|
||||
}`}
|
||||
src={buildImgUrl(assistant.uploaded_image_id)}
|
||||
loading="lazy"
|
||||
@ -68,7 +70,9 @@ export function AssistantIcon({
|
||||
? "w-14 h-14"
|
||||
: size === "medium"
|
||||
? "w-8 h-8"
|
||||
: "w-6 h-6"
|
||||
: size === "xs"
|
||||
? "w-4 h-4"
|
||||
: "w-6 h-6"
|
||||
} `}
|
||||
>
|
||||
{createSVG(
|
||||
@ -80,7 +84,9 @@ export function AssistantIcon({
|
||||
? 56
|
||||
: size === "medium"
|
||||
? 32
|
||||
: 24
|
||||
: size === "xs"
|
||||
? 16
|
||||
: 24
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@ -90,6 +96,7 @@ export function AssistantIcon({
|
||||
${size === "large" ? "w-10 h-10" : ""}
|
||||
${size === "header" ? "w-14 h-14" : ""}
|
||||
${size === "medium" ? "w-8 h-8" : ""}
|
||||
${size === "xs" ? "w-4 h-4" : ""}
|
||||
${!size || size === "small" ? "w-6 h-6" : ""} `}
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
|
51
web/src/components/assistants/StarterMessage.tsx
Normal file
51
web/src/components/assistants/StarterMessage.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { useContext } from "react";
|
||||
import { Persona } from "../../app/admin/assistants/interfaces";
|
||||
import { SettingsContext } from "../settings/SettingsProvider";
|
||||
|
||||
export function StarterMessages({
|
||||
currentPersona,
|
||||
onSubmit,
|
||||
}: {
|
||||
currentPersona: Persona;
|
||||
onSubmit: (messageOverride: string) => void;
|
||||
}) {
|
||||
const settings = useContext(SettingsContext);
|
||||
const isMobile = settings?.isMobile;
|
||||
return (
|
||||
<div
|
||||
key={-4}
|
||||
className={`
|
||||
mx-auto
|
||||
w-full
|
||||
${
|
||||
isMobile
|
||||
? "gap-x-2 w-2/3 justify-between"
|
||||
: "justify-center max-w-[750px] items-start"
|
||||
}
|
||||
flex
|
||||
mt-6
|
||||
`}
|
||||
>
|
||||
{currentPersona?.starter_messages &&
|
||||
currentPersona.starter_messages.length > 0 && (
|
||||
<>
|
||||
{currentPersona.starter_messages
|
||||
.slice(0, isMobile ? 2 : 4)
|
||||
.map((starterMessage, i) => (
|
||||
<div key={i} className={`${isMobile ? "w-1/2" : "w-1/4"}`}>
|
||||
<button
|
||||
onClick={() => onSubmit(starterMessage.message)}
|
||||
className={`relative flex ${
|
||||
!isMobile && "w-40"
|
||||
} flex-col gap-2 rounded-2xl shadow-sm border border-border px-3 py-2 text-start align-to text-wrap text-[15px] shadow-xs transition enabled:hover:bg-background-100 disabled:cursor-not-allowed line-clamp-3`}
|
||||
style={{ height: `5.2rem` }}
|
||||
>
|
||||
{starterMessage.name}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -22,7 +22,8 @@ interface AssistantsContextProps {
|
||||
ownedButHiddenAssistants: Persona[];
|
||||
refreshAssistants: () => Promise<void>;
|
||||
isImageGenerationAvailable: boolean;
|
||||
|
||||
recentAssistants: Persona[];
|
||||
refreshRecentAssistants: (currentAssistant: number) => Promise<void>;
|
||||
// Admin only
|
||||
editablePersonas: Persona[];
|
||||
allAssistants: Persona[];
|
||||
@ -46,10 +47,21 @@ export const AssistantsProvider: React.FC<{
|
||||
const [assistants, setAssistants] = useState<Persona[]>(
|
||||
initialAssistants || []
|
||||
);
|
||||
const { user, isLoadingUser, isAdmin } = useUser();
|
||||
const { user, isLoadingUser, refreshUser, isAdmin } = useUser();
|
||||
const [editablePersonas, setEditablePersonas] = useState<Persona[]>([]);
|
||||
const [allAssistants, setAllAssistants] = useState<Persona[]>([]);
|
||||
|
||||
const [recentAssistants, setRecentAssistants] = useState<Persona[]>(
|
||||
user?.preferences.recent_assistants
|
||||
?.filter((assistantId) =>
|
||||
assistants.find((assistant) => assistant.id === assistantId)
|
||||
)
|
||||
.map(
|
||||
(assistantId) =>
|
||||
assistants.find((assistant) => assistant.id === assistantId)!
|
||||
) || []
|
||||
);
|
||||
|
||||
const [isImageGenerationAvailable, setIsImageGenerationAvailable] =
|
||||
useState<boolean>(false);
|
||||
|
||||
@ -98,6 +110,28 @@ export const AssistantsProvider: React.FC<{
|
||||
fetchPersonas();
|
||||
}, [isAdmin]);
|
||||
|
||||
const refreshRecentAssistants = async (currentAssistant: number) => {
|
||||
const response = await fetch("/api/user/recent-assistants", {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
current_assistant: currentAssistant,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
setRecentAssistants((recentAssistants) => [
|
||||
assistants.find((assistant) => assistant.id === currentAssistant)!,
|
||||
|
||||
...recentAssistants.filter(
|
||||
(assistant) => assistant.id !== currentAssistant
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
||||
const refreshAssistants = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/persona", {
|
||||
@ -125,6 +159,12 @@ export const AssistantsProvider: React.FC<{
|
||||
} catch (error) {
|
||||
console.error("Error refreshing assistants:", error);
|
||||
}
|
||||
setRecentAssistants(
|
||||
assistants.filter(
|
||||
(assistant) =>
|
||||
user?.preferences.recent_assistants?.includes(assistant.id) || false
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const {
|
||||
@ -167,6 +207,8 @@ export const AssistantsProvider: React.FC<{
|
||||
editablePersonas,
|
||||
allAssistants,
|
||||
isImageGenerationAvailable,
|
||||
recentAssistants,
|
||||
refreshRecentAssistants,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -203,7 +203,7 @@ export default function CreateCredential({
|
||||
for information on setting up this connector.
|
||||
</p>
|
||||
)}
|
||||
<CardSection className="!border-0 mt-4 flex flex-col gap-y-6">
|
||||
<CardSection className="w-full !border-0 mt-4 flex flex-col gap-y-6">
|
||||
<TextFormField
|
||||
name="name"
|
||||
placeholder="(Optional) credential name.."
|
||||
|
@ -471,9 +471,9 @@ export function HorizontalSourceSelector({
|
||||
icon={<FiBook size={16} />}
|
||||
defaultDisplay="Sets"
|
||||
resetValues={resetDocuments}
|
||||
width="w-fit max-w-24 ellipsis truncate"
|
||||
width="w-fit max-w-24 etext-llipsis truncate"
|
||||
dropdownWidth="max-w-36 w-fit"
|
||||
optionClassName="truncate break-all ellipsis"
|
||||
optionClassName="truncate w-full break-all"
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -506,7 +506,7 @@ export function HorizontalSourceSelector({
|
||||
resetValues={resetTags}
|
||||
width="w-fit max-w-24 ellipsis truncate"
|
||||
dropdownWidth="max-w-80 w-fit"
|
||||
optionClassName="truncate break-all ellipsis"
|
||||
optionClassName="truncate w-full break-all ellipsis"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -14,13 +14,16 @@ const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {
|
||||
maxWidth?: string;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
>(({ className, sideOffset = 4, maxWidth, ...props }, ref) => (
|
||||
>(({ className, sideOffset = 4, maxWidth, backgroundColor, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md border border-neutral-200 text-white bg-background-900 px-2 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50 max-w-sm",
|
||||
`z-50 overflow-hidden rounded-md border border-neutral-200 text-white ${
|
||||
backgroundColor || "bg-background-900"
|
||||
} px-2 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50 max-w-sm`,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
@ -8,6 +8,7 @@ interface UserPreferences {
|
||||
visible_assistants: number[];
|
||||
hidden_assistants: number[];
|
||||
default_model: string | null;
|
||||
recent_assistants: number[];
|
||||
}
|
||||
|
||||
export enum UserStatus {
|
||||
|
Loading…
x
Reference in New Issue
Block a user