diff --git a/backend/alembic/versions/ed9e44312505_add_icon_name_field.py b/backend/alembic/versions/ed9e44312505_add_icon_name_field.py new file mode 100644 index 0000000000..a328ed9d5d --- /dev/null +++ b/backend/alembic/versions/ed9e44312505_add_icon_name_field.py @@ -0,0 +1,34 @@ +"""Add icon_name field + +Revision ID: ed9e44312505 +Revises: 5e6f7a8b9c0d +Create Date: 2025-12-03 16:35:07.828393 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "ed9e44312505" +down_revision = "5e6f7a8b9c0d" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Add icon_name column + op.add_column("persona", sa.Column("icon_name", sa.String(), nullable=True)) + + # Remove old icon columns + op.drop_column("persona", "icon_shape") + op.drop_column("persona", "icon_color") + + +def downgrade() -> None: + # Re-add old icon columns + op.add_column("persona", sa.Column("icon_color", sa.String(), nullable=True)) + op.add_column("persona", sa.Column("icon_shape", sa.Integer(), nullable=True)) + + # Remove icon_name column + op.drop_column("persona", "icon_name") diff --git a/backend/onyx/db/models.py b/backend/onyx/db/models.py index 2909504306..ec93a9d618 100644 --- a/backend/onyx/db/models.py +++ b/backend/onyx/db/models.py @@ -2791,8 +2791,7 @@ class Persona(Base): datetime_aware: Mapped[bool] = mapped_column(Boolean, default=True) uploaded_image_id: Mapped[str | None] = mapped_column(String, nullable=True) - icon_color: Mapped[str | None] = mapped_column(String, nullable=True) - icon_shape: Mapped[int | None] = mapped_column(Integer, nullable=True) + icon_name: Mapped[str | None] = mapped_column(String, nullable=True) # These are only defaults, users can select from all if desired document_sets: Mapped[list[DocumentSet]] = relationship( diff --git a/backend/onyx/db/persona.py b/backend/onyx/db/persona.py index 67bce7ce6b..6d848c460a 100644 --- a/backend/onyx/db/persona.py +++ b/backend/onyx/db/persona.py @@ -269,9 +269,8 @@ def create_update_persona( system_prompt=create_persona_request.system_prompt, task_prompt=create_persona_request.task_prompt, datetime_aware=create_persona_request.datetime_aware, - icon_color=create_persona_request.icon_color, - icon_shape=create_persona_request.icon_shape, uploaded_image_id=create_persona_request.uploaded_image_id, + icon_name=create_persona_request.icon_name, display_priority=create_persona_request.display_priority, remove_image=create_persona_request.remove_image, search_start_date=create_persona_request.search_start_date, @@ -738,9 +737,8 @@ def upsert_persona( tool_ids: list[int] | None = None, persona_id: int | None = None, commit: bool = True, - icon_color: str | None = None, - icon_shape: int | None = None, uploaded_image_id: str | None = None, + icon_name: str | None = None, display_priority: int | None = None, is_visible: bool = True, remove_image: bool | None = None, @@ -841,10 +839,9 @@ def upsert_persona( existing_persona.starter_messages = starter_messages existing_persona.deleted = False # Un-delete if previously deleted existing_persona.is_public = is_public - existing_persona.icon_color = icon_color - existing_persona.icon_shape = icon_shape if remove_image or uploaded_image_id: existing_persona.uploaded_image_id = uploaded_image_id + existing_persona.icon_name = icon_name existing_persona.is_visible = is_visible existing_persona.search_start_date = search_start_date existing_persona.labels = labels or [] @@ -905,9 +902,8 @@ def upsert_persona( llm_model_version_override=llm_model_version_override, starter_messages=starter_messages, tools=tools or [], - icon_shape=icon_shape, - icon_color=icon_color, uploaded_image_id=uploaded_image_id, + icon_name=icon_name, display_priority=display_priority, is_visible=is_visible, search_start_date=search_start_date, diff --git a/backend/onyx/seeding/prebuilt_personas.py b/backend/onyx/seeding/prebuilt_personas.py index 42c3c35199..c70c65746c 100644 --- a/backend/onyx/seeding/prebuilt_personas.py +++ b/backend/onyx/seeding/prebuilt_personas.py @@ -47,12 +47,11 @@ class PrebuiltPersona(BaseModel): ) # UI configuration - icon_shape: int = Field(default=0, description="Icon shape ID for UI") - icon_color: str = Field(default="#6FB1FF", description="Icon color hex code") display_priority: int = Field(default=0, description="Display order priority") is_visible: bool = Field( default=True, description="Whether persona is visible in UI" ) + icon_name: Optional[str] = Field(default=None, description="A user-friendly icon") # Special flags is_default_persona: bool = Field( @@ -111,10 +110,9 @@ PREBUILT_PERSONAS = [ llm_relevance_filter=False, llm_filter_extraction=True, recency_bias=RecencyBiasSetting.AUTO, - icon_shape=23013, - icon_color="#6FB1FF", display_priority=0, is_visible=True, + icon_name="search", is_default_persona=True, starter_messages=[ StarterMessage( @@ -160,8 +158,6 @@ PREBUILT_PERSONAS = [ llm_relevance_filter=True, llm_filter_extraction=True, recency_bias=RecencyBiasSetting.AUTO, - icon_shape=50910, - icon_color="#FF6F6F", display_priority=1, is_visible=True, is_default_persona=True, @@ -213,8 +209,6 @@ PREBUILT_PERSONAS = [ llm_relevance_filter=True, llm_filter_extraction=True, recency_bias=RecencyBiasSetting.AUTO, - icon_shape=45519, - icon_color="#6FFF8D", display_priority=2, is_visible=False, is_default_persona=True, @@ -270,11 +264,10 @@ PREBUILT_PERSONAS = [ llm_relevance_filter=False, llm_filter_extraction=False, recency_bias=RecencyBiasSetting.NO_DECAY, - icon_shape=234124, - icon_color="#9B59B6", image_generation=True, display_priority=3, is_visible=True, + icon_name="art", is_default_persona=True, starter_messages=[ StarterMessage( diff --git a/backend/onyx/server/features/persona/models.py b/backend/onyx/server/features/persona/models.py index 3a57871715..cc86b9e5e4 100644 --- a/backend/onyx/server/features/persona/models.py +++ b/backend/onyx/server/features/persona/models.py @@ -69,10 +69,11 @@ class PersonaUpsertRequest(BaseModel): groups: list[int] = Field(default_factory=list) # e.g. ID of SearchTool or ImageGenerationTool or tool_ids: list[int] - icon_color: str | None = None - icon_shape: int | None = None remove_image: bool | None = None uploaded_image_id: str | None = None # New field for uploaded image + icon_name: str | None = ( + None # New field that is custom chosen during agent creation/editing + ) search_start_date: datetime | None = None label_ids: list[int] | None = None is_default_persona: bool = False @@ -106,8 +107,7 @@ class MinimalPersonaSnapshot(BaseModel): llm_model_provider_override: str | None uploaded_image_id: str | None - icon_shape: int | None - icon_color: str | None + icon_name: str | None is_public: bool is_visible: bool @@ -143,8 +143,7 @@ class MinimalPersonaSnapshot(BaseModel): llm_model_version_override=persona.llm_model_version_override, llm_model_provider_override=persona.llm_model_provider_override, uploaded_image_id=persona.uploaded_image_id, - icon_shape=persona.icon_shape, - icon_color=persona.icon_color, + icon_name=persona.icon_name, is_public=persona.is_public, is_visible=persona.is_visible, display_priority=persona.display_priority, @@ -165,9 +164,8 @@ class PersonaSnapshot(BaseModel): description: str is_public: bool is_visible: bool - icon_shape: int | None - icon_color: str | None uploaded_image_id: str | None + icon_name: str | None # Return string UUIDs to frontend for consistency user_file_ids: list[str] display_priority: int | None @@ -199,9 +197,8 @@ class PersonaSnapshot(BaseModel): description=persona.description, is_public=persona.is_public, is_visible=persona.is_visible, - icon_shape=persona.icon_shape, - icon_color=persona.icon_color, uploaded_image_id=persona.uploaded_image_id, + icon_name=persona.icon_name, user_file_ids=[str(file.id) for file in persona.user_files], display_priority=persona.display_priority, is_default_persona=persona.is_default_persona, @@ -262,9 +259,8 @@ class FullPersonaSnapshot(PersonaSnapshot): description=persona.description, is_public=persona.is_public, is_visible=persona.is_visible, - icon_shape=persona.icon_shape, - icon_color=persona.icon_color, uploaded_image_id=persona.uploaded_image_id, + icon_name=persona.icon_name, user_file_ids=[str(file.id) for file in persona.user_files], display_priority=persona.display_priority, is_default_persona=persona.is_default_persona, diff --git a/backend/onyx/server/openai_assistants_api/asssistants_api.py b/backend/onyx/server/openai_assistants_api/assistants_api.py similarity index 99% rename from backend/onyx/server/openai_assistants_api/asssistants_api.py rename to backend/onyx/server/openai_assistants_api/assistants_api.py index 0cc9b90012..fd421d04ea 100644 --- a/backend/onyx/server/openai_assistants_api/asssistants_api.py +++ b/backend/onyx/server/openai_assistants_api/assistants_api.py @@ -47,6 +47,7 @@ class CreateAssistantRequest(BaseModel): description: Optional[str] = None instructions: Optional[str] = None tools: Optional[list[dict[str, Any]]] = None + icon_name: Optional[str] = None file_ids: Optional[list[str]] = None metadata: Optional[dict[str, Any]] = None @@ -139,8 +140,7 @@ def create_assistant( db_session=db_session, document_set_ids=[], tool_ids=tool_ids, - icon_color=None, - icon_shape=None, + icon_name=request.icon_name, is_visible=True, system_prompt=request.instructions or "", task_prompt="", diff --git a/backend/onyx/server/openai_assistants_api/full_openai_assistants_api.py b/backend/onyx/server/openai_assistants_api/full_openai_assistants_api.py index 3e717e95a3..f35b243f6e 100644 --- a/backend/onyx/server/openai_assistants_api/full_openai_assistants_api.py +++ b/backend/onyx/server/openai_assistants_api/full_openai_assistants_api.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from onyx.server.openai_assistants_api.asssistants_api import ( +from onyx.server.openai_assistants_api.assistants_api import ( router as assistants_router, ) from onyx.server.openai_assistants_api.messages_api import router as messages_router diff --git a/backend/onyx/server/query_and_chat/chat_backend.py b/backend/onyx/server/query_and_chat/chat_backend.py index cace00d350..4d5639a914 100644 --- a/backend/onyx/server/query_and_chat/chat_backend.py +++ b/backend/onyx/server/query_and_chat/chat_backend.py @@ -288,12 +288,7 @@ def get_chat_session( description=chat_session.description, persona_id=chat_session.persona_id, persona_name=chat_session.persona.name if chat_session.persona else None, - persona_icon_color=( - chat_session.persona.icon_color if chat_session.persona else None - ), - persona_icon_shape=( - chat_session.persona.icon_shape if chat_session.persona else None - ), + personal_icon_name=chat_session.persona.icon_name, current_alternate_model=chat_session.current_alternate_model, messages=chat_message_details, time_created=chat_session.time_created, diff --git a/backend/onyx/server/query_and_chat/models.py b/backend/onyx/server/query_and_chat/models.py index 6e7c70d81e..e6711014bf 100644 --- a/backend/onyx/server/query_and_chat/models.py +++ b/backend/onyx/server/query_and_chat/models.py @@ -263,15 +263,13 @@ class ChatSessionDetailResponse(BaseModel): description: str | None persona_id: int | None = None persona_name: str | None - persona_icon_color: str | None - persona_icon_shape: int | None + personal_icon_name: str | None messages: list[ChatMessageDetail] time_created: datetime shared_status: ChatSessionSharedStatus current_alternate_model: str | None current_temperature_override: float | None deleted: bool = False - packets: list[list[Packet]] diff --git a/web/src/app/admin/assistants/AssistantEditor.tsx b/web/src/app/admin/assistants/AssistantEditor.tsx index 67123d012d..0f9301dbc7 100644 --- a/web/src/app/admin/assistants/AssistantEditor.tsx +++ b/web/src/app/admin/assistants/AssistantEditor.tsx @@ -1,8 +1,7 @@ "use client"; -import React, { JSX } from "react"; +import React from "react"; import { Option } from "@/components/Dropdown"; -import { generateRandomIconShape } from "@/lib/assistantIconUtils"; import { CCPairBasicInfo, DocumentSetSummary, @@ -53,12 +52,11 @@ import { GroupsIconSkeleton, SwapIcon, } from "@/components/icons/icons"; -import { buildImgUrl } from "@/app/chat/components/files/images/utils"; import { debounce } from "lodash"; import { LLMProviderView } from "@/app/admin/configuration/llm/interfaces"; import StarterMessagesList from "@/app/admin/assistants/StarterMessageList"; import UnlabeledSwitchField from "@/refresh-components/formik-fields/UnlabeledSwitchField"; -import { generateIdenticon } from "@/refresh-components/AgentIcon"; +import CustomAgentAvatar from "@/refresh-components/avatars/CustomAgentAvatar"; import { BackButton } from "@/components/BackButton"; import { AdvancedOptionsToggle } from "@/components/AdvancedOptionsToggle"; import { MinimalUserSnapshot } from "@/lib/types"; @@ -93,7 +91,6 @@ import { import { useProjectsContext } from "@/app/chat/projects/ProjectsContext"; import FilePickerPopover from "@/refresh-components/popovers/FilePickerPopover"; import SvgTrash from "@/icons/trash"; -import SvgEditBig from "@/icons/edit-big"; import SvgFiles from "@/icons/files"; import { useAgents } from "@/lib/hooks/useAgents"; import Text from "@/refresh-components/texts/Text"; @@ -115,7 +112,11 @@ function findWebSearchTool(tools: ToolSnapshot[]) { return tools.find((tool) => tool.in_code_tool_id === WEB_SEARCH_TOOL_ID); } -function SubLabel({ children }: { children: string | JSX.Element }) { +interface SubLabelProps { + children: React.ReactNode; +} + +function SubLabel({ children }: SubLabelProps) { return (
(null); const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const userFilesModal = useCreateModal(); - // both `defautIconColor` and `defaultIconShape` are state so that they - // persist across formik reformatting - const [defautIconColor, _setDeafultIconColor] = useState( - colorOptions[Math.floor(Math.random() * colorOptions.length)] - ); - const [defaultIconShape] = useState( - () => generateRandomIconShape().encodedGrid - ); const [isRefreshing, setIsRefreshing] = useState(false); const [removePersonaImage, setRemovePersonaImage] = useState(false); @@ -267,8 +250,6 @@ export default function AssistantEditor({ ? existingPersona.starter_messages : [{ message: "", name: "" }], enabled_tools_map: enabledToolsMap, - icon_color: existingPersona?.icon_color ?? defautIconColor, - icon_shape: existingPersona?.icon_shape ?? defaultIconShape, uploaded_image: null, labels: existingPersona?.labels ?? null, @@ -374,9 +355,7 @@ export default function AssistantEditor({ fetchMcpServers(); }, []); - if (!labels) { - return <>; - } + if (!labels) return null; const openDeleteModal = () => { setDeleteModalOpen(true); @@ -487,8 +466,6 @@ export default function AssistantEditor({ }) ), search_start_date: Yup.date().nullable(), - icon_color: Yup.string(), - icon_shape: Yup.number(), uploaded_image: Yup.mixed().nullable(), // EE Only label_ids: Yup.array().of(Yup.number()), @@ -573,7 +550,6 @@ export default function AssistantEditor({ const submissionData: PersonaUpsertParameters = { ...values, - icon_color: values.icon_color ?? null, starter_messages: starterMessages, groups: groups, users: values.is_public @@ -686,29 +662,15 @@ export default function AssistantEditor({ values.llm_model_version_override || defaultModelName || "" ); - const iconElement = (() => { - if (uploadedImagePreview) { - return ( - Uploaded agent icon - ); - } + const src = + uploadedImagePreview ?? + (existingPersona?.uploaded_image_id && !removePersonaImage + ? existingPersona?.uploaded_image_id + : undefined); - if (existingPersona?.uploaded_image_id && !removePersonaImage) { - return ( - Uploaded agent icon - ); - } - - return generateIdenticon((values.icon_shape || 0).toString(), 36); - })(); + const iconElement = ( + + ); return ( <> @@ -744,6 +706,7 @@ export default function AssistantEditor({
+ {/* Refresh starter messages when name or description changes */}

{existingPersona ? ( @@ -754,6 +717,7 @@ export default function AssistantEditor({ "Create an Agent" )}

+
@@ -815,29 +779,6 @@ export default function AssistantEditor({ )} - {!values.uploaded_image && - (!existingPersona?.uploaded_image_id || - removePersonaImage) && ( - - )} - {existingPersona?.uploaded_image_id && removePersonaImage && !values.uploaded_image && ( @@ -883,98 +824,90 @@ export default function AssistantEditor({
- <> - -
-
-
-

- Knowledge -

-
- -
- - toggleToolInValues(searchTool?.id || -1) - } - name={`enabled_tools_map.${ - searchTool?.id || -1 - }`} - disabled={!connectorsExist || !searchTool} - /> -
-
+ +
+
+

Knowledge

+
+ +
+ + toggleToolInValues(searchTool?.id || -1) + } + name={`enabled_tools_map.${ + searchTool?.id || -1 + }`} + disabled={!connectorsExist || !searchTool} + />
-
+
- +
{((searchTool && values.enabled_tools_map[searchTool.id]) || !connectorsExist) && (
{canShowKnowledgeSource && ( - <> -
-
+
+
+
+ setFieldValue( + "knowledge_source", + "team_knowledge" + ) + } + > +
+ +
+

+ Team Knowledge +

+
+ + {userKnowledgeEnabled && (
setFieldValue( "knowledge_source", - "team_knowledge" + "user_files" ) } >
- +

- Team Knowledge + User Knowledge

- - {userKnowledgeEnabled && ( -
- setFieldValue( - "knowledge_source", - "user_files" - ) - } - > -
- -
-

- User Knowledge -

-
- )} -
+ )}
- +
)} {values.knowledge_source === "user_files" && @@ -1256,8 +1189,8 @@ export default function AssistantEditor({
- +
@@ -1311,6 +1244,7 @@ export default function AssistantEditor({ showAdvancedOptions={showAdvancedOptions} setShowAdvancedOptions={setShowAdvancedOptions} /> + {showAdvancedOptions && ( <>
@@ -1635,8 +1569,8 @@ export default function AssistantEditor({
- +

@@ -1675,8 +1609,8 @@ export default function AssistantEditor({

- + - )}
-
- {existingPersona && ( - - )} -
+ {existingPersona && ( + + )} +
) diff --git a/web/src/app/chat/components/WelcomeMessage.tsx b/web/src/app/chat/components/WelcomeMessage.tsx index f294275aa0..be14ebf1dc 100644 --- a/web/src/app/chat/components/WelcomeMessage.tsx +++ b/web/src/app/chat/components/WelcomeMessage.tsx @@ -1,8 +1,9 @@ -// import { AssistantIcon } from "@/components/assistants/AssistantIcon"; +"use client"; + import { Logo } from "@/components/logo/Logo"; import { getRandomGreeting } from "@/lib/chat/greetingMessages"; import { cn } from "@/lib/utils"; -import AgentIcon from "@/refresh-components/AgentIcon"; +import AgentAvatar from "@/refresh-components/avatars/AgentAvatar"; import Text from "@/refresh-components/texts/Text"; import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { useMemo } from "react"; @@ -17,6 +18,7 @@ export default function WelcomeMessage({ isDefaultAgent, }: WelcomeMessageProps) { let content: React.ReactNode = null; + if (isDefaultAgent) { const greeting = useMemo(getRandomGreeting, []); content = ( @@ -32,7 +34,7 @@ export default function WelcomeMessage({ data-testid="assistant-name-display" className="flex flex-row items-center gap-3" > - + {agent.name}
{agent.description && ( diff --git a/web/src/app/chat/components/projects/ProjectChatSessionList.tsx b/web/src/app/chat/components/projects/ProjectChatSessionList.tsx index b8ef870867..a0f82add71 100644 --- a/web/src/app/chat/components/projects/ProjectChatSessionList.tsx +++ b/web/src/app/chat/components/projects/ProjectChatSessionList.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { ChatSessionMorePopup } from "@/components/sidebar/ChatSessionMorePopup"; import { useProjectsContext } from "../../projects/ProjectsContext"; import { ChatSession } from "@/app/chat/interfaces"; -import AgentIcon from "@/refresh-components/AgentIcon"; +import AgentAvatar from "@/refresh-components/avatars/AgentAvatar"; import SvgBubbleText from "@/icons/bubble-text"; import { useAgents } from "@/lib/hooks/useAgents"; import { formatRelativeTime } from "./project_utils"; @@ -84,7 +84,7 @@ export default function ProjectChatSessionList() { if (assistant) { return (
- +
); } diff --git a/web/src/app/chat/interfaces.ts b/web/src/app/chat/interfaces.ts index 2ea6724372..df6f61612b 100644 --- a/web/src/app/chat/interfaces.ts +++ b/web/src/app/chat/interfaces.ts @@ -150,8 +150,6 @@ export interface BackendChatSession { description: string; persona_id: number; persona_name: string; - persona_icon_color: string | null; - persona_icon_shape: number | null; messages: BackendMessage[]; time_created: string; time_updated: string; diff --git a/web/src/app/chat/message/messageComponents/AIMessage.tsx b/web/src/app/chat/message/messageComponents/AIMessage.tsx index 236666f96f..e1f8a7c678 100644 --- a/web/src/app/chat/message/messageComponents/AIMessage.tsx +++ b/web/src/app/chat/message/messageComponents/AIMessage.tsx @@ -32,7 +32,7 @@ import { import { useMessageSwitching } from "@/app/chat/message/messageComponents/hooks/useMessageSwitching"; import MultiToolRenderer from "@/app/chat/message/messageComponents/MultiToolRenderer"; import { RendererComponent } from "@/app/chat/message/messageComponents/renderMessageComponent"; -import AgentIcon from "@/refresh-components/AgentIcon"; +import AgentAvatar from "@/refresh-components/avatars/AgentAvatar"; import IconButton from "@/refresh-components/buttons/IconButton"; import CopyIconButton from "@/refresh-components/buttons/CopyIconButton"; import SvgThumbsUp from "@/icons/thumbs-up"; @@ -427,7 +427,7 @@ export default function AIMessage({
- +
diff --git a/web/src/app/chat/message/messageComponents/renderers/PythonToolRenderer.tsx b/web/src/app/chat/message/messageComponents/renderers/PythonToolRenderer.tsx index 2b0837c879..baf1b2bca8 100644 --- a/web/src/app/chat/message/messageComponents/renderers/PythonToolRenderer.tsx +++ b/web/src/app/chat/message/messageComponents/renderers/PythonToolRenderer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from "react"; +import { useEffect, useMemo } from "react"; import SvgCode from "@/icons/code"; import { PacketType, @@ -6,9 +6,11 @@ import { PythonToolStart, PythonToolDelta, SectionEnd, -} from "../../../services/streamingModels"; -import { MessageRenderer, RenderType } from "../interfaces"; -import { IconProps } from "@/icons"; +} from "@/app/chat/services/streamingModels"; +import { + MessageRenderer, + RenderType, +} from "@/app/chat/message/messageComponents/interfaces"; import { CodeBlock } from "@/app/chat/message/CodeBlock"; import hljs from "highlight.js/lib/core"; import python from "highlight.js/lib/languages/python"; @@ -71,10 +73,6 @@ function constructCurrentPythonState(packets: PythonToolPacket[]) { }; } -function CodeIcon({ size = 16, ...props }: IconProps) { - return ; -} - export const PythonToolRenderer: MessageRenderer = ({ packets, onComplete, @@ -108,7 +106,7 @@ export const PythonToolRenderer: MessageRenderer = ({ // Loading state - when executing if (isExecuting) { return children({ - icon: CodeIcon, + icon: SvgCode, status: "Executing Python code...", content: (
@@ -132,7 +130,7 @@ export const PythonToolRenderer: MessageRenderer = ({ // Complete state - show output if (isComplete) { return children({ - icon: CodeIcon, + icon: SvgCode, status: hasError ? "Python execution failed" : "Python execution completed", @@ -140,7 +138,7 @@ export const PythonToolRenderer: MessageRenderer = ({
{code && (
- {/* NOTE: note that we need to trim since otherwise there's a huge + {/* NOTE: note that we need to trim since otherwise there's a huge "space" at the start of the code block */} @@ -185,7 +183,7 @@ export const PythonToolRenderer: MessageRenderer = ({ // Fallback return children({ - icon: CodeIcon, + icon: SvgCode, status: status, content:
, }); @@ -194,7 +192,7 @@ export const PythonToolRenderer: MessageRenderer = ({ // Highlight/Short rendering if (isExecuting) { return children({ - icon: CodeIcon, + icon: SvgCode, status: "Executing Python code...", content: (
@@ -217,7 +215,7 @@ export const PythonToolRenderer: MessageRenderer = ({ if (hasError) { return children({ - icon: CodeIcon, + icon: SvgCode, status: "Python execution failed", content: (
@@ -229,7 +227,7 @@ export const PythonToolRenderer: MessageRenderer = ({ if (isComplete) { return children({ - icon: CodeIcon, + icon: SvgCode, status: "Python execution completed", content: (
@@ -244,7 +242,7 @@ export const PythonToolRenderer: MessageRenderer = ({ } return children({ - icon: CodeIcon, + icon: SvgCode, status: "Python execution", content: (
Python execution
diff --git a/web/src/app/chat/shared/[chatId]/page.tsx b/web/src/app/chat/shared/[chatId]/page.tsx index bbc144029b..b5123cee78 100644 --- a/web/src/app/chat/shared/[chatId]/page.tsx +++ b/web/src/app/chat/shared/[chatId]/page.tsx @@ -4,9 +4,32 @@ import { requireAuth } from "@/lib/auth/requireAuth"; import SharedChatDisplay from "@/app/chat/shared/[chatId]/SharedChatDisplay"; import AppPageLayout from "@/layouts/AppPageLayout"; import { Persona } from "@/app/admin/assistants/interfaces"; -import { constructMiniFiedPersona } from "@/lib/assistantIconUtils"; import { fetchHeaderDataSS } from "@/lib/headers/fetchHeaderDataSS"; +// This is used for rendering a persona in the shared chat display +export function constructMiniFiedPersona(name: string, id: number): Persona { + return { + id, + name, + is_visible: true, + is_public: true, + display_priority: 0, + description: "", + document_sets: [], + tools: [], + owner: null, + starter_messages: null, + builtin_persona: false, + is_default_persona: false, + users: [], + groups: [], + user_file_ids: [], + system_prompt: null, + task_prompt: null, + datetime_aware: true, + }; +} + async function getSharedChat(chatId: string) { const response = await fetchSS( `/chat/get-chat-session/${chatId}?is_shared=True` @@ -34,8 +57,6 @@ export default async function Page(props: PageProps) { const chatSession = await getSharedChat(params.chatId).catch(() => null); const persona: Persona = constructMiniFiedPersona( - chatSession?.persona_icon_color ?? null, - chatSession?.persona_icon_shape ?? null, chatSession?.persona_name ?? "", chatSession?.persona_id ?? 0 ); diff --git a/web/src/app/ee/admin/billing/BillingInformationPage.tsx b/web/src/app/ee/admin/billing/BillingInformationPage.tsx index 6d86573546..a05d69173d 100644 --- a/web/src/app/ee/admin/billing/BillingInformationPage.tsx +++ b/web/src/app/ee/admin/billing/BillingInformationPage.tsx @@ -16,10 +16,10 @@ import { CardTitle, } from "@/components/ui/card"; import Button from "@/refresh-components/buttons/Button"; -import { CreditCard } from "@phosphor-icons/react"; import { SubscriptionSummary } from "./SubscriptionSummary"; import { BillingAlerts } from "./BillingAlerts"; -import { ClipboardIcon } from "@/components/icons/icons"; +import SvgClipboard from "@/icons/clipboard"; +import SvgWallet from "@/icons/wallet"; export default function BillingInformationPage() { const router = useRouter(); @@ -95,7 +95,7 @@ export default function BillingInformationPage() { - + Subscription Details @@ -118,7 +118,7 @@ export default function BillingInformationPage() { diff --git a/web/src/app/ee/assistants/stats/[id]/AssistantStats.tsx b/web/src/app/ee/assistants/stats/[id]/AssistantStats.tsx index 8f483e9b55..72486ca6cb 100644 --- a/web/src/app/ee/assistants/stats/[id]/AssistantStats.tsx +++ b/web/src/app/ee/assistants/stats/[id]/AssistantStats.tsx @@ -1,4 +1,5 @@ "use client"; + import { ThreeDotsLoader } from "@/components/Loading"; import { getDatesList } from "@/app/ee/admin/performance/lib"; import { useEffect, useState, useMemo } from "react"; @@ -7,7 +8,7 @@ import { DateRange, } from "@/components/dateRangeSelectors/AdminDateRangeSelector"; import { useAgents } from "@/lib/hooks/useAgents"; -import AssistantIcon from "@/refresh-components/AgentIcon"; +import AgentAvatar from "@/refresh-components/avatars/AgentAvatar"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { AreaChartDisplay } from "@/components/ui/areaChart"; @@ -156,7 +157,7 @@ export function AssistantStats({ assistantId }: { assistantId: number }) {
- {assistant && } + {assistant && }

{assistant?.name}

diff --git a/web/src/components/chat/sources/SourceCard.tsx b/web/src/components/chat/sources/SourceCard.tsx index ff812b6ab9..d106a92bea 100644 --- a/web/src/components/chat/sources/SourceCard.tsx +++ b/web/src/components/chat/sources/SourceCard.tsx @@ -4,7 +4,59 @@ import { OnyxDocument } from "@/lib/search/interfaces"; import { ValidSources } from "@/lib/types"; import React, { useEffect, useState, JSX } from "react"; import { SearchResultIcon } from "@/components/SearchResultIcon"; -import { getFileIconFromFileNameAndLink } from "@/lib/assistantIconUtils"; +import { + FileOptionIcon, + PDFIcon, + TXTIcon, + DOCIcon, + HTMLIcon, + JSONIcon, + ImagesIcon, + XMLIcon, +} from "@/components/icons/icons"; + +export function getFileIconFromFileNameAndLink( + fileName: string, + linkUrl?: string | null +) { + if (linkUrl) { + return ; + } + const extension = fileName.split(".").pop()?.toLowerCase(); + if (extension === "pdf") { + return ; + } else if (extension === "txt") { + return ; + } else if (extension === "doc" || extension === "docx") { + return ; + } else if (extension === "html" || extension === "htm") { + return ; + } else if (extension === "json") { + return ; + } else if ( + ["jpg", "jpeg", "png", "gif", "svg", "webp"].includes(extension || "") + ) { + return ; + } else if (extension === "xml") { + return ; + } else { + if (fileName.includes(".")) { + try { + // Check if fileName could be a valid domain when prefixed with https:// + const url = new URL(`https://${fileName}`); + if (url.hostname === fileName) { + return ; + } + } catch (e) { + // If URL construction fails, it's not a valid domain + } + return ; + } else { + return ; + } + } +} + // Minimal shape needed locally for file icon rendering type FileResponse = { id: number; diff --git a/web/src/icons/clipboard.tsx b/web/src/icons/clipboard.tsx new file mode 100644 index 0000000000..7d42ef2811 --- /dev/null +++ b/web/src/icons/clipboard.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const SvgClipboard = (props: SVGProps) => ( + + + +); +export default SvgClipboard; diff --git a/web/src/icons/two-line-small.tsx b/web/src/icons/two-line-small.tsx new file mode 100644 index 0000000000..33468038dc --- /dev/null +++ b/web/src/icons/two-line-small.tsx @@ -0,0 +1,18 @@ +import type { SVGProps } from "react"; +const SvgTwoLineSmall = (props: SVGProps) => ( + + + +); + +export default SvgTwoLineSmall; diff --git a/web/src/icons/wallet.tsx b/web/src/icons/wallet.tsx new file mode 100644 index 0000000000..abaee13cfd --- /dev/null +++ b/web/src/icons/wallet.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; +import type { SVGProps } from "react"; +const SvgWallet = (props: SVGProps) => ( + + + +); +export default SvgWallet; diff --git a/web/src/lib/assistantIconUtils.tsx b/web/src/lib/assistantIconUtils.tsx deleted file mode 100644 index 6cea3e6f2b..0000000000 --- a/web/src/lib/assistantIconUtils.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { Persona } from "@/app/admin/assistants/interfaces"; -import { - FileOptionIcon, - PDFIcon, - TXTIcon, - DOCIcon, - HTMLIcon, - JSONIcon, - ImagesIcon, - XMLIcon, -} from "@/components/icons/icons"; -import { SearchResultIcon } from "@/components/SearchResultIcon"; - -export interface GridShape { - encodedGrid: number; - filledSquares: number; -} - -export function generateRandomIconShape(): GridShape { - const grid: boolean[][] = Array(4) - .fill(null) - .map(() => Array(4).fill(false)); - - const centerSquares: number[][] = [ - [1, 1], - [1, 2], - [2, 1], - [2, 2], - ]; - - shuffleArray(centerSquares); - const centerFillCount = Math.floor(Math.random() * 2) + 3; // 3 or 4 - for (let i = 0; i < centerFillCount; i++) { - const centerSquare: number[] | undefined = centerSquares[i]; - if (centerSquare === undefined) { - continue; - } - - const [row, col] = centerSquare; - if (row === undefined || col === undefined) { - continue; - } - - const grid_row = grid[row]; - if (grid_row === undefined) { - continue; - } - - grid_row[col] = true; - } - // Randomly fill remaining squares up to 10 total - const remainingSquares = []; - for (let row = 0; row < 4; row++) { - for (let col = 0; col < 4; col++) { - const grid_row = grid[row]; - if (grid_row === undefined) { - continue; - } - - if (!grid_row[col]) { - remainingSquares.push([row, col]); - } - } - } - shuffleArray(remainingSquares); - - let filledSquares = centerFillCount; - for (const [row, col] of remainingSquares) { - if (row === undefined || col == undefined) { - continue; - } - - if (filledSquares >= 10) break; - - const grid_row = grid[row]; - if (grid_row === undefined) { - continue; - } - grid_row[col] = true; - filledSquares++; - } - - let path = ""; - for (let row = 0; row < 4; row++) { - for (let col = 0; col < 4; col++) { - const grid_row = grid[row]; - if (grid_row === undefined) { - continue; - } - - if (grid_row[col]) { - const x = col * 12; - const y = row * 12; - path += `M ${x} ${y} L ${x + 12} ${y} L ${x + 12} ${y + 12} L ${x} ${ - y + 12 - } Z `; - } - } - } - const encodedGrid = encodeGrid(grid); - return { encodedGrid, filledSquares }; -} - -function encodeGrid(grid: boolean[][]): number { - let encoded = 0; - for (let row = 0; row < 4; row++) { - for (let col = 0; col < 4; col++) { - const grid_row = grid[row]; - if (grid_row === undefined) { - continue; - } - - if (grid_row[col]) { - encoded |= 1 << (row * 4 + col); - } - } - } - return encoded; -} - -function shuffleArray(array: any[]) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } -} - -// This is used for rendering a persona in the shared chat display -export const constructMiniFiedPersona = ( - assistant_icon_color: string | null, - assistant_icon_shape: number | null, - name: string, - id: number -): Persona => { - return { - id, - name, - icon_color: assistant_icon_color ?? undefined, - icon_shape: assistant_icon_shape ?? undefined, - is_visible: true, - is_public: true, - display_priority: 0, - description: "", - document_sets: [], - tools: [], - owner: null, - starter_messages: null, - builtin_persona: false, - is_default_persona: false, - users: [], - groups: [], - user_file_ids: [], - system_prompt: null, - task_prompt: null, - datetime_aware: true, - }; -}; - -export const getFileIconFromFileNameAndLink = ( - fileName: string, - linkUrl?: string | null -) => { - if (linkUrl) { - return ; - } - const extension = fileName.split(".").pop()?.toLowerCase(); - if (extension === "pdf") { - return ; - } else if (extension === "txt") { - return ; - } else if (extension === "doc" || extension === "docx") { - return ; - } else if (extension === "html" || extension === "htm") { - return ; - } else if (extension === "json") { - return ; - } else if ( - ["jpg", "jpeg", "png", "gif", "svg", "webp"].includes(extension || "") - ) { - return ; - } else if (extension === "xml") { - return ; - } else { - if (fileName.includes(".")) { - try { - // Check if fileName could be a valid domain when prefixed with https:// - const url = new URL(`https://${fileName}`); - if (url.hostname === fileName) { - return ; - } - } catch (e) { - // If URL construction fails, it's not a valid domain - } - return ; - } else { - return ; - } - } -}; diff --git a/web/src/refresh-components/AgentCard.tsx b/web/src/refresh-components/AgentCard.tsx index ad837182f0..e4b468c22b 100644 --- a/web/src/refresh-components/AgentCard.tsx +++ b/web/src/refresh-components/AgentCard.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from "react"; import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; -import AgentIcon from "@/refresh-components/AgentIcon"; +import AgentAvatar from "@/refresh-components/avatars/AgentAvatar"; import Button from "@/refresh-components/buttons/Button"; import Text from "@/refresh-components/texts/Text"; import SvgBubbleText from "@/icons/bubble-text"; @@ -72,7 +72,7 @@ export default function AgentCard({ agent }: AgentCardProps) {

- +
{agent.name} diff --git a/web/src/refresh-components/AgentIcon.tsx b/web/src/refresh-components/AgentIcon.tsx deleted file mode 100644 index 871d65cd9c..0000000000 --- a/web/src/refresh-components/AgentIcon.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, { JSX } from "react"; -import crypto from "crypto"; -import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; -import { buildImgUrl } from "@/app/chat/components/files/images/utils"; -import { ArtAsistantIcon, OnyxIcon } from "@/components/icons/icons"; -import SimpleTooltip from "@/refresh-components/SimpleTooltip"; -import { cn } from "@/lib/utils"; -import Text from "@/refresh-components/texts/Text"; -import { useSettingsContext } from "@/components/settings/SettingsProvider"; - -function md5ToBits(str: string): number[] { - const md5hex = crypto.createHash("md5").update(str).digest("hex"); - const bits: number[] = []; - for (let i = 0; i < md5hex.length; i += 2) { - const byteVal = parseInt(md5hex.substring(i, i + 2), 16); - for (let b = 7; b >= 0; b--) { - bits.push((byteVal >> b) & 1); - } - } - return bits; -} - -export function generateIdenticon(str: string, size: number) { - const bits = md5ToBits(str); - const gridSize = 5; - const halfCols = 4; - const cellSize = size / gridSize; - - let bitIndex = 0; - const squares: JSX.Element[] = []; - - for (let row = 0; row < gridSize; row++) { - for (let col = 0; col < halfCols; col++) { - const bit = bits[bitIndex % bits.length]; - bitIndex++; - - if (bit === 1) { - const xPos = col * cellSize; - const yPos = row * cellSize; - squares.push( - - ); - - const mirrorCol = gridSize - 1 - col; - if (mirrorCol !== col) { - const mirrorX = mirrorCol * cellSize; - squares.push( - - ); - } - } - } - } - - return ( - - {squares} - - ); -} - -export interface AgentIconProps { - agent: MinimalPersonaSnapshot; - size?: number; -} - -export default function AgentIcon({ agent, size = 24 }: AgentIconProps) { - const settings = useSettingsContext(); - - // Check if whitelabeling is enabled for the default assistant - const shouldUseWhitelabelLogo = - agent.id === 0 && settings?.enterpriseSettings?.use_custom_logo === true; - - return ( - -
- {agent.id === -3 ? ( - - ) : agent.id === 0 ? ( - shouldUseWhitelabelLogo ? ( - Logo - ) : ( - - ) - ) : agent.uploaded_image_id ? ( - {agent.name} - ) : ( - generateIdenticon((agent.icon_shape || 0).toString(), size) - )} -
-
- ); -} diff --git a/web/src/refresh-components/avatars/AgentAvatar.tsx b/web/src/refresh-components/avatars/AgentAvatar.tsx new file mode 100644 index 0000000000..79a77972ea --- /dev/null +++ b/web/src/refresh-components/avatars/AgentAvatar.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; +import { buildImgUrl } from "@/app/chat/components/files/images/utils"; +import { OnyxIcon } from "@/components/icons/icons"; +import { useSettingsContext } from "@/components/settings/SettingsProvider"; +import { DEFAULT_ASSISTANT_ID } from "@/lib/constants"; +import CustomAgentAvatar from "@/refresh-components/avatars/CustomAgentAvatar"; + +export interface AgentAvatarProps { + agent: MinimalPersonaSnapshot; + size?: number; +} + +export default function AgentAvatar({ agent, ...props }: AgentAvatarProps) { + const settings = useSettingsContext(); + + if (agent.id === DEFAULT_ASSISTANT_ID) { + return settings.enterpriseSettings?.use_custom_logo ? ( +
+ Logo +
+ ) : ( + + ); + } + + return ( + + ); +} diff --git a/web/src/refresh-components/avatars/CustomAgentAvatar.tsx b/web/src/refresh-components/avatars/CustomAgentAvatar.tsx new file mode 100644 index 0000000000..07a1233802 --- /dev/null +++ b/web/src/refresh-components/avatars/CustomAgentAvatar.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import SvgCheck from "@/icons/check"; +import SvgCode from "@/icons/code"; +import SvgTwoLineSmall from "@/icons/two-line-small"; +import SvgOnyxOctagon from "@/icons/onyx-octagon"; +import SvgSearch from "@/icons/search"; +import { IconProps } from "@/icons"; +import Text from "@/refresh-components/texts/Text"; + +interface IconConfig { + Icon: React.FunctionComponent; + className?: string; +} + +const iconMap: Record = { + search: { Icon: SvgSearch, className: "stroke-green-500" }, + check: { Icon: SvgCheck, className: "stroke-green-500" }, + code: { Icon: SvgCode, className: "stroke-orange-500" }, +}; + +interface SvgOctagonWrapperProps { + size: number; + children: React.ReactNode; +} + +function SvgOctagonWrapper({ size, children }: SvgOctagonWrapperProps) { + return ( +
+
+ {children} +
+ +
+ ); +} + +export interface CustomAgentAvatarProps { + name?: string; + src?: string; + iconName?: string; + + size?: number; +} + +export default function CustomAgentAvatar({ + name, + src, + iconName, + + size = 18, +}: CustomAgentAvatarProps) { + if (src) { + return ( +
+ {name} +
+ ); + } + + const iconConfig = iconName && iconMap[iconName]; + if (iconConfig) { + const { Icon, className } = iconConfig; + return ( + + + + ); + } + + // Display first letter of name if available, otherwise fall back to two-line-small icon + const trimmedName = name?.trim(); + const firstLetter = + trimmedName && trimmedName.length > 0 + ? trimmedName[0]!.toUpperCase() + : undefined; + const validFirstLetter = !!firstLetter && /^[a-zA-Z]$/.test(firstLetter); + if (validFirstLetter) { + return ( + + + {firstLetter} + + + ); + } + + return ( + + + + ); +} diff --git a/web/src/refresh-components/onboarding/constants.tsx b/web/src/refresh-components/onboarding/constants.tsx index f6a1a8ae89..a9fe22b38b 100644 --- a/web/src/refresh-components/onboarding/constants.tsx +++ b/web/src/refresh-components/onboarding/constants.tsx @@ -1,16 +1,8 @@ import { OnboardingStep } from "./types"; -import type { ReactNode } from "react"; import SvgGlobe from "@/icons/globe"; import SvgImage from "@/icons/image"; import SvgUsers from "@/icons/users"; import { FinalStepItemProps } from "./types"; -import { IconProps } from "@/icons"; -import { AzureIcon, GeminiIcon } from "@/components/icons/icons"; -import SvgClaude from "@/icons/claude"; -import SvgAws from "@/icons/aws"; -import SvgOllama from "@/icons/ollama"; -import SvgOpenai from "@/icons/openai"; -import SvgOpenrouter from "@/icons/openrouter"; import { LLMProviderName } from "@/app/admin/configuration/llm/interfaces"; import InlineExternalLink from "../InlineExternalLink"; @@ -90,19 +82,6 @@ export const FINAL_SETUP_CONFIG: FinalStepItemProps[] = [ }, ]; -export const PROVIDER_ICON_MAP: Record< - string, - React.FunctionComponent -> = { - [LLMProviderName.ANTHROPIC]: SvgClaude, - [LLMProviderName.BEDROCK]: SvgAws, - [LLMProviderName.AZURE]: AzureIcon, - [LLMProviderName.VERTEX_AI]: GeminiIcon, - [LLMProviderName.OPENAI]: SvgOpenai, - [LLMProviderName.OLLAMA_CHAT]: SvgOllama, - [LLMProviderName.OPENROUTER]: SvgOpenrouter, -}; - export const MODAL_CONTENT_MAP: Record = { [LLMProviderName.OPENAI]: { description: "Connect to OpenAI and set up your chatGPT models.", diff --git a/web/src/refresh-components/onboarding/steps/LLMStep.tsx b/web/src/refresh-components/onboarding/steps/LLMStep.tsx index 2ef87c5a13..1c749bb228 100644 --- a/web/src/refresh-components/onboarding/steps/LLMStep.tsx +++ b/web/src/refresh-components/onboarding/steps/LLMStep.tsx @@ -7,13 +7,13 @@ import Separator from "@/refresh-components/Separator"; import LLMProvider from "../components/LLMProvider"; import { OnboardingActions, OnboardingState, OnboardingStep } from "../types"; import { WellKnownLLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces"; -import { PROVIDER_ICON_MAP } from "../constants"; import LLMConnectionModal, { LLMConnectionModalProps, } from "@/refresh-components/onboarding/components/LLMConnectionModal"; import { cn } from "@/lib/utils"; import SvgCheckCircle from "@/icons/check-circle"; import { useCreateModal } from "@/refresh-components/contexts/ModalContext"; +import { getProviderIcon } from "@/app/admin/configuration/llm/utils"; type LLMStepProps = { state: OnboardingState; @@ -51,8 +51,7 @@ const StackedProviderIcons = ({ providers }: StackedProviderIconsProps) => { return (
{providers.slice(0, 3).map((provider, index) => { - const IconComponent = PROVIDER_ICON_MAP[provider]; - if (!IconComponent) return null; + const IconComponent = getProviderIcon(provider); return (
} onClick={() => route({ agentId: agent.id })} active={ typeof activeSidebarTab === "object" && diff --git a/web/src/sections/sidebar/utils.tsx b/web/src/sections/sidebar/utils.tsx deleted file mode 100644 index 022bccbd80..0000000000 --- a/web/src/sections/sidebar/utils.tsx +++ /dev/null @@ -1,50 +0,0 @@ -"use client"; - -import React from "react"; -import { IconProps } from "@/icons"; -import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; -import { - ART_ASSISTANT_ID, - DEFAULT_ASSISTANT_ID, - GENERAL_ASSISTANT_ID, - IMAGE_ASSISTANT_ID, -} from "@/lib/constants"; -import SvgLightbulbSimple from "@/icons/lightbulb-simple"; -import { OnyxIcon } from "@/components/icons/icons"; -import SvgImage from "@/icons/image"; -import { generateIdenticon } from "@/refresh-components/AgentIcon"; -import { buildImgUrl } from "@/app/chat/components/files/images/utils"; -import { cn } from "@/lib/utils"; - -export function getAgentIcon( - agent: MinimalPersonaSnapshot -): React.FunctionComponent { - if (agent.id === DEFAULT_ASSISTANT_ID) return OnyxIcon; - if (agent.id === GENERAL_ASSISTANT_ID) return SvgLightbulbSimple; - if (agent.id === IMAGE_ASSISTANT_ID || agent.id === ART_ASSISTANT_ID) - return SvgImage; - const uploadedImageId = agent.uploaded_image_id; - if (uploadedImageId) { - const UploadedImageIcon: React.FunctionComponent = ({ - className, - }) => ( -
- {agent.name} -
- ); - UploadedImageIcon.displayName = "SidebarUploadedAgentIcon"; - return UploadedImageIcon; - } - const GeneratedIcon: React.FunctionComponent = ({ className }) => ( -
- {generateIdenticon((agent.icon_shape || 0).toString(), 16)} -
- ); - GeneratedIcon.displayName = "SidebarGeneratedAgentIcon"; - return GeneratedIcon; -}