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:
pablodanswer 2024-11-06 16:03:14 -08:00 committed by GitHub
parent 43d8daa5bc
commit 07a1b49b4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 532 additions and 265 deletions

View File

@ -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
"""
)
)

View File

@ -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")

View File

@ -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?"

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -3,7 +3,7 @@ kind: Deployment
metadata:
name: celery-worker-indexing
spec:
replicas: 3
replicas: 1
selector:
matchLabels:
app: celery-worker-indexing

View File

@ -3,7 +3,7 @@ kind: Deployment
metadata:
name: celery-worker-light
spec:
replicas: 2
replicas: 1
selector:
matchLabels:
app: celery-worker-light

View File

@ -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
&quot;to a client about a new
feature&quot;
</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>

View File

@ -3,7 +3,6 @@ import { DocumentSet, MinimalUserSnapshot } from "@/lib/types";
export interface StarterMessage {
name: string;
description: string | null;
message: string;
}

View File

@ -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}
>

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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>
)}

View File

@ -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>
);
}

View 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>
);
}

View File

@ -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>
);
}

View 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>
);
}

View File

@ -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 }}
/>

View 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>
);
}

View File

@ -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}

View File

@ -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.."

View File

@ -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>

View File

@ -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}

View File

@ -8,6 +8,7 @@ interface UserPreferences {
visible_assistants: number[];
hidden_assistants: number[];
default_model: string | null;
recent_assistants: number[];
}
export enum UserStatus {