mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-28 12:39:54 +02:00
Support svg navigation items (#2542)
* Support SVG nav items * Handle specifying custom SVGs for navbar * Add comment * More comment * More comment
This commit is contained in:
parent
45f67368a2
commit
34c2aa0860
@ -64,19 +64,12 @@ def upsert_cloud_embedding_provider(
|
||||
def upsert_llm_provider(
|
||||
llm_provider: LLMProviderUpsertRequest,
|
||||
db_session: Session,
|
||||
is_creation: bool = True,
|
||||
) -> FullLLMProvider:
|
||||
existing_llm_provider = db_session.scalar(
|
||||
select(LLMProviderModel).where(LLMProviderModel.name == llm_provider.name)
|
||||
)
|
||||
if existing_llm_provider and is_creation:
|
||||
raise ValueError(f"LLM Provider with name {llm_provider.name} already exists")
|
||||
|
||||
if not existing_llm_provider:
|
||||
if not is_creation:
|
||||
raise ValueError(
|
||||
f"LLM Provider with name {llm_provider.name} does not exist"
|
||||
)
|
||||
existing_llm_provider = LLMProviderModel(name=llm_provider.name)
|
||||
db_session.add(existing_llm_provider)
|
||||
|
||||
|
@ -10,6 +10,7 @@ from danswer.auth.users import current_admin_user
|
||||
from danswer.auth.users import current_user
|
||||
from danswer.db.engine import get_session
|
||||
from danswer.db.llm import fetch_existing_llm_providers
|
||||
from danswer.db.llm import fetch_provider
|
||||
from danswer.db.llm import remove_llm_provider
|
||||
from danswer.db.llm import update_default_provider
|
||||
from danswer.db.llm import upsert_llm_provider
|
||||
@ -130,11 +131,20 @@ def put_llm_provider(
|
||||
_: User | None = Depends(current_admin_user),
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> FullLLMProvider:
|
||||
# validate request (e.g. if we're intending to create but the name already exists we should throw an error)
|
||||
# NOTE: may involve duplicate fetching to Postgres, but we're assuming SQLAlchemy is smart enough to cache
|
||||
# the result
|
||||
existing_provider = fetch_provider(db_session, llm_provider.name)
|
||||
if existing_provider and is_creation:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"LLM Provider with name {llm_provider.name} already exists",
|
||||
)
|
||||
|
||||
try:
|
||||
return upsert_llm_provider(
|
||||
llm_provider=llm_provider,
|
||||
db_session=db_session,
|
||||
is_creation=is_creation,
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.exception("Failed to upsert LLM Provider")
|
||||
|
@ -1,3 +1,4 @@
|
||||
from typing import Any
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
@ -6,8 +7,20 @@ from pydantic import Field
|
||||
|
||||
class NavigationItem(BaseModel):
|
||||
link: str
|
||||
icon: str
|
||||
title: str
|
||||
# Right now must be one of the FA icons
|
||||
icon: str | None = None
|
||||
# NOTE: SVG must not have a width / height specified
|
||||
# This is the actual SVG as a string. Done this way to reduce
|
||||
# complexity / having to store additional "logos" in Postgres
|
||||
svg_logo: str | None = None
|
||||
|
||||
@classmethod
|
||||
def model_validate(cls, *args: Any, **kwargs: Any) -> "NavigationItem":
|
||||
instance = super().model_validate(*args, **kwargs)
|
||||
if bool(instance.icon) == bool(instance.svg_logo):
|
||||
raise ValueError("Exactly one of fa_icon or svg_logo must be specified")
|
||||
return instance
|
||||
|
||||
|
||||
class EnterpriseSettings(BaseModel):
|
||||
|
@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
from copy import deepcopy
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
@ -22,6 +23,7 @@ from ee.danswer.db.standard_answer import (
|
||||
)
|
||||
from ee.danswer.server.enterprise_settings.models import AnalyticsScriptUpload
|
||||
from ee.danswer.server.enterprise_settings.models import EnterpriseSettings
|
||||
from ee.danswer.server.enterprise_settings.models import NavigationItem
|
||||
from ee.danswer.server.enterprise_settings.store import store_analytics_script
|
||||
from ee.danswer.server.enterprise_settings.store import (
|
||||
store_settings as store_ee_settings,
|
||||
@ -44,6 +46,13 @@ logger = setup_logger()
|
||||
_SEED_CONFIG_ENV_VAR_NAME = "ENV_SEED_CONFIGURATION"
|
||||
|
||||
|
||||
class NavigationItemSeed(BaseModel):
|
||||
link: str
|
||||
title: str
|
||||
# NOTE: SVG at this path must not have a width / height specified
|
||||
svg_path: str
|
||||
|
||||
|
||||
class SeedConfiguration(BaseModel):
|
||||
llms: list[LLMProviderUpsertRequest] | None = None
|
||||
admin_user_emails: list[str] | None = None
|
||||
@ -51,6 +60,10 @@ class SeedConfiguration(BaseModel):
|
||||
personas: list[CreatePersonaRequest] | None = None
|
||||
settings: Settings | None = None
|
||||
enterprise_settings: EnterpriseSettings | None = None
|
||||
|
||||
# allows for specifying custom navigation items that have your own custom SVG logos
|
||||
nav_item_overrides: list[NavigationItemSeed] | None = None
|
||||
|
||||
# Use existing `CUSTOM_ANALYTICS_SECRET_KEY` for reference
|
||||
analytics_script_path: str | None = None
|
||||
custom_tools: List[CustomToolSeed] | None = None
|
||||
@ -60,7 +73,7 @@ def _parse_env() -> SeedConfiguration | None:
|
||||
seed_config_str = os.getenv(_SEED_CONFIG_ENV_VAR_NAME)
|
||||
if not seed_config_str:
|
||||
return None
|
||||
seed_config = SeedConfiguration.parse_raw(seed_config_str)
|
||||
seed_config = SeedConfiguration.model_validate_json(seed_config_str)
|
||||
return seed_config
|
||||
|
||||
|
||||
@ -152,9 +165,35 @@ def _seed_settings(settings: Settings) -> None:
|
||||
|
||||
|
||||
def _seed_enterprise_settings(seed_config: SeedConfiguration) -> None:
|
||||
if seed_config.enterprise_settings is not None:
|
||||
if (
|
||||
seed_config.enterprise_settings is not None
|
||||
or seed_config.nav_item_overrides is not None
|
||||
):
|
||||
final_enterprise_settings = (
|
||||
deepcopy(seed_config.enterprise_settings)
|
||||
if seed_config.enterprise_settings
|
||||
else EnterpriseSettings()
|
||||
)
|
||||
|
||||
final_nav_items = final_enterprise_settings.custom_nav_items
|
||||
if seed_config.nav_item_overrides is not None:
|
||||
final_nav_items = []
|
||||
for item in seed_config.nav_item_overrides:
|
||||
with open(item.svg_path, "r") as file:
|
||||
svg_content = file.read().strip()
|
||||
|
||||
final_nav_items.append(
|
||||
NavigationItem(
|
||||
link=item.link,
|
||||
title=item.title,
|
||||
svg_logo=svg_content,
|
||||
)
|
||||
)
|
||||
|
||||
final_enterprise_settings.custom_nav_items = final_nav_items
|
||||
|
||||
logger.notice("Seeding enterprise settings")
|
||||
store_ee_settings(seed_config.enterprise_settings)
|
||||
store_ee_settings(final_enterprise_settings)
|
||||
|
||||
|
||||
def _seed_logo(db_session: Session, logo_path: str | None) -> None:
|
||||
|
@ -18,7 +18,8 @@ export interface Notification {
|
||||
|
||||
export interface NavigationItem {
|
||||
link: string;
|
||||
icon: string;
|
||||
icon?: string;
|
||||
svg_logo?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,9 @@ export function UserDropdown({
|
||||
combinedSettings?.enterpriseSettings?.custom_nav_items || [];
|
||||
|
||||
useEffect(() => {
|
||||
const iconNames = customNavItems.map((item) => item.icon);
|
||||
const iconNames = customNavItems
|
||||
.map((item) => item.icon)
|
||||
.filter((icon) => icon) as string[];
|
||||
preloadIcons(iconNames);
|
||||
}, [customNavItems]);
|
||||
|
||||
@ -141,10 +143,34 @@ export function UserDropdown({
|
||||
key={i}
|
||||
href={item.link}
|
||||
icon={
|
||||
<DynamicFaIcon
|
||||
name={item.icon}
|
||||
className="h-4 w-4 my-auto mr-2"
|
||||
/>
|
||||
item.svg_logo ? (
|
||||
<div
|
||||
className="
|
||||
h-4
|
||||
w-4
|
||||
my-auto
|
||||
mr-2
|
||||
overflow-hidden
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
aria-label={item.title}
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="100%"
|
||||
height="100%"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
dangerouslySetInnerHTML={{ __html: item.svg_logo }}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<DynamicFaIcon
|
||||
name={item.icon!}
|
||||
className="h-4 w-4 my-auto mr-2"
|
||||
/>
|
||||
)
|
||||
}
|
||||
label={item.title}
|
||||
/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user