mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-02-20 09:15:47 +00:00
Compare commits
16 Commits
remove_emp
...
remove_end
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14f57d6475 | ||
|
|
1a7d627949 | ||
|
|
f318e302c5 | ||
|
|
7384ca8768 | ||
|
|
5be457e321 | ||
|
|
8223dc763d | ||
|
|
ea406c55cd | ||
|
|
ea80cdce02 | ||
|
|
40a0f71960 | ||
|
|
fcb94f1173 | ||
|
|
cc40f0d27b | ||
|
|
75dd103238 | ||
|
|
aafcf7af55 | ||
|
|
1201ed5ac0 | ||
|
|
a60613ec11 | ||
|
|
5640230f5b |
@@ -21,6 +21,8 @@ celery_app.config_from_object("danswer.background.celery.configs.beat")
|
||||
@beat_init.connect
|
||||
def on_beat_init(sender: Any, **kwargs: Any) -> None:
|
||||
logger.info("beat_init signal received.")
|
||||
|
||||
# celery beat shouldn't touch the db at all. But just setting a low minimum here.
|
||||
SqlEngine.set_app_name(POSTGRES_CELERY_BEAT_APP_NAME)
|
||||
SqlEngine.init_engine(pool_size=2, max_overflow=0)
|
||||
app_base.wait_for_redis(sender, **kwargs)
|
||||
|
||||
@@ -58,7 +58,7 @@ def on_worker_init(sender: Any, **kwargs: Any) -> None:
|
||||
logger.info(f"Multiprocessing start method: {multiprocessing.get_start_method()}")
|
||||
|
||||
SqlEngine.set_app_name(POSTGRES_CELERY_WORKER_HEAVY_APP_NAME)
|
||||
SqlEngine.init_engine(pool_size=8, max_overflow=0)
|
||||
SqlEngine.init_engine(pool_size=4, max_overflow=12)
|
||||
|
||||
app_base.wait_for_redis(sender, **kwargs)
|
||||
app_base.on_secondary_worker_init(sender, **kwargs)
|
||||
|
||||
@@ -166,19 +166,6 @@ def on_worker_init(sender: Any, **kwargs: Any) -> None:
|
||||
r.delete(key)
|
||||
|
||||
|
||||
# @worker_process_init.connect
|
||||
# def on_worker_process_init(sender: Any, **kwargs: Any) -> None:
|
||||
# """This only runs inside child processes when the worker is in pool=prefork mode.
|
||||
# This may be technically unnecessary since we're finding prefork pools to be
|
||||
# unstable and currently aren't planning on using them."""
|
||||
# logger.info("worker_process_init signal received.")
|
||||
# SqlEngine.set_app_name(POSTGRES_CELERY_WORKER_INDEXING_CHILD_APP_NAME)
|
||||
# SqlEngine.init_engine(pool_size=5, max_overflow=0)
|
||||
|
||||
# # https://stackoverflow.com/questions/43944787/sqlalchemy-celery-with-scoped-session-error
|
||||
# SqlEngine.get_engine().dispose(close=False)
|
||||
|
||||
|
||||
@worker_ready.connect
|
||||
def on_worker_ready(sender: Any, **kwargs: Any) -> None:
|
||||
app_base.on_worker_ready(sender, **kwargs)
|
||||
|
||||
@@ -34,6 +34,7 @@ from danswer.db.engine import get_session_with_tenant
|
||||
from danswer.db.enums import ConnectorCredentialPairStatus
|
||||
from danswer.db.models import ConnectorCredentialPair
|
||||
from danswer.redis.redis_pool import get_redis_client
|
||||
from danswer.utils.logger import pruning_ctx
|
||||
from danswer.utils.logger import setup_logger
|
||||
|
||||
logger = setup_logger()
|
||||
@@ -229,10 +230,15 @@ def connector_pruning_generator_task(
|
||||
and compares those IDs to locally stored documents and deletes all locally stored IDs missing
|
||||
from the most recently pulled document ID list"""
|
||||
|
||||
r = get_redis_client(tenant_id=tenant_id)
|
||||
pruning_ctx_dict = pruning_ctx.get()
|
||||
pruning_ctx_dict["cc_pair_id"] = cc_pair_id
|
||||
pruning_ctx_dict["request_id"] = self.request.id
|
||||
pruning_ctx.set(pruning_ctx_dict)
|
||||
|
||||
rcp = RedisConnectorPruning(cc_pair_id)
|
||||
|
||||
r = get_redis_client(tenant_id=tenant_id)
|
||||
|
||||
lock = r.lock(
|
||||
DanswerRedisLocks.PRUNING_LOCK_PREFIX + f"_{rcp._id}",
|
||||
timeout=CELERY_PRUNING_LOCK_TIMEOUT,
|
||||
|
||||
@@ -11,7 +11,8 @@ from typing import Any
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
|
||||
from danswer.db.engine import get_sqlalchemy_engine
|
||||
from danswer.configs.constants import POSTGRES_CELERY_WORKER_INDEXING_CHILD_APP_NAME
|
||||
from danswer.db.engine import SqlEngine
|
||||
from danswer.utils.logger import setup_logger
|
||||
|
||||
logger = setup_logger()
|
||||
@@ -37,7 +38,9 @@ def _initializer(
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
|
||||
get_sqlalchemy_engine().dispose(close=False)
|
||||
logger.info("Initializing spawned worker child process.")
|
||||
SqlEngine.set_app_name(POSTGRES_CELERY_WORKER_INDEXING_CHILD_APP_NAME)
|
||||
SqlEngine.init_engine(pool_size=4, max_overflow=12, pool_recycle=60)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -91,12 +91,13 @@ class ConfluenceConnector(LoadConnector, PollConnector, SlimConnector):
|
||||
cql_page_query += f" and id='{page_id}'"
|
||||
|
||||
self.cql_page_query = cql_page_query
|
||||
self.cql_label_filter = ""
|
||||
self.cql_time_filter = ""
|
||||
|
||||
self.cql_label_filter = ""
|
||||
if labels_to_skip:
|
||||
labels_to_skip = list(set(labels_to_skip))
|
||||
comma_separated_labels = ",".join(labels_to_skip)
|
||||
self.cql_label_filter = f"&label not in ({comma_separated_labels})"
|
||||
comma_separated_labels = ",".join(f"'{label}'" for label in labels_to_skip)
|
||||
self.cql_label_filter = f" and label not in ({comma_separated_labels})"
|
||||
|
||||
def load_credentials(self, credentials: dict[str, Any]) -> dict[str, Any] | None:
|
||||
# see https://github.com/atlassian-api/atlassian-python-api/blob/master/atlassian/rest_client.py
|
||||
@@ -125,7 +126,8 @@ class ConfluenceConnector(LoadConnector, PollConnector, SlimConnector):
|
||||
for comment in comments:
|
||||
comment_string += "\nComment:\n"
|
||||
comment_string += extract_text_from_confluence_html(
|
||||
confluence_client=self.confluence_client, confluence_object=comment
|
||||
confluence_client=self.confluence_client,
|
||||
confluence_object=comment,
|
||||
)
|
||||
|
||||
return comment_string
|
||||
|
||||
@@ -231,6 +231,7 @@ def _get_chunks_via_visit_api(
|
||||
return document_chunks
|
||||
|
||||
|
||||
@retry(tries=10, delay=1, backoff=2)
|
||||
def get_all_vespa_ids_for_document_id(
|
||||
document_id: str,
|
||||
index_name: str,
|
||||
|
||||
@@ -104,6 +104,7 @@ class TenantRedis(redis.Redis):
|
||||
"startswith",
|
||||
"sadd",
|
||||
"srem",
|
||||
"scard",
|
||||
] # Regular methods that need simple prefixing
|
||||
|
||||
if item == "scan_iter":
|
||||
|
||||
@@ -81,18 +81,6 @@ def get_cc_source_full_info(
|
||||
]
|
||||
|
||||
|
||||
@router.get("/credential/{id}")
|
||||
def list_credentials_by_id(
|
||||
user: User | None = Depends(current_user),
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> list[CredentialSnapshot]:
|
||||
credentials = fetch_credentials(db_session=db_session, user=user)
|
||||
return [
|
||||
CredentialSnapshot.from_credential_db_model(credential)
|
||||
for credential in credentials
|
||||
]
|
||||
|
||||
|
||||
@router.delete("/admin/credential/{credential_id}")
|
||||
def delete_credential_by_id_admin(
|
||||
credential_id: int,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import contextvars
|
||||
import logging
|
||||
import os
|
||||
from collections.abc import MutableMapping
|
||||
@@ -16,6 +17,10 @@ from shared_configs.configs import TENANT_ID_PREFIX
|
||||
|
||||
logging.addLevelName(logging.INFO + 5, "NOTICE")
|
||||
|
||||
pruning_ctx: contextvars.ContextVar[dict[str, Any]] = contextvars.ContextVar(
|
||||
"pruning_ctx", default=dict()
|
||||
)
|
||||
|
||||
|
||||
class IndexAttemptSingleton:
|
||||
"""Used to tell if this process is an indexing job, and if so what is the
|
||||
@@ -61,14 +66,22 @@ class DanswerLoggingAdapter(logging.LoggerAdapter):
|
||||
) -> tuple[str, MutableMapping[str, Any]]:
|
||||
# If this is an indexing job, add the attempt ID to the log message
|
||||
# This helps filter the logs for this specific indexing
|
||||
attempt_id = IndexAttemptSingleton.get_index_attempt_id()
|
||||
index_attempt_id = IndexAttemptSingleton.get_index_attempt_id()
|
||||
cc_pair_id = IndexAttemptSingleton.get_connector_credential_pair_id()
|
||||
|
||||
if attempt_id is not None:
|
||||
msg = f"[Attempt: {attempt_id}] {msg}"
|
||||
pruning_ctx_dict = pruning_ctx.get()
|
||||
if len(pruning_ctx_dict) > 0:
|
||||
if "request_id" in pruning_ctx_dict:
|
||||
msg = f"[Prune: {pruning_ctx_dict['request_id']}] {msg}"
|
||||
|
||||
if cc_pair_id is not None:
|
||||
msg = f"[CC Pair: {cc_pair_id}] {msg}"
|
||||
if "cc_pair_id" in pruning_ctx_dict:
|
||||
msg = f"[CC Pair: {pruning_ctx_dict['cc_pair_id']}] {msg}"
|
||||
else:
|
||||
if index_attempt_id is not None:
|
||||
msg = f"[Index Attempt: {index_attempt_id}] {msg}"
|
||||
|
||||
if cc_pair_id is not None:
|
||||
msg = f"[CC Pair: {cc_pair_id}] {msg}"
|
||||
|
||||
# Add tenant information if it differs from default
|
||||
# This will always be the case for authenticated API requests
|
||||
|
||||
@@ -10,6 +10,11 @@ from ee.danswer.configs.app_configs import API_KEY_HASH_ROUNDS
|
||||
|
||||
|
||||
_API_KEY_HEADER_NAME = "Authorization"
|
||||
# NOTE for others who are curious: In the context of a header, "X-" often refers
|
||||
# to non-standard, experimental, or custom headers in HTTP or other protocols. It
|
||||
# indicates that the header is not part of the official standards defined by
|
||||
# organizations like the Internet Engineering Task Force (IETF).
|
||||
_API_KEY_HEADER_ALTERNATIVE_NAME = "X-Danswer-Authorization"
|
||||
_BEARER_PREFIX = "Bearer "
|
||||
_API_KEY_PREFIX = "dn_"
|
||||
_API_KEY_LEN = 192
|
||||
@@ -43,7 +48,9 @@ def build_displayable_api_key(api_key: str) -> str:
|
||||
|
||||
|
||||
def get_hashed_api_key_from_request(request: Request) -> str | None:
|
||||
raw_api_key_header = request.headers.get(_API_KEY_HEADER_NAME)
|
||||
raw_api_key_header = request.headers.get(
|
||||
_API_KEY_HEADER_ALTERNATIVE_NAME
|
||||
) or request.headers.get(_API_KEY_HEADER_NAME)
|
||||
if raw_api_key_header is None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ from pywikibot.family import Family # type: ignore[import-untyped]
|
||||
|
||||
from danswer.connectors.mediawiki import family
|
||||
|
||||
|
||||
# Disabling these tests as they are flaky and rely on external wikis that are maintained by just fan communities
|
||||
|
||||
|
||||
NON_BUILTIN_WIKIS: Final[list[tuple[str, str]]] = [
|
||||
("https://fallout.fandom.com", "falloutwiki"),
|
||||
("https://harrypotter.fandom.com/wiki/", "harrypotterwiki"),
|
||||
@@ -19,6 +23,7 @@ NON_BUILTIN_WIKIS: Final[list[tuple[str, str]]] = [
|
||||
|
||||
|
||||
# TODO: Add support for more builtin family types from `pywikibot.families`.
|
||||
@pytest.mark.skip(reason="Temporarily skipped")
|
||||
@pytest.mark.parametrize(
|
||||
"url, name, expected",
|
||||
[
|
||||
@@ -48,6 +53,7 @@ def test_family_class_dispatch_builtins(
|
||||
assert family.family_class_dispatch(url, name) == expected
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Temporarily skipped")
|
||||
@pytest.mark.parametrize("url, name", NON_BUILTIN_WIKIS)
|
||||
def test_family_class_dispatch_on_non_builtins_generates_new_class_fast(
|
||||
url: str, name: str, mocker: MockFixture
|
||||
@@ -58,6 +64,7 @@ def test_family_class_dispatch_on_non_builtins_generates_new_class_fast(
|
||||
mock_generate_family_class.assert_called_once_with(url, name)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Temporarily skipped")
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.parametrize("url, name", NON_BUILTIN_WIKIS)
|
||||
def test_family_class_dispatch_on_non_builtins_generates_new_class_slow(
|
||||
|
||||
@@ -253,9 +253,9 @@ export default function AddConnector({
|
||||
|
||||
// Apply advanced configuration-specific transforms.
|
||||
const advancedConfiguration: any = {
|
||||
pruneFreq: (pruneFreq || defaultPruneFreqDays) * 60 * 60 * 24,
|
||||
pruneFreq: (pruneFreq ?? defaultPruneFreqDays) * 60 * 60 * 24,
|
||||
indexingStart: convertStringToDateTime(indexingStart),
|
||||
refreshFreq: (refreshFreq || defaultRefreshFreqMinutes) * 60,
|
||||
refreshFreq: (refreshFreq ?? defaultRefreshFreqMinutes) * 60,
|
||||
};
|
||||
|
||||
// Google sites-specific handling
|
||||
|
||||
@@ -153,13 +153,15 @@ export default function SidebarWrapper<T extends object>({
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`mt-4 w-full ${size == "lg" ? "max-w-4xl" : "max-w-3xl"} mx-auto`}
|
||||
className={`mt-4 w-full ${
|
||||
size == "lg" ? "max-w-4xl" : "max-w-3xl"
|
||||
} mx-auto`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FixedLogo />
|
||||
<FixedLogo backgroundToggled={toggledSidebar || showDocSidebar} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2512,7 +2512,7 @@ export function ChatPage({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<FixedLogo />
|
||||
<FixedLogo backgroundToggled={toggledSidebar || showDocSidebar} />
|
||||
</div>
|
||||
</div>
|
||||
<DocumentSidebar
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Hoverable } from "@/components/Hoverable";
|
||||
import { Popover } from "@/components/popover/Popover";
|
||||
import { StarFeedback } from "@/components/icons/icons";
|
||||
import { IconType } from "react-icons";
|
||||
import { FiRefreshCw } from "react-icons/fi";
|
||||
|
||||
export function RegenerateDropdown({
|
||||
options,
|
||||
@@ -68,7 +69,7 @@ export function RegenerateDropdown({
|
||||
py-1.5
|
||||
"
|
||||
>
|
||||
Pick a model
|
||||
Regenerate with
|
||||
</p>
|
||||
{options.map((option, ind) => {
|
||||
const isSelected = option.value === selected;
|
||||
@@ -92,7 +93,7 @@ export function RegenerateDropdown({
|
||||
content={
|
||||
<div onClick={() => toggleDropdownVisible(!isOpen)}>
|
||||
{!alternate ? (
|
||||
<Hoverable size={16} icon={StarFeedback as IconType} />
|
||||
<Hoverable size={16} icon={FiRefreshCw as IconType} />
|
||||
) : (
|
||||
<Hoverable
|
||||
size={16}
|
||||
|
||||
@@ -299,7 +299,9 @@ export const AIMessage = ({
|
||||
className={"py-5 ml-4 px-5 relative flex "}
|
||||
>
|
||||
<div
|
||||
className={`mx-auto ${shared ? "w-full" : "w-[90%]"} max-w-message-max`}
|
||||
className={`mx-auto ${
|
||||
shared ? "w-full" : "w-[90%]"
|
||||
} max-w-message-max`}
|
||||
>
|
||||
<div className={`desktop:mr-12 ${!shared && "mobile:ml-0 md:ml-8"}`}>
|
||||
<div className="flex">
|
||||
@@ -545,9 +547,21 @@ export const AIMessage = ({
|
||||
className={`
|
||||
absolute -bottom-5
|
||||
z-10
|
||||
invisible ${(isHovering || isRegenerateHovered || settings?.isMobile) && "!visible"}
|
||||
opacity-0 ${(isHovering || isRegenerateHovered || settings?.isMobile) && "!opacity-100"}
|
||||
translate-y-2 ${(isHovering || settings?.isMobile) && "!translate-y-0"}
|
||||
invisible ${
|
||||
(isHovering ||
|
||||
isRegenerateHovered ||
|
||||
settings?.isMobile) &&
|
||||
"!visible"
|
||||
}
|
||||
opacity-0 ${
|
||||
(isHovering ||
|
||||
isRegenerateHovered ||
|
||||
settings?.isMobile) &&
|
||||
"!opacity-100"
|
||||
}
|
||||
translate-y-2 ${
|
||||
(isHovering || settings?.isMobile) && "!translate-y-0"
|
||||
}
|
||||
transition-transform duration-300 ease-in-out
|
||||
flex md:flex-row gap-x-0.5 bg-background-125/40 -mx-1.5 p-1.5 rounded-lg
|
||||
`}
|
||||
@@ -703,9 +717,7 @@ export const HumanMessage = ({
|
||||
}, [isEditing]);
|
||||
|
||||
const handleEditSubmit = () => {
|
||||
if (editedContent.trim() !== content.trim()) {
|
||||
onEdit?.(editedContent);
|
||||
}
|
||||
onEdit?.(editedContent);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
@@ -721,7 +733,9 @@ export const HumanMessage = ({
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<div
|
||||
className={`text-user-text mx-auto ${shared ? "w-full" : "w-[90%]"} max-w-[790px]`}
|
||||
className={`text-user-text mx-auto ${
|
||||
shared ? "w-full" : "w-[90%]"
|
||||
} max-w-[790px]`}
|
||||
>
|
||||
<div className="xl:ml-8">
|
||||
<div className="flex flex-col mr-4">
|
||||
|
||||
@@ -142,7 +142,11 @@ export function ChatSessionDisplay({
|
||||
{chatName || `Chat ${chatSession.id}`}
|
||||
<span
|
||||
className={`absolute right-0 top-0 h-full w-8 bg-gradient-to-r from-transparent
|
||||
${isSelected ? "to-background-200" : " to-background-100 group-hover:to-background-200"} `}
|
||||
${
|
||||
isSelected
|
||||
? "to-background-chat-selected"
|
||||
: "group-hover:to-background-chat-hover"
|
||||
} `}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
@@ -176,7 +180,9 @@ export function ChatSessionDisplay({
|
||||
This chat will expire{" "}
|
||||
{daysUntilExpiration < 1
|
||||
? "today"
|
||||
: `in ${daysUntilExpiration} day${daysUntilExpiration !== 1 ? "s" : ""}`}
|
||||
: `in ${daysUntilExpiration} day${
|
||||
daysUntilExpiration !== 1 ? "s" : ""
|
||||
}`}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -94,7 +94,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
bg-background-sidebar
|
||||
w-full
|
||||
border-r
|
||||
border-border
|
||||
border-sidebar-border
|
||||
flex
|
||||
flex-col relative
|
||||
h-screen
|
||||
|
||||
@@ -27,7 +27,6 @@ function BackToDanswerButton() {
|
||||
Back to {enterpriseSettings?.application_name || "Danswer Chat"}
|
||||
</Button>
|
||||
</div>
|
||||
pr
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,12 @@ import Link from "next/link";
|
||||
import { useContext } from "react";
|
||||
import { FiSidebar } from "react-icons/fi";
|
||||
|
||||
export default function FixedLogo() {
|
||||
export default function FixedLogo({
|
||||
// Whether the sidebar is toggled or not
|
||||
backgroundToggled,
|
||||
}: {
|
||||
backgroundToggled?: boolean;
|
||||
}) {
|
||||
const combinedSettings = useContext(SettingsContext);
|
||||
const settings = combinedSettings?.settings;
|
||||
const enterpriseSettings = combinedSettings?.enterpriseSettings;
|
||||
@@ -28,13 +33,17 @@ export default function FixedLogo() {
|
||||
<div className="w-full">
|
||||
{enterpriseSettings && enterpriseSettings.application_name ? (
|
||||
<div>
|
||||
<HeaderTitle>{enterpriseSettings.application_name}</HeaderTitle>
|
||||
<HeaderTitle backgroundToggled={backgroundToggled}>
|
||||
{enterpriseSettings.application_name}
|
||||
</HeaderTitle>
|
||||
{!NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED && (
|
||||
<p className="text-xs text-subtle">Powered by Danswer</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<HeaderTitle>Danswer</HeaderTitle>
|
||||
<HeaderTitle backgroundToggled={backgroundToggled}>
|
||||
Danswer
|
||||
</HeaderTitle>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ const ToggleSwitch = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-gray-100 mobile:mt-8 flex rounded-full p-1">
|
||||
<div className="bg-background-toggle mobile:mt-8 flex rounded-full p-1">
|
||||
<div
|
||||
className={`absolute mobile:mt-8 top-1 bottom-1 ${
|
||||
activeTab === "chat" ? "w-[45%]" : "w-[50%]"
|
||||
@@ -148,7 +148,9 @@ export default function FunctionalWrapper({
|
||||
{(!settings ||
|
||||
(settings.search_page_enabled && settings.chat_page_enabled)) && (
|
||||
<div
|
||||
className={`mobile:hidden z-30 flex fixed ${chatBannerPresent ? (twoLines ? "top-20" : "top-14") : "top-4"} left-1/2 transform -translate-x-1/2`}
|
||||
className={`mobile:hidden z-30 flex fixed ${
|
||||
chatBannerPresent ? (twoLines ? "top-20" : "top-14") : "top-4"
|
||||
} left-1/2 transform -translate-x-1/2`}
|
||||
>
|
||||
<div
|
||||
style={{ transition: "width 0.30s ease-out" }}
|
||||
|
||||
@@ -100,7 +100,11 @@ export function BasicSelectable({
|
||||
${padding == "extra" && "p-1.5"}
|
||||
select-none
|
||||
${hasBorder ? "border border-border" : ""}
|
||||
${selected ? "bg-hover" : "hover:bg-hover"}
|
||||
${
|
||||
selected
|
||||
? "bg-background-chat-selected"
|
||||
: "hover:bg-background-chat-hover"
|
||||
}
|
||||
${fullWidth ? "w-full" : ""}`}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function ClientLayout({
|
||||
return (
|
||||
<div className="h-screen overflow-y-hidden">
|
||||
<div className="flex h-full">
|
||||
<div className="flex-none text-text-settings-sidebar bg-background-settings-sidebar w-[250px] z-20 pt-4 pb-8 h-full border-r border-border miniscroll overflow-auto">
|
||||
<div className="flex-none text-text-settings-sidebar bg-background-sidebar w-[250px] z-20 pt-4 pb-8 h-full border-r border-border miniscroll overflow-auto">
|
||||
<AdminSidebar
|
||||
collections={[
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
||||
<div className="flex-grow min-w-0 my-auto">
|
||||
{enterpriseSettings && enterpriseSettings.application_name ? (
|
||||
<div className="w-full">
|
||||
<HeaderTitle>
|
||||
<HeaderTitle backgroundToggled={true}>
|
||||
{enterpriseSettings.application_name}
|
||||
</HeaderTitle>
|
||||
{!NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED && (
|
||||
@@ -65,7 +65,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<HeaderTitle>Danswer</HeaderTitle>
|
||||
<HeaderTitle backgroundToggled={true}>Danswer</HeaderTitle>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,7 +74,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
||||
</div>
|
||||
<div className="flex w-full justify-center">
|
||||
<Link href={"/chat"}>
|
||||
<button className="text-sm flex items-center block w-52 py-2.5 flex px-2 text-left bg-background-200 hover:bg-background-200/80 cursor-pointer rounded">
|
||||
<button className="text-sm flex items-center block w-52 py-2.5 flex px-2 text-left text-text-back-button bg-background-back-button hover:bg-opacity-80 cursor-pointer rounded">
|
||||
<BackIcon className="my-auto" size={18} />
|
||||
<p className="ml-1 break-words line-clamp-2 ellipsis leading-none">
|
||||
Back to{" "}
|
||||
|
||||
@@ -2,13 +2,23 @@
|
||||
|
||||
import React from "react";
|
||||
|
||||
export function HeaderTitle({ children }: { children: JSX.Element | string }) {
|
||||
export function HeaderTitle({
|
||||
children,
|
||||
backgroundToggled,
|
||||
}: {
|
||||
children: JSX.Element | string;
|
||||
backgroundToggled?: boolean;
|
||||
}) {
|
||||
const isString = typeof children === "string";
|
||||
const textSize = isString && children.length > 10 ? "text-xl" : "text-2xl";
|
||||
|
||||
return (
|
||||
<h1
|
||||
className={`${textSize} break-words line-clamp-2 ellipsis text-strong leading-none font-bold`}
|
||||
className={`${textSize} ${
|
||||
backgroundToggled
|
||||
? "text-text-sidebar-toggled-header"
|
||||
: "text-text-sidebar-header"
|
||||
} break-words line-clamp-2 ellipsis text-strong leading-none font-bold`}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
|
||||
@@ -37,7 +37,9 @@ export default function LogoType({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${hideOnMobile && "mobile:hidden"} z-[100] mb-auto shrink-0 flex items-center text-xl font-bold`}
|
||||
className={`${
|
||||
hideOnMobile && "mobile:hidden"
|
||||
} z-[100] mb-auto shrink-0 flex items-center text-xl font-bold`}
|
||||
>
|
||||
{toggleSidebar && page == "chat" ? (
|
||||
<button
|
||||
@@ -55,7 +57,9 @@ export default function LogoType({
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`cursor-pointer ${showArrow ? "desktop:invisible" : "invisible"} break-words inline-block w-fit ml-2 text-text-700 text-xl`}
|
||||
className={`cursor-pointer ${
|
||||
showArrow ? "desktop:invisible" : "invisible"
|
||||
} break-words inline-block w-fit ml-2 text-text-700 text-xl`}
|
||||
>
|
||||
<div className="max-w-[175px]">
|
||||
{enterpriseSettings && enterpriseSettings.application_name ? (
|
||||
@@ -111,9 +115,9 @@ export default function LogoType({
|
||||
}}
|
||||
>
|
||||
{!toggled && !combinedSettings?.isMobile ? (
|
||||
<RightToLineIcon />
|
||||
<RightToLineIcon className="text-sidebar-toggle" />
|
||||
) : (
|
||||
<LeftToLineIcon />
|
||||
<LeftToLineIcon className="text-sidebar-toggle" />
|
||||
)}
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -70,7 +70,11 @@ export default function SearchAnswer({
|
||||
return (
|
||||
<div
|
||||
ref={answerContainerRef}
|
||||
className={`my-4 ${searchAnswerExpanded ? "min-h-[16rem]" : "h-[16rem]"} ${!searchAnswerExpanded && searchAnswerOverflowing && "overflow-y-hidden"} p-4 border-2 border-border rounded-lg relative`}
|
||||
className={`my-4 ${
|
||||
searchAnswerExpanded ? "min-h-[16rem]" : "h-[16rem]"
|
||||
} ${
|
||||
!searchAnswerExpanded && searchAnswerOverflowing && "overflow-y-hidden"
|
||||
} p-4 border-2 border-search-answer-border rounded-lg relative`}
|
||||
>
|
||||
<div>
|
||||
<div className="flex gap-x-2">
|
||||
|
||||
@@ -68,8 +68,8 @@ export const AnimatedToggle = ({
|
||||
{/* Toggle switch */}
|
||||
<div
|
||||
className={`
|
||||
w-10 h-6 flex items-center rounded-full p-1 transition-all duration-300 ease-in-out
|
||||
${isOn ? "bg-background-400" : "bg-background-200"}
|
||||
w-10 h-6 flex items-center rounded-full p-1 transition-all duration-300 ease-in-out
|
||||
${isOn ? "bg-toggled-background" : "bg-untoggled-background"}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
@@ -178,10 +178,14 @@ export const FullSearchBar = ({
|
||||
suppressContentEditableWarning={true}
|
||||
/>
|
||||
<div
|
||||
className={`flex flex-nowrap ${showingSidebar ? " 2xl:justify-between" : "2xl:justify-end"} justify-between 4xl:justify-end w-full max-w-full items-center space-x-3 py-3 px-4`}
|
||||
className={`flex flex-nowrap ${
|
||||
showingSidebar ? " 2xl:justify-between" : "2xl:justify-end"
|
||||
} justify-between 4xl:justify-end w-full max-w-full items-center space-x-3 py-3 px-4`}
|
||||
>
|
||||
<div
|
||||
className={`-my-1 flex-grow 4xl:hidden ${!showingSidebar && "2xl:hidden"}`}
|
||||
className={`-my-1 flex-grow 4xl:hidden ${
|
||||
!showingSidebar && "2xl:hidden"
|
||||
}`}
|
||||
>
|
||||
{(ccPairs.length > 0 || documentSets.length > 0) && (
|
||||
<HorizontalSourceSelector
|
||||
@@ -208,7 +212,11 @@ export const FullSearchBar = ({
|
||||
>
|
||||
<SendIcon
|
||||
size={28}
|
||||
className={`text-emphasis ${disabled || !query ? "bg-disabled-submit-background" : "bg-submit-background"} text-white p-1 rounded-full`}
|
||||
className={`text-emphasis ${
|
||||
disabled || !query
|
||||
? "bg-disabled-submit-background"
|
||||
: "bg-submit-background"
|
||||
} text-white p-1 rounded-full`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -724,7 +724,9 @@ export const SearchSection = ({
|
||||
|
||||
{
|
||||
<div
|
||||
className={`desktop:px-24 w-full ${chatBannerPresent && "mt-10"} pt-10 relative max-w-[2000px] xl:max-w-[1430px] mx-auto`}
|
||||
className={`desktop:px-24 w-full ${
|
||||
chatBannerPresent && "mt-10"
|
||||
} pt-10 relative max-w-[2000px] xl:max-w-[1430px] mx-auto`}
|
||||
>
|
||||
<div className="absolute z-10 mobile:px-4 mobile:max-w-searchbar-max mobile:w-[90%] top-12 desktop:left-4 hidden 2xl:block mobile:left-1/2 mobile:transform mobile:-translate-x-1/2 desktop:w-52 3xl:w-64">
|
||||
{!settings?.isMobile &&
|
||||
@@ -849,7 +851,7 @@ export const SearchSection = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FixedLogo />
|
||||
<FixedLogo backgroundToggled={toggledSidebar || showDocSidebar} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { fetchSS } from "@/lib/utilsSS";
|
||||
import { CCPairBasicInfo } from "@/lib/types";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { fetchLLMProvidersSS } from "@/lib/llm/fetchLLMs";
|
||||
import { personaComparator } from "@/app/admin/assistants/lib";
|
||||
@@ -12,58 +11,77 @@ interface AssistantData {
|
||||
hasImageCompatibleModel: boolean;
|
||||
}
|
||||
export async function fetchAssistantData(): Promise<AssistantData> {
|
||||
const [assistants, assistantsFetchError] = await fetchAssistantsSS();
|
||||
const ccPairsResponse = await fetchSS("/manage/indexing-status");
|
||||
|
||||
let ccPairs: CCPairBasicInfo[] = [];
|
||||
if (ccPairsResponse?.ok) {
|
||||
ccPairs = await ccPairsResponse.json();
|
||||
} else {
|
||||
console.log(`Failed to fetch connectors - ${ccPairsResponse?.status}`);
|
||||
}
|
||||
|
||||
const hasAnyConnectors = ccPairs.length > 0;
|
||||
|
||||
// if no connectors are setup, only show personas that are pure
|
||||
// passthrough and don't do any retrieval
|
||||
let filteredAssistants = assistants;
|
||||
if (assistantsFetchError) {
|
||||
console.log(`Failed to fetch assistants - ${assistantsFetchError}`);
|
||||
}
|
||||
|
||||
// remove those marked as hidden by an admin
|
||||
filteredAssistants = filteredAssistants.filter(
|
||||
(assistant) => assistant.is_visible
|
||||
);
|
||||
|
||||
if (!hasAnyConnectors) {
|
||||
filteredAssistants = filteredAssistants.filter(
|
||||
(assistant) => assistant.num_chunks === 0
|
||||
);
|
||||
}
|
||||
|
||||
// sort them in priority order
|
||||
filteredAssistants.sort(personaComparator);
|
||||
|
||||
const llmProviders = await fetchLLMProvidersSS();
|
||||
const hasImageCompatibleModel = llmProviders.some(
|
||||
(provider) =>
|
||||
provider.provider === "openai" ||
|
||||
provider.model_names.some((model) => checkLLMSupportsImageInput(model))
|
||||
);
|
||||
|
||||
if (!hasImageCompatibleModel) {
|
||||
filteredAssistants = filteredAssistants.filter(
|
||||
(assistant) =>
|
||||
!assistant.tools.some(
|
||||
(tool) => tool.in_code_tool_id === "ImageGenerationTool"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
assistants: filteredAssistants,
|
||||
hasAnyConnectors,
|
||||
hasImageCompatibleModel,
|
||||
// Default state if anything fails
|
||||
const defaultState: AssistantData = {
|
||||
assistants: [],
|
||||
hasAnyConnectors: false,
|
||||
hasImageCompatibleModel: false,
|
||||
};
|
||||
|
||||
try {
|
||||
// Fetch core assistants data first
|
||||
const [assistants, assistantsFetchError] = await fetchAssistantsSS();
|
||||
if (assistantsFetchError) {
|
||||
console.error(`Failed to fetch assistants - ${assistantsFetchError}`);
|
||||
return defaultState;
|
||||
}
|
||||
|
||||
// Parallel fetch of additional data
|
||||
const [ccPairsResponse, llmProviders] = await Promise.all([
|
||||
fetchSS("/manage/indexing-status").catch((error) => {
|
||||
console.error("Failed to fetch connectors:", error);
|
||||
return null;
|
||||
}),
|
||||
fetchLLMProvidersSS().catch((error) => {
|
||||
console.error("Failed to fetch LLM providers:", error);
|
||||
return [];
|
||||
}),
|
||||
]);
|
||||
|
||||
// Process visible assistants
|
||||
let filteredAssistants = assistants.filter(
|
||||
(assistant) => assistant.is_visible
|
||||
);
|
||||
|
||||
// Process connector status
|
||||
const hasAnyConnectors = ccPairsResponse?.ok
|
||||
? (await ccPairsResponse.json()).length > 0
|
||||
: false;
|
||||
|
||||
// Filter assistants based on connector status
|
||||
if (!hasAnyConnectors) {
|
||||
filteredAssistants = filteredAssistants.filter(
|
||||
(assistant) => assistant.num_chunks === 0
|
||||
);
|
||||
}
|
||||
|
||||
// Sort assistants
|
||||
filteredAssistants.sort(personaComparator);
|
||||
|
||||
// Check for image-compatible models
|
||||
const hasImageCompatibleModel = llmProviders.some(
|
||||
(provider) =>
|
||||
provider.provider === "openai" ||
|
||||
provider.model_names.some((model) => checkLLMSupportsImageInput(model))
|
||||
);
|
||||
|
||||
// Filter out image generation tools if no compatible model
|
||||
if (!hasImageCompatibleModel) {
|
||||
filteredAssistants = filteredAssistants.filter(
|
||||
(assistant) =>
|
||||
!assistant.tools.some(
|
||||
(tool) => tool.in_code_tool_id === "ImageGenerationTool"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
assistants: filteredAssistants,
|
||||
hasAnyConnectors,
|
||||
hasImageCompatibleModel,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Unexpected error in fetchAssistantData:", error);
|
||||
return defaultState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,15 @@ module.exports = {
|
||||
|
||||
// colors for sidebar in chat, search, and manage settings
|
||||
"background-sidebar": "var(--background-100)",
|
||||
"background-settings-sidebar": "var(--background-100)",
|
||||
"background-chatbar": "var(--background-100)",
|
||||
"text-sidebar": "var(--text-500)",
|
||||
|
||||
"toggled-background": "var(--background-400)",
|
||||
"untoggled-background": "var(--background-200)",
|
||||
|
||||
"background-back-button": "var(--background-200)",
|
||||
"text-back-button": "var(--text-800)",
|
||||
|
||||
// Settings
|
||||
"text-sidebar-subtle": "var(--text-500)",
|
||||
"icon-settings-sidebar": "var(--text-600)",
|
||||
@@ -114,9 +119,14 @@ module.exports = {
|
||||
"text-settings-sidebar-strong": "var(--text-900)",
|
||||
"background-settings-hover": "var(--background-200)",
|
||||
|
||||
"background-chat-hover": "var(--background-200)",
|
||||
"background-chat-selected": "var(--background-200)",
|
||||
|
||||
// Background for chat messages (user bubbles)
|
||||
user: "var(--user-bubble)",
|
||||
|
||||
"background-toggle": "var(--background-100)",
|
||||
|
||||
// Colors for the search toggle buttons
|
||||
"background-agentic-toggled": "var(--light-success)",
|
||||
"background-agentic-untoggled": "var(--undo)",
|
||||
|
||||
Reference in New Issue
Block a user