mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-30 01:30:21 +02:00
Update UX (#2324)
This commit is contained in:
parent
61a17319c9
commit
420aabc963
@ -1,3 +1,13 @@
|
||||
import {
|
||||
AnthropicIcon,
|
||||
AWSIcon,
|
||||
AzureIcon,
|
||||
CPUIcon,
|
||||
OpenAIIcon,
|
||||
OpenSourceIcon,
|
||||
} from "@/components/icons/icons";
|
||||
import { FaRobot } from "react-icons/fa";
|
||||
|
||||
export interface CustomConfigKey {
|
||||
name: string;
|
||||
description: string | null;
|
||||
@ -53,3 +63,18 @@ export interface LLMProviderDescriptor {
|
||||
groups: number[];
|
||||
display_model_names: string[] | null;
|
||||
}
|
||||
|
||||
export const getProviderIcon = (providerName: string) => {
|
||||
switch (providerName) {
|
||||
case "openai":
|
||||
return OpenAIIcon;
|
||||
case "anthropic":
|
||||
return AnthropicIcon;
|
||||
case "bedrock":
|
||||
return AWSIcon;
|
||||
case "azure":
|
||||
return AzureIcon;
|
||||
default:
|
||||
return CPUIcon;
|
||||
}
|
||||
};
|
||||
|
@ -482,7 +482,7 @@ export function CCPairIndexingStatusTable({
|
||||
if (sourceMatches || matchingConnectors.length > 0) {
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
<div className="mt-4" />
|
||||
<br className="mt-4" />
|
||||
|
||||
<SummaryRow
|
||||
source={source}
|
||||
|
@ -57,6 +57,7 @@ export function ChatBanner() {
|
||||
className={`${settings.enterpriseSettings.two_lines_for_chat_header ? "line-clamp-2" : "line-clamp-1"} text-center w-full overflow-hidden pr-8`}
|
||||
>
|
||||
<MinimalMarkdown
|
||||
className="prose text-sm max-w-full"
|
||||
content={settings.enterpriseSettings.custom_header_content}
|
||||
/>
|
||||
</div>
|
||||
@ -65,6 +66,7 @@ export function ChatBanner() {
|
||||
className="absolute top-0 left-0 invisible w-full"
|
||||
>
|
||||
<MinimalMarkdown
|
||||
className="prose text-sm max-w-full"
|
||||
content={settings.enterpriseSettings.custom_header_content}
|
||||
/>
|
||||
</div>
|
||||
|
@ -338,10 +338,10 @@ export function ChatInputBar({
|
||||
updateInputPrompt(currentPrompt);
|
||||
}}
|
||||
>
|
||||
<p className="font-bold">{currentPrompt.prompt}</p>
|
||||
<p className="line-clamp-1">
|
||||
<p className="font-bold">{currentPrompt.prompt}:</p>
|
||||
<p className="text-left flex-grow mr-auto line-clamp-1">
|
||||
{currentPrompt.id == selectedAssistant.id && "(default) "}
|
||||
{currentPrompt.content}
|
||||
{currentPrompt.content?.trim()}
|
||||
</p>
|
||||
</button>
|
||||
))}
|
||||
|
@ -46,6 +46,7 @@ export function ChatSessionDisplay({
|
||||
showDeleteModal?: (chatSession: ChatSession) => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const [isRenamingChat, setIsRenamingChat] = useState(false);
|
||||
const [isMoreOptionsDropdownOpen, setIsMoreOptionsDropdownOpen] =
|
||||
useState(false);
|
||||
@ -97,6 +98,11 @@ export function ChatSessionDisplay({
|
||||
<Link
|
||||
className="flex my-1 group relative"
|
||||
key={chatSession.id}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => {
|
||||
setIsMoreOptionsDropdownOpen(false);
|
||||
setIsHovering(false);
|
||||
}}
|
||||
onClick={() => {
|
||||
if (settings?.isMobile && closeSidebar) {
|
||||
closeSidebar();
|
||||
@ -145,7 +151,7 @@ export function ChatSessionDisplay({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSelected &&
|
||||
{isHovering &&
|
||||
(isRenamingChat ? (
|
||||
<div className="ml-auto my-auto items-center flex">
|
||||
<div
|
||||
@ -185,54 +191,68 @@ export function ChatSessionDisplay({
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsMoreOptionsDropdownOpen(
|
||||
!isMoreOptionsDropdownOpen
|
||||
);
|
||||
}}
|
||||
className={"-my-1"}
|
||||
>
|
||||
<Popover
|
||||
open={isMoreOptionsDropdownOpen}
|
||||
onOpenChange={(open: boolean) =>
|
||||
setIsMoreOptionsDropdownOpen(open)
|
||||
}
|
||||
content={
|
||||
<div className="hover:bg-black/10 p-1 rounded">
|
||||
<FiMoreHorizontal size={16} />
|
||||
</div>
|
||||
}
|
||||
popover={
|
||||
<div className="border border-border rounded-lg bg-background z-50 w-32">
|
||||
{showShareModal && (
|
||||
<DefaultDropdownElement
|
||||
name="Share"
|
||||
icon={FiShare2}
|
||||
onSelect={() => showShareModal(chatSession)}
|
||||
/>
|
||||
)}
|
||||
<DefaultDropdownElement
|
||||
name="Rename"
|
||||
icon={FiEdit2}
|
||||
onSelect={() => setIsRenamingChat(true)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
requiresContentPadding
|
||||
sideOffset={6}
|
||||
triggerMaxWidth
|
||||
/>
|
||||
</div>
|
||||
{search ? (
|
||||
showDeleteModal && (
|
||||
<div
|
||||
onClick={() => showDeleteModal(chatSession)}
|
||||
className={`p-1 -m-1 rounded ml-1`}
|
||||
>
|
||||
<FiTrash size={16} />
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsMoreOptionsDropdownOpen(
|
||||
!isMoreOptionsDropdownOpen
|
||||
);
|
||||
}}
|
||||
className="-my-1"
|
||||
>
|
||||
<Popover
|
||||
open={isMoreOptionsDropdownOpen}
|
||||
onOpenChange={(open: boolean) =>
|
||||
setIsMoreOptionsDropdownOpen(open)
|
||||
}
|
||||
content={
|
||||
<div className="p-1 rounded">
|
||||
<FiMoreHorizontal size={16} />
|
||||
</div>
|
||||
}
|
||||
popover={
|
||||
<div className="border border-border rounded-lg bg-background z-50 w-32">
|
||||
{showShareModal && (
|
||||
<DefaultDropdownElement
|
||||
name="Share"
|
||||
icon={FiShare2}
|
||||
onSelect={() => showShareModal(chatSession)}
|
||||
/>
|
||||
)}
|
||||
{!search && (
|
||||
<DefaultDropdownElement
|
||||
name="Rename"
|
||||
icon={FiEdit2}
|
||||
onSelect={() => setIsRenamingChat(true)}
|
||||
/>
|
||||
)}
|
||||
{showDeleteModal && (
|
||||
<DefaultDropdownElement
|
||||
name="Delete"
|
||||
icon={FiTrash}
|
||||
onSelect={() =>
|
||||
showDeleteModal(chatSession)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
requiresContentPadding
|
||||
sideOffset={6}
|
||||
triggerMaxWidth
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showDeleteModal && (
|
||||
<div
|
||||
onClick={() => showDeleteModal(chatSession)}
|
||||
className={`hover:bg-black/10 p-1 -m-1 rounded ml-1`}
|
||||
>
|
||||
<FiTrash size={16} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -53,9 +53,14 @@ const ToggleSwitch = () => {
|
||||
onClick={() => handleTabChange("search")}
|
||||
>
|
||||
<SearchIcon size={16} className="mr-2" />
|
||||
<div className="flex items-end">
|
||||
<div className="flex items-center">
|
||||
Search
|
||||
<div className="ml-2 flex items-end">{commandSymbol}S</div>
|
||||
<div className="ml-2 flex content-center">
|
||||
<span className="leading-none pb-[1px] my-auto">
|
||||
{commandSymbol}
|
||||
</span>
|
||||
<span className="my-auto">S</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@ -69,7 +74,12 @@ const ToggleSwitch = () => {
|
||||
<ChatIcon size={16} className="mr-2" />
|
||||
<div className="items-end flex">
|
||||
Chat
|
||||
<div className="ml-2 flex items-end">{commandSymbol}D</div>
|
||||
<div className="ml-2 flex content-center">
|
||||
<span className="leading-none pb-[1px] my-auto">
|
||||
{commandSymbol}
|
||||
</span>
|
||||
<span className="my-auto">D</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
.include-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #888 #f1f1f1;
|
||||
scrollbar-color: #888 transparent;
|
||||
}
|
||||
|
||||
.inputscroll::-webkit-scrollbar-track {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { CodeBlock } from "@/app/chat/message/CodeBlock";
|
||||
import React from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
@ -5,11 +6,13 @@ import remarkGfm from "remark-gfm";
|
||||
interface MinimalMarkdownProps {
|
||||
content: string;
|
||||
className?: string;
|
||||
useCodeBlock?: boolean;
|
||||
}
|
||||
|
||||
export const MinimalMarkdown: React.FC<MinimalMarkdownProps> = ({
|
||||
content,
|
||||
className = "",
|
||||
useCodeBlock = false,
|
||||
}) => {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
@ -26,6 +29,11 @@ export const MinimalMarkdown: React.FC<MinimalMarkdownProps> = ({
|
||||
p: ({ node, ...props }) => (
|
||||
<p {...props} className="text-wrap break-word text-sm m-0 w-full" />
|
||||
),
|
||||
code: useCodeBlock
|
||||
? (props) => (
|
||||
<CodeBlock className="w-full" {...props} content={content} />
|
||||
)
|
||||
: (props) => <code {...props} />,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React from "react";
|
||||
import { getDisplayNameForModel } from "@/lib/hooks";
|
||||
import { structureValue } from "@/lib/llm/utils";
|
||||
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
|
||||
import {
|
||||
getProviderIcon,
|
||||
LLMProviderDescriptor,
|
||||
} from "@/app/admin/configuration/llm/interfaces";
|
||||
|
||||
interface LlmListProps {
|
||||
llmProviders: LLMProviderDescriptor[];
|
||||
@ -9,6 +12,7 @@ interface LlmListProps {
|
||||
onSelect: (value: string | null) => void;
|
||||
userDefault?: string | null;
|
||||
scrollable?: boolean;
|
||||
hideProviderIcon?: boolean;
|
||||
}
|
||||
|
||||
export const LlmList: React.FC<LlmListProps> = ({
|
||||
@ -19,7 +23,11 @@ export const LlmList: React.FC<LlmListProps> = ({
|
||||
scrollable,
|
||||
}) => {
|
||||
const llmOptionsByProvider: {
|
||||
[provider: string]: { name: string; value: string }[];
|
||||
[provider: string]: {
|
||||
name: string;
|
||||
value: string;
|
||||
icon: React.FC<{ size?: number; className?: string }>;
|
||||
}[];
|
||||
} = {};
|
||||
const uniqueModelNames = new Set<string>();
|
||||
|
||||
@ -39,6 +47,7 @@ export const LlmList: React.FC<LlmListProps> = ({
|
||||
llmProvider.provider,
|
||||
modelName
|
||||
),
|
||||
icon: getProviderIcon(llmProvider.provider),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -67,17 +76,18 @@ export const LlmList: React.FC<LlmListProps> = ({
|
||||
User Default (currently {getDisplayNameForModel(userDefault)})
|
||||
</button>
|
||||
)}
|
||||
{llmOptions.map(({ name, value }, index) => (
|
||||
{llmOptions.map(({ name, icon, value }, index) => (
|
||||
<button
|
||||
type="button"
|
||||
key={index}
|
||||
className={`w-full py-1.5 px-2 text-sm ${
|
||||
className={`w-full py-1.5 flex gap-x-2 px-2 text-sm ${
|
||||
currentLlm == name
|
||||
? "bg-background-200"
|
||||
: "bg-background hover:bg-background-100"
|
||||
} text-left rounded`}
|
||||
onClick={() => onSelect(value)}
|
||||
>
|
||||
{icon({ size: 16 })}
|
||||
{getDisplayNameForModel(name)}
|
||||
</button>
|
||||
))}
|
||||
|
@ -71,7 +71,7 @@ export const AnimatedToggle = ({
|
||||
Get quality results immediately, best suited for instant access to
|
||||
your documents.
|
||||
</p>
|
||||
<p className="mt-2 text-xs">Shortcut: ({commandSymbol}/)</p>
|
||||
<p className="mt-2 flex text-xs">Shortcut: ({commandSymbol}/)</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
@ -34,8 +34,9 @@ import FixedLogo from "@/app/chat/shared_chat_search/FixedLogo";
|
||||
import { usePopup } from "../admin/connectors/Popup";
|
||||
import { FeedbackType } from "@/app/chat/types";
|
||||
import { FeedbackModal } from "@/app/chat/modal/FeedbackModal";
|
||||
import { handleChatFeedback } from "@/app/chat/lib";
|
||||
import { deleteChatSession, handleChatFeedback } from "@/app/chat/lib";
|
||||
import SearchAnswer from "./SearchAnswer";
|
||||
import { DeleteEntityModal } from "../modals/DeleteEntityModal";
|
||||
|
||||
export type searchState =
|
||||
| "input"
|
||||
@ -511,7 +512,12 @@ export const SearchSection = ({
|
||||
};
|
||||
const [firstSearch, setFirstSearch] = useState(true);
|
||||
const [searchState, setSearchState] = useState<searchState>("input");
|
||||
const [deletingChatSession, setDeletingChatSession] =
|
||||
useState<ChatSession | null>();
|
||||
|
||||
const showDeleteModal = (chatSession: ChatSession) => {
|
||||
setDeletingChatSession(chatSession);
|
||||
};
|
||||
// Used to maintain a "time out" for history sidebar so our existing refs can have time to process change
|
||||
const [untoggled, setUntoggled] = useState(false);
|
||||
|
||||
@ -591,6 +597,24 @@ export const SearchSection = ({
|
||||
<>
|
||||
<div className="flex relative pr-[8px] h-full text-default">
|
||||
{popup}
|
||||
|
||||
{deletingChatSession && (
|
||||
<DeleteEntityModal
|
||||
entityType="search"
|
||||
entityName={deletingChatSession.name}
|
||||
onClose={() => setDeletingChatSession(null)}
|
||||
onSubmit={async () => {
|
||||
const response = await deleteChatSession(deletingChatSession.id);
|
||||
if (response.ok) {
|
||||
setDeletingChatSession(null);
|
||||
// go back to the main page
|
||||
router.push("/search");
|
||||
} else {
|
||||
alert("Failed to delete chat session");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{currentFeedback && (
|
||||
<FeedbackModal
|
||||
feedbackType={currentFeedback[0]}
|
||||
@ -628,6 +652,7 @@ export const SearchSection = ({
|
||||
>
|
||||
<div className="w-full relative">
|
||||
<HistorySidebar
|
||||
showDeleteModal={showDeleteModal}
|
||||
explicitlyUntoggle={explicitlyUntoggle}
|
||||
reset={() => setQuery("")}
|
||||
page="search"
|
||||
@ -672,7 +697,7 @@ export const SearchSection = ({
|
||||
(ccPairs.length > 0 || documentSets.length > 0) && (
|
||||
<SourceSelector
|
||||
{...filterManager}
|
||||
showDocSidebar={showDocSidebar || toggledSidebar}
|
||||
showDocSidebar={toggledSidebar}
|
||||
availableDocumentSets={finalAvailableDocumentSets}
|
||||
existingSources={finalAvailableSources}
|
||||
availableTags={tags}
|
||||
@ -727,7 +752,7 @@ export const SearchSection = ({
|
||||
toggleAgentic={
|
||||
disabledAgentic ? undefined : toggleAgentic
|
||||
}
|
||||
showingSidebar={showDocSidebar || toggledSidebar}
|
||||
showingSidebar={toggledSidebar}
|
||||
agentic={agentic}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
|
@ -115,7 +115,7 @@ export function TagFilter({
|
||||
<FiTag className="mr-1 my-auto" />
|
||||
Tags
|
||||
</div>
|
||||
<div className="flex overflow-y-scroll max-h-96 flex-wrap gap-x-1 gap-y-1">
|
||||
<div className="flex overflow-y-scroll overflow-x-hidden input-scrollbar max-h-96 flex-wrap gap-x-1 gap-y-1">
|
||||
{filteredTags.length > 0 ? (
|
||||
filteredTags.map((tag) => (
|
||||
<div
|
||||
|
@ -2,6 +2,7 @@ import { Quote } from "@/lib/search/interfaces";
|
||||
import { ResponseSection, StatusOptions } from "./ResponseSection";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
|
||||
|
||||
const TEMP_STRING = "__$%^TEMP$%^__";
|
||||
|
||||
@ -40,12 +41,10 @@ export const AnswerSection = (props: AnswerSectionProps) => {
|
||||
header = <></>;
|
||||
|
||||
body = (
|
||||
<ReactMarkdown
|
||||
className="prose text-sm max-w-full"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{replaceNewlines(props.answer || "")}
|
||||
</ReactMarkdown>
|
||||
<MinimalMarkdown
|
||||
useCodeBlock
|
||||
content={replaceNewlines(props.answer || "")}
|
||||
/>
|
||||
);
|
||||
|
||||
// error while building answer (NOTE: if error occurs during quote generation
|
||||
@ -63,12 +62,7 @@ export const AnswerSection = (props: AnswerSectionProps) => {
|
||||
status = "success";
|
||||
header = <></>;
|
||||
body = (
|
||||
<ReactMarkdown
|
||||
className="prose text-sm max-w-full"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{replaceNewlines(props.answer)}
|
||||
</ReactMarkdown>
|
||||
<MinimalMarkdown useCodeBlock content={replaceNewlines(props.answer)} />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,10 @@ import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { CCPairBasicInfo, DocumentSet, User } from "../types";
|
||||
import { getCurrentUserSS } from "../userSS";
|
||||
import { fetchSS } from "../utilsSS";
|
||||
import { FullLLMProvider } from "@/app/admin/configuration/llm/interfaces";
|
||||
import {
|
||||
FullLLMProvider,
|
||||
getProviderIcon,
|
||||
} from "@/app/admin/configuration/llm/interfaces";
|
||||
import { ToolSnapshot } from "../tools/interfaces";
|
||||
import { fetchToolsSS } from "../tools/fetchTools";
|
||||
import {
|
||||
@ -94,17 +97,7 @@ export async function fetchAssistantEditorInfoSS(
|
||||
}
|
||||
|
||||
for (const provider of llmProviders) {
|
||||
if (provider.provider == "openai") {
|
||||
provider.icon = OpenAIIcon;
|
||||
} else if (provider.provider == "anthropic") {
|
||||
provider.icon = AnthropicIcon;
|
||||
} else if (provider.provider == "bedrock") {
|
||||
provider.icon = AWSIcon;
|
||||
} else if (provider.provider == "azure") {
|
||||
provider.icon = AzureIcon;
|
||||
} else {
|
||||
provider.icon = OpenSourceIcon;
|
||||
}
|
||||
provider.icon = getProviderIcon(provider.provider);
|
||||
}
|
||||
|
||||
const existingPersona = personaResponse
|
||||
|
Loading…
x
Reference in New Issue
Block a user