mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-06-04 03:59:25 +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(
|
def upsert_llm_provider(
|
||||||
llm_provider: LLMProviderUpsertRequest,
|
llm_provider: LLMProviderUpsertRequest,
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
is_creation: bool = True,
|
|
||||||
) -> FullLLMProvider:
|
) -> FullLLMProvider:
|
||||||
existing_llm_provider = db_session.scalar(
|
existing_llm_provider = db_session.scalar(
|
||||||
select(LLMProviderModel).where(LLMProviderModel.name == llm_provider.name)
|
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 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)
|
existing_llm_provider = LLMProviderModel(name=llm_provider.name)
|
||||||
db_session.add(existing_llm_provider)
|
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.auth.users import current_user
|
||||||
from danswer.db.engine import get_session
|
from danswer.db.engine import get_session
|
||||||
from danswer.db.llm import fetch_existing_llm_providers
|
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 remove_llm_provider
|
||||||
from danswer.db.llm import update_default_provider
|
from danswer.db.llm import update_default_provider
|
||||||
from danswer.db.llm import upsert_llm_provider
|
from danswer.db.llm import upsert_llm_provider
|
||||||
@ -130,11 +131,20 @@ def put_llm_provider(
|
|||||||
_: User | None = Depends(current_admin_user),
|
_: User | None = Depends(current_admin_user),
|
||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
) -> FullLLMProvider:
|
) -> 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:
|
try:
|
||||||
return upsert_llm_provider(
|
return upsert_llm_provider(
|
||||||
llm_provider=llm_provider,
|
llm_provider=llm_provider,
|
||||||
db_session=db_session,
|
db_session=db_session,
|
||||||
is_creation=is_creation,
|
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.exception("Failed to upsert LLM Provider")
|
logger.exception("Failed to upsert LLM Provider")
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from typing import Any
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@ -6,8 +7,20 @@ from pydantic import Field
|
|||||||
|
|
||||||
class NavigationItem(BaseModel):
|
class NavigationItem(BaseModel):
|
||||||
link: str
|
link: str
|
||||||
icon: str
|
|
||||||
title: 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):
|
class EnterpriseSettings(BaseModel):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from copy import deepcopy
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
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 AnalyticsScriptUpload
|
||||||
from ee.danswer.server.enterprise_settings.models import EnterpriseSettings
|
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_analytics_script
|
||||||
from ee.danswer.server.enterprise_settings.store import (
|
from ee.danswer.server.enterprise_settings.store import (
|
||||||
store_settings as store_ee_settings,
|
store_settings as store_ee_settings,
|
||||||
@ -44,6 +46,13 @@ logger = setup_logger()
|
|||||||
_SEED_CONFIG_ENV_VAR_NAME = "ENV_SEED_CONFIGURATION"
|
_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):
|
class SeedConfiguration(BaseModel):
|
||||||
llms: list[LLMProviderUpsertRequest] | None = None
|
llms: list[LLMProviderUpsertRequest] | None = None
|
||||||
admin_user_emails: list[str] | None = None
|
admin_user_emails: list[str] | None = None
|
||||||
@ -51,6 +60,10 @@ class SeedConfiguration(BaseModel):
|
|||||||
personas: list[CreatePersonaRequest] | None = None
|
personas: list[CreatePersonaRequest] | None = None
|
||||||
settings: Settings | None = None
|
settings: Settings | None = None
|
||||||
enterprise_settings: EnterpriseSettings | 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
|
# Use existing `CUSTOM_ANALYTICS_SECRET_KEY` for reference
|
||||||
analytics_script_path: str | None = None
|
analytics_script_path: str | None = None
|
||||||
custom_tools: List[CustomToolSeed] | 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)
|
seed_config_str = os.getenv(_SEED_CONFIG_ENV_VAR_NAME)
|
||||||
if not seed_config_str:
|
if not seed_config_str:
|
||||||
return None
|
return None
|
||||||
seed_config = SeedConfiguration.parse_raw(seed_config_str)
|
seed_config = SeedConfiguration.model_validate_json(seed_config_str)
|
||||||
return seed_config
|
return seed_config
|
||||||
|
|
||||||
|
|
||||||
@ -152,9 +165,35 @@ def _seed_settings(settings: Settings) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _seed_enterprise_settings(seed_config: SeedConfiguration) -> 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")
|
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:
|
def _seed_logo(db_session: Session, logo_path: str | None) -> None:
|
||||||
|
@ -18,7 +18,8 @@ export interface Notification {
|
|||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
link: string;
|
link: string;
|
||||||
icon: string;
|
icon?: string;
|
||||||
|
svg_logo?: string;
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,9 @@ export function UserDropdown({
|
|||||||
combinedSettings?.enterpriseSettings?.custom_nav_items || [];
|
combinedSettings?.enterpriseSettings?.custom_nav_items || [];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const iconNames = customNavItems.map((item) => item.icon);
|
const iconNames = customNavItems
|
||||||
|
.map((item) => item.icon)
|
||||||
|
.filter((icon) => icon) as string[];
|
||||||
preloadIcons(iconNames);
|
preloadIcons(iconNames);
|
||||||
}, [customNavItems]);
|
}, [customNavItems]);
|
||||||
|
|
||||||
@ -141,10 +143,34 @@ export function UserDropdown({
|
|||||||
key={i}
|
key={i}
|
||||||
href={item.link}
|
href={item.link}
|
||||||
icon={
|
icon={
|
||||||
|
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
|
<DynamicFaIcon
|
||||||
name={item.icon}
|
name={item.icon!}
|
||||||
className="h-4 w-4 my-auto mr-2"
|
className="h-4 w-4 my-auto mr-2"
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
label={item.title}
|
label={item.title}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user