Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/* && \
rm -f /usr/local/lib/python3.11/site-packages/tornado/test/test.key


# Pre-downloading models for setups with limited egress
RUN python -c "from tokenizers import Tokenizer; \
Tokenizer.from_pretrained('nomic-ai/nomic-embed-text-v1')"
Expand Down
6 changes: 6 additions & 0 deletions backend/danswer/file_store/file_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def read_file(
Contents of the file and metadata dict
"""

@abstractmethod
def read_file_record(self, file_name: str) -> PGFileStore:
"""
Read the file record by the name
"""

@abstractmethod
def delete_file(self, file_name: str) -> None:
"""
Expand Down
12 changes: 8 additions & 4 deletions backend/danswer/server/query_and_chat/chat_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,14 +707,18 @@ def upload_files_for_chat(
}


@router.get("/file/{file_id}")
@router.get("/file/{file_id:path}")
def fetch_chat_file(
file_id: str,
db_session: Session = Depends(get_session),
_: User | None = Depends(current_user),
) -> Response:
file_store = get_default_file_store(db_session)
file_record = file_store.read_file_record(file_id)
if not file_record:
raise HTTPException(status_code=404, detail="File not found")

media_type = file_record.file_type
file_io = file_store.read_file(file_id, mode="b")
# NOTE: specifying "image/jpeg" here, but it still works for pngs
# TODO: do this properly
return Response(content=file_io.read(), media_type="image/jpeg")

return StreamingResponse(file_io, media_type=media_type)
45 changes: 45 additions & 0 deletions backend/shared_configs/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,47 +163,92 @@ async def async_return_default_schema(*args: Any, **kwargs: Any) -> str:
dim=1024,
index_name="danswer_chunk_cohere_embed_english_v3_0",
),
SupportedEmbeddingModel(
name="cohere/embed-english-v3.0",
dim=1024,
index_name="danswer_chunk_embed_english_v3_0",
),
SupportedEmbeddingModel(
name="cohere/embed-english-light-v3.0",
dim=384,
index_name="danswer_chunk_cohere_embed_english_light_v3_0",
),
SupportedEmbeddingModel(
name="cohere/embed-english-light-v3.0",
dim=384,
index_name="danswer_chunk_embed_english_light_v3_0",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-large",
dim=3072,
index_name="danswer_chunk_openai_text_embedding_3_large",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-large",
dim=3072,
index_name="danswer_chunk_text_embedding_3_large",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-small",
dim=1536,
index_name="danswer_chunk_openai_text_embedding_3_small",
),
SupportedEmbeddingModel(
name="openai/text-embedding-3-small",
dim=1536,
index_name="danswer_chunk_text_embedding_3_small",
),
SupportedEmbeddingModel(
name="google/text-embedding-004",
dim=768,
index_name="danswer_chunk_google_text_embedding_004",
),
SupportedEmbeddingModel(
name="google/text-embedding-004",
dim=768,
index_name="danswer_chunk_text_embedding_004",
),
SupportedEmbeddingModel(
name="google/textembedding-gecko@003",
dim=768,
index_name="danswer_chunk_google_textembedding_gecko_003",
),
SupportedEmbeddingModel(
name="google/textembedding-gecko@003",
dim=768,
index_name="danswer_chunk_textembedding_gecko_003",
),
SupportedEmbeddingModel(
name="voyage/voyage-large-2-instruct",
dim=1024,
index_name="danswer_chunk_voyage_large_2_instruct",
),
SupportedEmbeddingModel(
name="voyage/voyage-large-2-instruct",
dim=1024,
index_name="danswer_chunk_large_2_instruct",
),
SupportedEmbeddingModel(
name="voyage/voyage-light-2-instruct",
dim=384,
index_name="danswer_chunk_voyage_light_2_instruct",
),
SupportedEmbeddingModel(
name="voyage/voyage-light-2-instruct",
dim=384,
index_name="danswer_chunk_light_2_instruct",
),
# Self-hosted models
SupportedEmbeddingModel(
name="nomic-ai/nomic-embed-text-v1",
dim=768,
index_name="danswer_chunk_nomic_ai_nomic_embed_text_v1",
),
SupportedEmbeddingModel(
name="nomic-ai/nomic-embed-text-v1",
dim=768,
index_name="danswer_chunk_nomic_embed_text_v1",
),
SupportedEmbeddingModel(
name="intfloat/e5-base-v2",
dim=768,
Expand Down
62 changes: 62 additions & 0 deletions backend/tests/integration/common_utils/managers/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import mimetypes
from typing import cast
from typing import IO
from typing import List
from typing import Tuple

import requests

from danswer.file_store.models import FileDescriptor
from tests.integration.common_utils.constants import API_SERVER_URL
from tests.integration.common_utils.constants import GENERAL_HEADERS
from tests.integration.common_utils.test_models import DATestUser


class FileManager:
@staticmethod
def upload_files(
files: List[Tuple[str, IO]],
user_performing_action: DATestUser | None = None,
) -> Tuple[List[FileDescriptor], str]:
headers = (
user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS
)
headers.pop("Content-Type", None)

files_param = []
for filename, file_obj in files:
mime_type, _ = mimetypes.guess_type(filename)
if mime_type is None:
mime_type = "application/octet-stream"
files_param.append(("files", (filename, file_obj, mime_type)))

response = requests.post(
f"{API_SERVER_URL}/chat/file",
files=files_param,
headers=headers,
)

if not response.ok:
return (
cast(List[FileDescriptor], []),
f"Failed to upload files - {response.json().get('detail', 'Unknown error')}",
)

response_json = response.json()
return response_json.get("files", cast(List[FileDescriptor], [])), ""

@staticmethod
def fetch_uploaded_file(
file_id: str,
user_performing_action: DATestUser | None = None,
) -> bytes:
response = requests.get(
f"{API_SERVER_URL}/chat/file/{file_id}",
headers=user_performing_action.headers
if user_performing_action
else GENERAL_HEADERS,
)
response.raise_for_status()
return response.content
6 changes: 6 additions & 0 deletions node_modules/.package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 18 additions & 1 deletion web/src/app/chat/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import { NoAssistantModal } from "@/components/modals/NoAssistantModal";
import { useAssistants } from "@/components/context/AssistantsContext";
import { Separator } from "@/components/ui/separator";
import AssistantBanner from "../../components/assistants/AssistantBanner";
import TextView from "@/components/chat_search/TextView";
import AssistantSelector from "@/components/chat_search/AssistantSelector";
import { Modal } from "@/components/Modal";

Expand Down Expand Up @@ -279,6 +280,9 @@ export function ChatPage({
const [alternativeAssistant, setAlternativeAssistant] =
useState<Persona | null>(null);

const [presentingDocument, setPresentingDocument] =
useState<DanswerDocument | null>(null);

const {
visibleAssistants: assistants,
recentAssistants,
Expand Down Expand Up @@ -490,6 +494,7 @@ export function ChatPage({
clientScrollToBottom(true);
}
}

setIsFetchingChatMessages(false);

// if this is a seeded chat, then kick off the AI message generation
Expand Down Expand Up @@ -1649,7 +1654,6 @@ export function ChatPage({
scrollDist,
endDivRef,
debounceNumber,
waitForScrollRef,
mobile: settings?.isMobile,
enableAutoScroll: autoScrollEnabled,
});
Expand Down Expand Up @@ -1946,6 +1950,7 @@ export function ChatPage({
{popup}

<ChatPopup />

{currentFeedback && (
<FeedbackModal
feedbackType={currentFeedback[0]}
Expand Down Expand Up @@ -1979,6 +1984,7 @@ export function ChatPage({
<div className="md:hidden">
<Modal noPadding noScroll>
<ChatFilters
setPresentingDocument={setPresentingDocument}
modal={true}
filterManager={filterManager}
ccPairs={ccPairs}
Expand Down Expand Up @@ -2024,6 +2030,13 @@ export function ChatPage({
/>
)}

{presentingDocument && (
<TextView
presentingDocument={presentingDocument}
onClose={() => setPresentingDocument(null)}
/>
)}

{stackTraceModalContent && (
<ExceptionTraceModal
onOutsideClick={() => setStackTraceModalContent(null)}
Expand Down Expand Up @@ -2127,6 +2140,7 @@ export function ChatPage({
`}
>
<ChatFilters
setPresentingDocument={setPresentingDocument}
modal={false}
filterManager={filterManager}
ccPairs={ccPairs}
Expand Down Expand Up @@ -2424,6 +2438,9 @@ export function ChatPage({
}
>
<AIMessage
setPresentingDocument={
setPresentingDocument
}
index={i}
selectedMessageForDocDisplay={
selectedMessageForDocDisplay
Expand Down
27 changes: 21 additions & 6 deletions web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay
import { DocumentUpdatedAtBadge } from "@/components/search/DocumentUpdatedAtBadge";
import { MetadataBadge } from "@/components/MetadataBadge";
import { WebResultIcon } from "@/components/WebResultIcon";
import { Dispatch, SetStateAction } from "react";

interface DocumentDisplayProps {
closeSidebar: () => void;
document: DanswerDocument;
modal?: boolean;
isSelected: boolean;
handleSelect: (documentId: string) => void;
tokenLimitReached: boolean;
setPresentingDocument: Dispatch<SetStateAction<DanswerDocument | null>>;
}

export function DocumentMetadataBlock({
Expand Down Expand Up @@ -55,30 +58,42 @@ export function DocumentMetadataBlock({
}

export function ChatDocumentDisplay({
closeSidebar,
document,
modal,
isSelected,
handleSelect,
tokenLimitReached,
setPresentingDocument,
}: DocumentDisplayProps) {
const isInternet = document.is_internet;

if (document.score === null) {
return null;
}

const handleViewFile = async () => {
if (document.link) {
window.open(document.link, "_blank");
} else {
closeSidebar();

setTimeout(async () => {
setPresentingDocument(document);
}, 100);
}
};

return (
<div className={`opacity-100 ${modal ? "w-[90vw]" : "w-full"}`}>
<div
className={`flex relative flex-col gap-0.5 rounded-xl mx-2 my-1 ${
isSelected ? "bg-gray-200" : "hover:bg-background-125"
}`}
>
<a
href={document.link}
target="_blank"
rel="noopener noreferrer"
className="cursor-pointer flex flex-col px-2 py-1.5"
<button
onClick={handleViewFile}
className="cursor-pointer text-left flex flex-col px-2 py-1.5"
>
<div className="line-clamp-1 mb-1 flex h-6 items-center gap-2 text-xs">
{document.is_internet || document.source_type === "web" ? (
Expand Down Expand Up @@ -111,7 +126,7 @@ export function ChatDocumentDisplay({
/>
)}
</div>
</a>
</button>
</div>
</div>
);
Expand Down
Loading
Loading