mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-03-17 13:22:42 +01:00
UX (#4014)
This commit is contained in:
parent
86bd121806
commit
58b252727f
@ -409,10 +409,6 @@ class DefaultMultiLLM(LLM):
|
||||
self._record_call(processed_prompt)
|
||||
|
||||
try:
|
||||
print(
|
||||
"model is",
|
||||
f"{self.config.model_provider}/{self.config.deployment_name or self.config.model_name}",
|
||||
)
|
||||
return litellm.completion(
|
||||
mock_response=MOCK_LLM_RESPONSE,
|
||||
# model choice
|
||||
|
@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { redirect, useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
redirect,
|
||||
usePathname,
|
||||
useRouter,
|
||||
useSearchParams,
|
||||
} from "next/navigation";
|
||||
import {
|
||||
BackendChatSession,
|
||||
BackendMessage,
|
||||
@ -130,6 +135,7 @@ import {
|
||||
} from "@/lib/browserUtilities";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ConfirmEntityModal } from "@/components/modals/ConfirmEntityModal";
|
||||
import { MessageChannel } from "node:worker_threads";
|
||||
|
||||
const TEMP_USER_MESSAGE_ID = -1;
|
||||
const TEMP_ASSISTANT_MESSAGE_ID = -2;
|
||||
@ -1145,6 +1151,7 @@ export function ChatPage({
|
||||
regenerationRequest?: RegenerationRequest | null;
|
||||
overrideFileDescriptors?: FileDescriptor[];
|
||||
} = {}) => {
|
||||
navigatingAway.current = false;
|
||||
let frozenSessionId = currentSessionId();
|
||||
updateCanContinue(false, frozenSessionId);
|
||||
|
||||
@ -1267,7 +1274,6 @@ export function ChatPage({
|
||||
let stackTrace: string | null = null;
|
||||
|
||||
let sub_questions: SubQuestionDetail[] = [];
|
||||
let second_level_sub_questions: SubQuestionDetail[] = [];
|
||||
let is_generating: boolean = false;
|
||||
let second_level_generating: boolean = false;
|
||||
let finalMessage: BackendMessage | null = null;
|
||||
@ -1291,7 +1297,7 @@ export function ChatPage({
|
||||
|
||||
const stack = new CurrentMessageFIFO();
|
||||
updateCurrentMessageFIFO(stack, {
|
||||
signal: controller.signal, // Add this line
|
||||
signal: controller.signal,
|
||||
message: currMessage,
|
||||
alternateAssistantId: currentAssistantId,
|
||||
fileDescriptors: overrideFileDescriptors || currentMessageFiles,
|
||||
@ -1712,7 +1718,10 @@ export function ChatPage({
|
||||
const newUrl = buildChatUrl(searchParams, currChatSessionId, null);
|
||||
// newUrl is like /chat?chatId=10
|
||||
// current page is like /chat
|
||||
router.push(newUrl, { scroll: false });
|
||||
|
||||
if (pathname == "/chat" && !navigatingAway.current) {
|
||||
router.push(newUrl, { scroll: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
@ -2086,6 +2095,31 @@ export function ChatPage({
|
||||
llmOverrideManager.updateImageFilesPresent(imageFileInMessageHistory);
|
||||
}, [imageFileInMessageHistory]);
|
||||
|
||||
const pathname = usePathname();
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Cleanup which only runs when the component unmounts (i.e. when you navigate away).
|
||||
const currentSession = currentSessionId();
|
||||
const controller = abortControllersRef.current.get(currentSession);
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
navigatingAway.current = true;
|
||||
setAbortControllers((prev) => {
|
||||
const newControllers = new Map(prev);
|
||||
newControllers.delete(currentSession);
|
||||
return newControllers;
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [pathname]);
|
||||
|
||||
const navigatingAway = useRef(false);
|
||||
// Keep a ref to abortControllers to ensure we always have the latest value
|
||||
const abortControllersRef = useRef(abortControllers);
|
||||
useEffect(() => {
|
||||
abortControllersRef.current = abortControllers;
|
||||
}, [abortControllers]);
|
||||
|
||||
useSidebarShortcut(router, toggleSidebar);
|
||||
|
||||
const [sharedChatSession, setSharedChatSession] =
|
||||
@ -2300,7 +2334,7 @@ export function ChatPage({
|
||||
fixed
|
||||
left-0
|
||||
z-40
|
||||
bg-background-100
|
||||
bg-neutral-200
|
||||
h-screen
|
||||
transition-all
|
||||
bg-opacity-80
|
||||
@ -2557,12 +2591,21 @@ export function ChatPage({
|
||||
) {
|
||||
return <></>;
|
||||
}
|
||||
const nextMessage =
|
||||
messageHistory.length > i + 1
|
||||
? messageHistory[i + 1]
|
||||
: null;
|
||||
return (
|
||||
<div
|
||||
id={`message-${message.messageId}`}
|
||||
key={messageReactComponentKey}
|
||||
>
|
||||
<HumanMessage
|
||||
disableSwitchingForStreaming={
|
||||
(nextMessage &&
|
||||
nextMessage.is_generating) ||
|
||||
false
|
||||
}
|
||||
stopGenerating={stopGenerating}
|
||||
content={message.message}
|
||||
files={message.files}
|
||||
|
@ -94,7 +94,7 @@ export function AgenticToggle({
|
||||
Agent Search (BETA)
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-xs text-neutarl-600 dark:text-neutral-700 mb-2">
|
||||
<p className="text-xs text-neutral-600 dark:text-neutral-700 mb-2">
|
||||
Use AI agents to break down questions and run deep iterative
|
||||
research through promising pathways. Gives more thorough and
|
||||
accurate responses but takes slightly longer.
|
||||
|
@ -113,7 +113,7 @@ export default function LLMPopover({
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className="focus:outline-none"
|
||||
className="dark:text-[#fff] text-[#000] focus:outline-none"
|
||||
data-testid="llm-popover-trigger"
|
||||
>
|
||||
<ChatInputOption
|
||||
|
@ -250,7 +250,7 @@ export async function* sendMessage({
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
yield* handleSSEStream<PacketType>(response);
|
||||
yield* handleSSEStream<PacketType>(response, signal);
|
||||
}
|
||||
|
||||
export async function nameChatSession(chatSessionId: string) {
|
||||
|
@ -9,6 +9,12 @@ import React, {
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { OnyxDocument, FilteredOnyxDocument } from "@/lib/search/interfaces";
|
||||
import remarkGfm from "remark-gfm";
|
||||
@ -308,7 +314,7 @@ export const AgenticMessage = ({
|
||||
const renderedAlternativeMarkdown = useMemo(() => {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className="prose max-w-full text-base"
|
||||
className="prose dark:prose-invert max-w-full text-base"
|
||||
components={{
|
||||
...markdownComponents,
|
||||
code: ({ node, className, children }: any) => {
|
||||
@ -335,7 +341,7 @@ export const AgenticMessage = ({
|
||||
const renderedMarkdown = useMemo(() => {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className="prose max-w-full text-base"
|
||||
className="prose dark:prose-invert max-w-full text-base"
|
||||
components={markdownComponents}
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[[rehypePrism, { ignoreMissing: true }], rehypeKatex]}
|
||||
@ -530,6 +536,7 @@ export const AgenticMessage = ({
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
disableForStreaming={!isComplete}
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
@ -616,6 +623,7 @@ export const AgenticMessage = ({
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
disableForStreaming={!isComplete}
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
@ -694,27 +702,52 @@ function MessageSwitcher({
|
||||
totalPages,
|
||||
handlePrevious,
|
||||
handleNext,
|
||||
disableForStreaming,
|
||||
}: {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
handlePrevious: () => void;
|
||||
handleNext: () => void;
|
||||
disableForStreaming?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center text-sm space-x-0.5">
|
||||
<Hoverable
|
||||
icon={FiChevronLeft}
|
||||
onClick={currentPage === 1 ? undefined : handlePrevious}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Hoverable
|
||||
icon={FiChevronLeft}
|
||||
onClick={currentPage === 1 ? undefined : handlePrevious}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{disableForStreaming ? "Disabled" : "Previous"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<span className="text-text-darker select-none">
|
||||
{currentPage} / {totalPages}
|
||||
{disableForStreaming ? "Complete" : "Generating"}
|
||||
</span>
|
||||
|
||||
<Hoverable
|
||||
icon={FiChevronRight}
|
||||
onClick={currentPage === totalPages ? undefined : handleNext}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Hoverable
|
||||
icon={FiChevronRight}
|
||||
onClick={currentPage === totalPages ? undefined : handleNext}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{disableForStreaming ? "Disabled" : "Next"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -383,7 +383,7 @@ export const AIMessage = ({
|
||||
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
||||
/>
|
||||
<ReactMarkdown
|
||||
className="prose max-w-full text-base"
|
||||
className="prose dark:prose-invert max-w-full text-base"
|
||||
components={markdownComponents}
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[[rehypePrism, { ignoreMissing: true }], rehypeKatex]}
|
||||
@ -495,7 +495,10 @@ export const AIMessage = ({
|
||||
{docs && docs.length > 0 && (
|
||||
<div
|
||||
className={`mobile:hidden ${
|
||||
query && "mt-2"
|
||||
(query ||
|
||||
toolCall?.tool_name ===
|
||||
INTERNET_SEARCH_TOOL_NAME) &&
|
||||
"mt-2"
|
||||
} -mx-8 w-full mb-4 flex relative`}
|
||||
>
|
||||
<div className="w-full">
|
||||
@ -795,27 +798,67 @@ function MessageSwitcher({
|
||||
totalPages,
|
||||
handlePrevious,
|
||||
handleNext,
|
||||
disableForStreaming,
|
||||
}: {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
handlePrevious: () => void;
|
||||
handleNext: () => void;
|
||||
disableForStreaming?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center text-sm space-x-0.5">
|
||||
<Hoverable
|
||||
icon={FiChevronLeft}
|
||||
onClick={currentPage === 1 ? undefined : handlePrevious}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Hoverable
|
||||
icon={FiChevronLeft}
|
||||
onClick={
|
||||
disableForStreaming
|
||||
? () => null
|
||||
: currentPage === 1
|
||||
? undefined
|
||||
: handlePrevious
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{disableForStreaming
|
||||
? "Wait for agent message to complete"
|
||||
: "Previous"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<span className="text-text-darker select-none">
|
||||
{currentPage} / {totalPages}
|
||||
</span>
|
||||
|
||||
<Hoverable
|
||||
icon={FiChevronRight}
|
||||
onClick={currentPage === totalPages ? undefined : handleNext}
|
||||
/>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div>
|
||||
<Hoverable
|
||||
icon={FiChevronRight}
|
||||
onClick={
|
||||
disableForStreaming
|
||||
? () => null
|
||||
: currentPage === totalPages
|
||||
? undefined
|
||||
: handleNext
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{disableForStreaming
|
||||
? "Wait for agent message to complete"
|
||||
: "Next"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -829,6 +872,7 @@ export const HumanMessage = ({
|
||||
onMessageSelection,
|
||||
shared,
|
||||
stopGenerating = () => null,
|
||||
disableSwitchingForStreaming = false,
|
||||
}: {
|
||||
shared?: boolean;
|
||||
content: string;
|
||||
@ -838,6 +882,7 @@ export const HumanMessage = ({
|
||||
onEdit?: (editedContent: string) => void;
|
||||
onMessageSelection?: (messageId: number) => void;
|
||||
stopGenerating?: () => void;
|
||||
disableSwitchingForStreaming?: boolean;
|
||||
}) => {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@ -1067,6 +1112,7 @@ export const HumanMessage = ({
|
||||
otherMessagesCanSwitchTo.length > 1 && (
|
||||
<div className="ml-auto mr-3">
|
||||
<MessageSwitcher
|
||||
disableForStreaming={disableSwitchingForStreaming}
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
|
@ -294,7 +294,7 @@ const SubQuestionDisplay: React.FC<{
|
||||
const renderedMarkdown = useMemo(() => {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className="prose max-w-full text-base"
|
||||
className="prose dark:prose-invert max-w-full text-base"
|
||||
components={markdownComponents}
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
@ -340,7 +340,7 @@ const SubQuestionDisplay: React.FC<{
|
||||
{subQuestion?.question || temporaryDisplay?.question}
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={`mt-0.5 text-text-darker transition-transform duration-500 ease-in-out ${
|
||||
className={`mt-0.5 flex-none text-text-darker transition-transform duration-500 ease-in-out ${
|
||||
toggled ? "" : "-rotate-90"
|
||||
}`}
|
||||
size={20}
|
||||
@ -632,9 +632,7 @@ const SubQuestionsDisplay: React.FC<SubQuestionsDisplayProps> = ({
|
||||
}
|
||||
`}</style>
|
||||
<div className="relative">
|
||||
{/* {subQuestions.map((subQuestion, index) => ( */}
|
||||
{memoizedSubQuestions.map((subQuestion, index) => (
|
||||
// {dynamicSubQuestions.map((subQuestion, index) => (
|
||||
<SubQuestionDisplay
|
||||
currentlyOpen={
|
||||
currentlyOpenQuestion?.level === subQuestion.level &&
|
||||
|
@ -131,7 +131,7 @@ const StandardAnswersTableRow = ({
|
||||
/>,
|
||||
<ReactMarkdown
|
||||
key={`answer-${standardAnswer.id}`}
|
||||
className="prose"
|
||||
className="prose dark:prose-invert"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{standardAnswer.answer}
|
||||
|
@ -562,6 +562,7 @@ body {
|
||||
.prose :where(pre):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
background-color: theme("colors.code-bg");
|
||||
font-size: theme("fontSize.code-sm");
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
pre[class*="language-"],
|
||||
@ -655,16 +656,3 @@ ul > li > p {
|
||||
display: inline;
|
||||
/* Make paragraphs inline to reduce vertical space */
|
||||
}
|
||||
|
||||
.dark strong {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.prose.dark li,
|
||||
.prose.dark h1,
|
||||
.prose.dark h2,
|
||||
.prose.dark h3,
|
||||
.prose.dark h4,
|
||||
.prose.dark h5 {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export const Hoverable: React.FC<{
|
||||
<div className="flex items-center">
|
||||
<Icon
|
||||
size={size}
|
||||
className="hover:bg-background-chat-hover dark:text-[#B4B4B4] text-neutral-600 rounded h-fit cursor-pointer"
|
||||
className="dark:text-[#B4B4B4] text-neutral-600 rounded h-fit cursor-pointer"
|
||||
/>
|
||||
{hoverText && (
|
||||
<div className="max-w-0 leading-none whitespace-nowrap overflow-hidden transition-all duration-300 ease-in-out group-hover:max-w-xs group-hover:ml-2">
|
||||
|
@ -50,7 +50,7 @@ export function SearchResultIcon({ url }: { url: string }) {
|
||||
return <SourceIcon sourceType={ValidSources.Web} iconSize={18} />;
|
||||
}
|
||||
if (url.includes("docs.onyx.app")) {
|
||||
return <OnyxIcon size={18} />;
|
||||
return <OnyxIcon size={18} className="dark:text-[#fff] text-[#000]" />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -23,7 +23,7 @@ export function WebResultIcon({
|
||||
return (
|
||||
<>
|
||||
{hostname == "docs.onyx.app" ? (
|
||||
<OnyxIcon size={size} />
|
||||
<OnyxIcon size={size} className="dark:text-[#fff] text-[#000]" />
|
||||
) : !error ? (
|
||||
<img
|
||||
className="my-0 rounded-full py-0"
|
||||
|
@ -432,7 +432,10 @@ export const MarkdownFormField = ({
|
||||
</div>
|
||||
{isPreviewOpen ? (
|
||||
<div className="p-4 border-t border-background-300">
|
||||
<ReactMarkdown className="prose" remarkPlugins={[remarkGfm]}>
|
||||
<ReactMarkdown
|
||||
className="prose dark:prose-invert"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{field.value}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ export default function BlurBackground({
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`
|
||||
desktop:hidden w-full h-full fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-30 transition-opacity duration-300 ease-in-out ${
|
||||
desktop:hidden w-full h-full fixed inset-0 bg-neutral-700 bg-opacity-50 backdrop-blur-sm z-30 transition-opacity duration-300 ease-in-out ${
|
||||
visible
|
||||
? "opacity-100 pointer-events-auto"
|
||||
: "opacity-0 pointer-events-none"
|
||||
|
@ -35,7 +35,7 @@ export const MinimalMarkdown: React.FC<MinimalMarkdownProps> = ({
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className={`w-full text-wrap break-word ${className}`}
|
||||
className={`w-full text-wrap break-word prose dark:prose-invert ${className}`}
|
||||
components={markdownComponents}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
|
@ -78,7 +78,7 @@ export function getUniqueIcons(docs: OnyxDocument[]): JSX.Element[] {
|
||||
|
||||
for (const doc of docs) {
|
||||
// If it's a web source, we check domain uniqueness
|
||||
if (doc.source_type === ValidSources.Web && doc.link) {
|
||||
if ((doc.is_internet || doc.source_type === ValidSources.Web) && doc.link) {
|
||||
const domain = getDomainFromUrl(doc.link);
|
||||
if (domain && !seenDomains.has(domain)) {
|
||||
seenDomains.add(domain);
|
||||
|
@ -47,7 +47,7 @@ export default function LogoWithText({
|
||||
className="flex gap-x-2 items-center ml-0 cursor-pointer desktop:hidden "
|
||||
>
|
||||
{!toggled ? (
|
||||
<Logo className="desktop:hidden -my-2" height={24} width={24} />
|
||||
<Logo className="desktop:hidden" height={24} width={24} />
|
||||
) : (
|
||||
<LogoComponent
|
||||
show={toggled}
|
||||
|
@ -23,8 +23,11 @@ import { AllUsersResponse } from "./types";
|
||||
import { Credential } from "./connectors/credentials";
|
||||
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||
import { Persona, PersonaLabel } from "@/app/admin/assistants/interfaces";
|
||||
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
|
||||
import { isAnthropic } from "@/app/admin/configuration/llm/interfaces";
|
||||
import {
|
||||
isAnthropic,
|
||||
LLMProviderDescriptor,
|
||||
} from "@/app/admin/configuration/llm/interfaces";
|
||||
|
||||
import { getSourceMetadata } from "./sources";
|
||||
import { AuthType, NEXT_PUBLIC_CLOUD_ENABLED } from "./constants";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
|
@ -79,12 +79,18 @@ export async function* handleStream<T extends NonEmptyObject>(
|
||||
}
|
||||
|
||||
export async function* handleSSEStream<T extends PacketType>(
|
||||
streamingResponse: Response
|
||||
streamingResponse: Response,
|
||||
signal?: AbortSignal
|
||||
): AsyncGenerator<T, void, unknown> {
|
||||
const reader = streamingResponse.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", () => {
|
||||
console.log("aborting");
|
||||
reader?.cancel();
|
||||
});
|
||||
}
|
||||
while (true) {
|
||||
const rawChunk = await reader?.read();
|
||||
if (!rawChunk) {
|
||||
|
@ -21,7 +21,6 @@ module.exports = {
|
||||
transitionProperty: {
|
||||
spacing: "margin, padding",
|
||||
},
|
||||
|
||||
keyframes: {
|
||||
"subtle-pulse": {
|
||||
"0%, 100%": { opacity: 0.9 },
|
||||
@ -148,7 +147,6 @@ module.exports = {
|
||||
"text-mobile-sidebar": "var(--text-800)",
|
||||
"background-search-filter": "var(--neutral-100-border-light)",
|
||||
"background-search-filter-dropdown": "var(--neutral-100-border-light)",
|
||||
"tw-prose-bold": "var(--text-800)",
|
||||
|
||||
"user-bubble": "var(--off-white)",
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user