Improved linking + scrolling (#3744)

* nits

* quick nit

* update various components

* quick nit

* update

* chat nits

* minor linear check fix
This commit is contained in:
pablonyx 2025-01-25 15:52:07 -08:00 committed by GitHub
parent b12c51f56c
commit d6863ec775
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 359 additions and 263 deletions

View File

@ -9,9 +9,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check PR body for Linear link or override
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
PR_BODY="${{ github.event.pull_request.body }}"
# Looking for "https://linear.app" in the body
if echo "$PR_BODY" | grep -qE "https://linear\.app"; then
echo "Found a Linear link. Check passed."

View File

@ -890,47 +890,20 @@ export function AssistantEditor({
{imageGenerationTool && (
<>
<div className="flex items-center content-start mb-2">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<CheckboxField
size="sm"
id={`enabled_tools_map.${imageGenerationTool.id}`}
name={`enabled_tools_map.${imageGenerationTool.id}`}
onCheckedChange={() => {
if (isImageGenerationAvailable) {
toggleToolInValues(
imageGenerationTool.id
);
}
}}
className={
!isImageGenerationAvailable
? "opacity-50 cursor-not-allowed"
: ""
}
/>
</TooltipTrigger>
{!isImageGenerationAvailable && (
<TooltipContent side="top" align="center">
<p className="bg-background-900 max-w-[200px] mb-1 text-sm rounded-lg p-1.5 text-white">
{!currentLLMSupportsImageOutput
? "To use Image Generation, select GPT-4 or another image compatible model as the default model for this Assistant."
: "Image Generation requires an OpenAI or Azure Dalle configuration."}
</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
<div className="flex flex-col ml-2">
<span className="text-sm">
{imageGenerationTool.display_name}
</span>
<span className="text-xs text-subtle">
Generate and manipulate images using AI-powered
tools
</span>
</div>
<BooleanFormField
name={`enabled_tools_map.${imageGenerationTool.id}`}
label={imageGenerationTool.display_name}
subtext="Generate and manipulate images using AI-powered tools"
disabled={
!currentLLMSupportsImageOutput ||
!isImageGenerationAvailable
}
disabledTooltip={
!currentLLMSupportsImageOutput
? "To use Image Generation, select GPT-4 or another image compatible model as the default model for this Assistant."
: "Image Generation requires an OpenAI or Azure Dall-E configuration."
}
/>
</div>
</>
)}
@ -964,23 +937,12 @@ export function AssistantEditor({
{customTools.length > 0 &&
customTools.map((tool) => (
<React.Fragment key={tool.id}>
<div className="flex items-center content-start mb-2">
<Checkbox
size="sm"
id={`enabled_tools_map.${tool.id}`}
checked={values.enabled_tools_map[tool.id]}
onCheckedChange={() => {
toggleToolInValues(tool.id);
}}
/>
<div className="ml-2">
<span className="text-sm">
{tool.display_name}
</span>
</div>
</div>
</React.Fragment>
<BooleanFormField
key={tool.id}
name={`enabled_tools_map.${tool.id}`}
label={tool.display_name}
subtext={tool.description}
/>
))}
</div>
</div>
@ -1333,7 +1295,6 @@ export function AssistantEditor({
<BooleanFormField
small
removeIndent
alignTop
name="llm_relevance_filter"
label="AI Relevance Filter"
subtext="If enabled, the LLM will filter out documents that are not useful for answering the user query prior to generating a response. This typically improves the quality of the response but incurs slightly higher cost."
@ -1342,7 +1303,6 @@ export function AssistantEditor({
<BooleanFormField
small
removeIndent
alignTop
name="include_citations"
label="Citations"
subtext="Response will include citations ([1], [2], etc.) for documents referenced by the LLM. In general, we recommend to leave this enabled in order to increase trust in the LLM answer."
@ -1355,7 +1315,6 @@ export function AssistantEditor({
<BooleanFormField
small
removeIndent
alignTop
name="datetime_aware"
label="Date and Time Aware"
subtext='Toggle this option to let the assistant know the current date and time (formatted like: "Thursday Jan 1, 1970 00:01"). To inject it in a specific place in the prompt, use the pattern [[CURRENT_DATETIME]]'

View File

@ -50,7 +50,7 @@ export const rerankingModels: RerankingModel[] = [
cloud: true,
displayName: "LiteLLM",
description: "Host your own reranker or router with LiteLLM proxy",
link: "https://docs.litellm.ai/docs/proxy",
link: "https://docs.litellm.ai/docs/simple_proxy",
},
{
rerank_provider_type: null,
@ -82,7 +82,7 @@ export const rerankingModels: RerankingModel[] = [
modelName: "rerank-english-v3.0",
displayName: "Cohere English",
description: "High-performance English-focused reranking model.",
link: "https://docs.cohere.com/docs/rerank",
link: "https://docs.cohere.com/v2/reference/rerank",
},
{
cloud: true,
@ -90,7 +90,7 @@ export const rerankingModels: RerankingModel[] = [
modelName: "rerank-multilingual-v3.0",
displayName: "Cohere Multilingual",
description: "Powerful multilingual reranking model.",
link: "https://docs.cohere.com/docs/rerank",
link: "https://docs.cohere.com/v2/reference/rerank",
},
];

View File

@ -1,14 +1,13 @@
"use client";
import React, { useMemo, useState } from "react";
import { Persona } from "@/app/admin/assistants/interfaces";
import { useRouter } from "next/navigation";
import { Modal } from "@/components/Modal";
import AssistantCard from "./AssistantCard";
import { useAssistants } from "@/components/context/AssistantsContext";
import { useUser } from "@/components/user/UserProvider";
import { FilterIcon } from "lucide-react";
import { checkUserOwnsAssistant } from "@/lib/assistants/checkOwnership";
import { Dialog, DialogContent } from "@/components/ui/dialog";
export const AssistantBadgeSelector = ({
text,
@ -21,11 +20,12 @@ export const AssistantBadgeSelector = ({
}) => {
return (
<div
className={`${
selected
? "bg-neutral-900 text-white"
: "bg-transparent text-neutral-900"
} w-12 h-5 text-center px-1 py-0.5 rounded-lg cursor-pointer text-[12px] font-normal leading-[10px] border border-black justify-center items-center gap-1 inline-flex`}
className={`
select-none ${
selected
? "bg-neutral-900 text-white"
: "bg-transparent text-neutral-900"
} w-12 h-5 text-center px-1 py-0.5 rounded-lg cursor-pointer text-[12px] font-normal leading-[10px] border border-black justify-center items-center gap-1 inline-flex`}
onClick={toggleFilter}
>
{text}
@ -60,11 +60,15 @@ const useAssistantFilter = () => {
return { assistantFilters, toggleAssistantFilter, setAssistantFilters };
};
export default function AssistantModal({
hideModal,
}: {
interface AssistantModalProps {
hideModal: () => void;
}) {
modalHeight?: string;
}
export function AssistantModal({
hideModal,
modalHeight,
}: AssistantModalProps) {
const { assistants, pinnedAssistants } = useAssistants();
const { assistantFilters, toggleAssistantFilter } = useAssistantFilter();
const router = useRouter();
@ -86,11 +90,11 @@ export default function AssistantModal({
!assistantFilters[AssistantFilter.Private] || !assistant.is_public;
const pinnedFilter =
!assistantFilters[AssistantFilter.Pinned] ||
(user?.preferences?.pinned_assistants?.includes(assistant.id) ?? false);
(pinnedAssistants.map((a) => a.id).includes(assistant.id) ?? false);
const mineFilter =
!assistantFilters[AssistantFilter.Mine] ||
assistants.map((a: Persona) => checkUserOwnsAssistant(user, a));
checkUserOwnsAssistant(user, assistant);
return (
(nameMatches || labelMatches) &&
@ -111,142 +115,145 @@ export default function AssistantModal({
(assistant) => !assistant.builtin_persona && !assistant.is_default_persona
);
const maxHeight = 900;
const calculatedHeight = Math.min(
Math.ceil(assistants.length / 2) * 170 + 200,
window.innerHeight * 0.8
);
const height = Math.min(calculatedHeight, maxHeight);
return (
<Modal
heightOverride={`${height}px`}
onOutsideClick={hideModal}
removeBottomPadding
className={`max-w-4xl max-h-[90vh] ${height} w-[95%] overflow-hidden`}
>
<div className="flex flex-col h-full">
<div className="flex bg-background flex-col sticky top-0 z-10">
<div className="flex px-2 justify-between items-center gap-x-2 mb-0">
<div className="h-12 w-full rounded-lg flex-col justify-center items-start gap-2.5 inline-flex">
<div className="h-12 rounded-md w-full shadow-[0px_0px_2px_0px_rgba(0,0,0,0.25)] border border-[#dcdad4] flex items-center px-3">
{!isSearchFocused && (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
)}
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onFocus={() => setIsSearchFocused(true)}
onBlur={() => setIsSearchFocused(false)}
type="text"
className="w-full h-full bg-transparent outline-none text-black"
/>
</div>
</div>
<button
onClick={() => router.push("/assistants/new")}
className="h-10 cursor-pointer px-6 py-3 bg-black rounded-md border border-black justify-center items-center gap-2.5 inline-flex"
>
<div className="text-[#fffcf4] text-lg font-normal leading-normal">
Create
</div>
</button>
</div>
<div className="px-2 flex py-4 items-center gap-x-2 flex-wrap">
<FilterIcon size={16} />
<AssistantBadgeSelector
text="Pinned"
selected={assistantFilters[AssistantFilter.Pinned]}
toggleFilter={() => toggleAssistantFilter(AssistantFilter.Pinned)}
/>
<AssistantBadgeSelector
text="Mine"
selected={assistantFilters[AssistantFilter.Mine]}
toggleFilter={() => toggleAssistantFilter(AssistantFilter.Mine)}
/>
<AssistantBadgeSelector
text="Private"
selected={assistantFilters[AssistantFilter.Private]}
toggleFilter={() =>
toggleAssistantFilter(AssistantFilter.Private)
}
/>
<AssistantBadgeSelector
text="Public"
selected={assistantFilters[AssistantFilter.Public]}
toggleFilter={() => toggleAssistantFilter(AssistantFilter.Public)}
/>
</div>
<div className="w-full border-t border-neutral-200" />
</div>
<div className="flex-grow overflow-y-auto">
<h2 className="text-2xl font-semibold text-gray-800 mb-2 px-4 py-2">
Featured Assistants
</h2>
<div className="w-full px-2 pb-2 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-6">
{featuredAssistants.length > 0 ? (
featuredAssistants.map((assistant, index) => (
<div key={index}>
<AssistantCard
pinned={pinnedAssistants
.map((a) => a.id)
.includes(assistant.id)}
persona={assistant}
closeModal={hideModal}
<Dialog open={true} onOpenChange={(open) => !open && hideModal()}>
<DialogContent
className="p-0 overflow-hidden max-h-[80vh] max-w-none w-[95%] bg-background rounded-sm shadow-2xl transform transition-all duration-300 ease-in-out relative w-11/12 max-w-4xl pt-10 pb-10 px-10 overflow-hidden flex flex-col max-w-4xl"
style={{
position: "fixed",
top: "10vh",
left: "50%",
transform: "translateX(-50%)",
margin: 0,
}}
>
<div className="flex overflow-hidden flex-col h-full">
<div className="flex flex-col sticky top-0 z-10">
<div className="flex px-2 justify-between items-center gap-x-2 mb-0">
<div className="h-12 w-full rounded-lg flex-col justify-center items-start gap-2.5 inline-flex">
<div className="h-12 rounded-md w-full shadow-[0px_0px_2px_0px_rgba(0,0,0,0.25)] border border-[#dcdad4] flex items-center px-3">
{!isSearchFocused && (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 text-gray-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
)}
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onFocus={() => setIsSearchFocused(true)}
onBlur={() => setIsSearchFocused(false)}
type="text"
className="w-full h-full bg-transparent outline-none text-black"
/>
</div>
))
) : (
<div className="col-span-2 text-center text-gray-500">
No featured assistants match filters
</div>
)}
<button
onClick={() => router.push("/assistants/new")}
className="h-10 cursor-pointer px-6 py-3 bg-black rounded-md border border-black justify-center items-center gap-2.5 inline-flex"
>
<div className="text-[#fffcf4] text-lg font-normal leading-normal">
Create
</div>
</button>
</div>
<div className="px-2 flex py-4 items-center gap-x-2 flex-wrap">
<FilterIcon size={16} />
<AssistantBadgeSelector
text="Pinned"
selected={assistantFilters[AssistantFilter.Pinned]}
toggleFilter={() =>
toggleAssistantFilter(AssistantFilter.Pinned)
}
/>
<AssistantBadgeSelector
text="Mine"
selected={assistantFilters[AssistantFilter.Mine]}
toggleFilter={() => toggleAssistantFilter(AssistantFilter.Mine)}
/>
<AssistantBadgeSelector
text="Private"
selected={assistantFilters[AssistantFilter.Private]}
toggleFilter={() =>
toggleAssistantFilter(AssistantFilter.Private)
}
/>
<AssistantBadgeSelector
text="Public"
selected={assistantFilters[AssistantFilter.Public]}
toggleFilter={() =>
toggleAssistantFilter(AssistantFilter.Public)
}
/>
</div>
<div className="w-full border-t border-neutral-200" />
</div>
{allAssistants && allAssistants.length > 0 && (
<>
<h2 className="text-2xl font-semibold text-gray-800 mt-4 mb-2 px-4 py-2">
All Assistants
</h2>
<div className="flex-grow overflow-y-auto">
<h2 className="text-2xl font-semibold text-gray-800 mb-2 px-4 py-2">
Featured Assistants
</h2>
<div className="w-full mt-2 px-2 pb-2 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-6">
{allAssistants
.sort((a, b) => b.id - a.id)
.map((assistant, index) => (
<div key={index}>
<AssistantCard
pinned={
user?.preferences?.pinned_assistants?.includes(
assistant.id
) ?? false
}
persona={assistant}
closeModal={hideModal}
/>
</div>
))}
</div>
</>
)}
<div className="w-full px-2 pb-2 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-6">
{featuredAssistants.length > 0 ? (
featuredAssistants.map((assistant, index) => (
<div key={index}>
<AssistantCard
pinned={pinnedAssistants
.map((a) => a.id)
.includes(assistant.id)}
persona={assistant}
closeModal={hideModal}
/>
</div>
))
) : (
<div className="col-span-2 text-center text-gray-500">
No featured assistants match filters
</div>
)}
</div>
{allAssistants && allAssistants.length > 0 && (
<>
<h2 className="text-2xl font-semibold text-gray-800 mt-4 mb-2 px-4 py-2">
All Assistants
</h2>
<div className="w-full mt-2 px-2 pb-2 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-6">
{allAssistants
.sort((a, b) => b.id - a.id)
.map((assistant, index) => (
<div key={index}>
<AssistantCard
pinned={
user?.preferences?.pinned_assistants?.includes(
assistant.id
) ?? false
}
persona={assistant}
closeModal={hideModal}
/>
</div>
))}
</div>
</>
)}
</div>
</div>
</div>
</Modal>
</DialogContent>
</Dialog>
);
}
export default AssistantModal;

View File

@ -694,6 +694,7 @@ export function ChatInputBar({
flexPriority="stiff"
name="Filters"
Icon={FiFilter}
toggle
tooltipContent="Filter your search"
/>
}

View File

@ -22,7 +22,7 @@ export const MemoizedAnchor = memo(
const index = parseInt(match[1], 10) - 1;
const associatedDoc = docs?.[index];
if (!associatedDoc) {
return <>{children}</>;
return <a href={children as string}>{children}</a>;
}
let icon: React.ReactNode = null;
@ -77,9 +77,24 @@ export const MemoizedLink = memo((props: any) => {
);
}
const handleMouseDown = () => {
let url = rest.href || rest.children?.toString();
if (url && !url.startsWith("http://") && !url.startsWith("https://")) {
// Try to construct a valid URL
const httpsUrl = `https://${url}`;
try {
new URL(httpsUrl);
url = httpsUrl;
} catch {
// If not a valid URL, don't modify original url
}
}
window.open(url, "_blank");
};
return (
<a
onMouseDown={() => rest.href && window.open(rest.href, "_blank")}
onMouseDown={handleMouseDown}
className="cursor-pointer text-link hover:text-link-hover"
>
{rest.children}

View File

@ -375,7 +375,11 @@ export const AIMessage = ({
return (
<>
<div
style={{ position: "absolute", left: "-9999px" }}
style={{
position: "absolute",
left: "-9999px",
display: "none",
}}
dangerouslySetInnerHTML={{ __html: htmlContent }}
/>
<ReactMarkdown

View File

@ -6,8 +6,17 @@ import React, {
useContext,
useState,
useCallback,
useLayoutEffect,
useRef,
} from "react";
import Link from "next/link";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipProvider,
} from "@/components/ui/tooltip";
import { useRouter, useSearchParams } from "next/navigation";
import { ChatSession } from "../interfaces";
import { NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA } from "@/lib/constants";
@ -44,6 +53,7 @@ import {
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { CircleX } from "lucide-react";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
interface HistorySidebarProps {
page: pageType;
@ -90,6 +100,24 @@ const SortableAssistant: React.FC<SortableAssistantProps> = ({
...(isDragging ? { zIndex: 1000, position: "relative" as const } : {}),
};
const nameRef = useRef<HTMLParagraphElement>(null);
const hiddenNameRef = useRef<HTMLSpanElement>(null);
const [isNameTruncated, setIsNameTruncated] = useState(false);
useLayoutEffect(() => {
const checkTruncation = () => {
if (nameRef.current && hiddenNameRef.current) {
const visibleWidth = nameRef.current.offsetWidth;
const fullTextWidth = hiddenNameRef.current.offsetWidth;
setIsNameTruncated(fullTextWidth > visibleWidth);
}
};
checkTruncation();
window.addEventListener("resize", checkTruncation);
return () => window.removeEventListener("resize", checkTruncation);
}, [assistant.name]);
return (
<div
ref={setNodeRef}
@ -115,10 +143,28 @@ const SortableAssistant: React.FC<SortableAssistantProps> = ({
: ""
} relative flex items-center gap-x-2 py-1 px-2 rounded-md`}
>
<AssistantIcon assistant={assistant} size={16} className="flex-none" />
<p className="text-base text-left w-fit line-clamp-1 text-ellipsis text-black">
<AssistantIcon assistant={assistant} size={20} className="flex-none" />
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<p
ref={nameRef}
className="text-base text-left w-fit line-clamp-1 text-ellipsis text-black"
>
{assistant.name}
</p>
</TooltipTrigger>
{isNameTruncated && (
<TooltipContent>{assistant.name}</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
<span
ref={hiddenNameRef}
className="absolute left-[-9999px] whitespace-nowrap"
>
{assistant.name}
</p>
</span>
<button
onClick={(e) => {
e.stopPropagation();
@ -295,7 +341,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
</div>
)}
<div className="h-full relative overflow-y-auto">
<div className="h-full relative overflow-x-hidden overflow-y-auto">
<div className="flex px-4 font-normal text-sm gap-x-2 leading-normal text-[#6c6c6c]/80 items-center font-normal leading-normal">
Assistants
</div>
@ -303,6 +349,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis]}
>
<SortableContext
items={pinnedAssistants.map((a) =>

View File

@ -13,7 +13,7 @@ import { FiPlus, FiTrash2, FiCheck, FiX } from "react-icons/fi";
import { NEXT_PUBLIC_DELETE_ALL_CHATS_ENABLED } from "@/lib/constants";
import { FolderDropdown } from "../folders/FolderDropdown";
import { ChatSessionDisplay } from "./ChatSessionDisplay";
import { useState, useCallback, useRef, useContext } from "react";
import { useState, useCallback, useRef, useContext, useEffect } from "react";
import { Caret } from "@/components/icons/icons";
import { groupSessionsByDateRange } from "../lib";
import React from "react";
@ -36,6 +36,7 @@ import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useChatContext } from "@/components/context/ChatContext";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
interface SortableFolderProps {
folder: Folder;
@ -53,34 +54,41 @@ interface SortableFolderProps {
const SortableFolder: React.FC<SortableFolderProps> = (props) => {
const settings = useContext(SettingsContext);
const mobile = settings?.isMobile;
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({
id: props.folder.folder_id?.toString() ?? "",
data: {
activationConstraint: {
distance: 8,
},
},
disabled: mobile,
});
const [isDragging, setIsDragging] = useState(false);
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging: isDraggingDndKit,
} = useSortable({
id: props.folder.folder_id?.toString() ?? "",
disabled: mobile,
});
const ref = useRef<HTMLDivElement>(null);
const style = {
const style: React.CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 1000 : "auto",
position: isDragging ? "relative" : "static",
opacity: isDragging ? 0.6 : 1,
};
useEffect(() => {
setIsDragging(isDraggingDndKit);
}, [isDraggingDndKit]);
return (
<div
ref={setNodeRef}
className="pr-3 ml-4 overflow-visible flex items-start"
style={style}
{...attributes}
{...listeners}
>
<FolderDropdown
ref={ref}
{...props}
{...(mobile ? {} : attributes)}
{...(mobile ? {} : listeners)}
/>
<FolderDropdown ref={ref} {...props} />
</div>
);
};
@ -359,6 +367,7 @@ export function PagesTab({
{folders && folders.length > 0 && (
<DndContext
modifiers={[restrictToVerticalAxis]}
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}

View File

@ -287,11 +287,53 @@
overflow-x: hidden;
}
.scrollbar {
width: 100%;
height: 100%;
}
/* Styling for textarea scrollbar */
textarea::-webkit-scrollbar {
width: 8px;
}
textarea::-webkit-scrollbar-track {
background: var(--scrollbar-track);
border-radius: 4px;
}
textarea::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 4px;
}
textarea::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover);
}
/* Styling for textarea resize handle */
textarea {
resize: vertical;
}
/* For Firefox */
textarea {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
.inputscroll::-webkit-scrollbar-track {
background: #e5e7eb;
scrollbar-width: none;
}
::-webkit-scrollbar {
width: 0px;
/* Vertical scrollbar width */
height: 8px;
/* Horizontal scrollbar height */
}
::-webkit-scrollbar-track {
background: transparent;
/* background: theme("colors.scrollbar.track"); */

View File

@ -63,7 +63,7 @@ export function Modal({
<div
onMouseDown={handleMouseDown}
className={cn(
`fixed inset-0 bg-black bg-opacity-25 backdrop-blur-sm h-full
`fixed inset-0 bg-white bg-opacity-25 backdrop-blur-sm h-full
flex items-center justify-center z-[9999] transition-opacity duration-300 ease-in-out`
)}
>
@ -76,6 +76,7 @@ export function Modal({
}}
className={`
bg-background
bg-blue-400
text-emphasis
rounded
shadow-2xl

View File

@ -25,11 +25,13 @@ import {
} from "@/components/ui/tooltip";
import ReactMarkdown from "react-markdown";
import { FaMarkdown } from "react-icons/fa";
import { useRef, useState } from "react";
import { useRef, useState, useCallback } from "react";
import remarkGfm from "remark-gfm";
import { EditIcon } from "@/components/icons/icons";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { CheckboxField } from "@/components/ui/checkbox";
import { CheckedState } from "@radix-ui/react-checkbox";
export function SectionHeader({
children,
@ -51,7 +53,7 @@ export function Label({
return (
<div
className={`block font-medium base ${className} ${
small ? "text-sm" : "text-base"
small ? "text-xs" : "text-sm"
}`}
>
{children}
@ -75,7 +77,7 @@ export function LabelWithTooltip({
}
export function SubLabel({ children }: { children: string | JSX.Element }) {
return <div className="text-sm text-subtle mb-2">{children}</div>;
return <div className="text-xs text-subtle">{children}</div>;
}
export function ManualErrorMessage({ children }: { children: string }) {
@ -439,53 +441,62 @@ interface BooleanFormFieldProps {
name: string;
label: string;
subtext?: string | JSX.Element;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
removeIndent?: boolean;
small?: boolean;
alignTop?: boolean;
noLabel?: boolean;
disabled?: boolean;
checked?: boolean;
optional?: boolean;
tooltip?: string;
disabledTooltip?: string;
}
export const BooleanFormField = ({
name,
label,
subtext,
onChange,
removeIndent,
noLabel,
optional,
small,
disabled,
alignTop,
checked,
tooltip,
disabledTooltip,
}: BooleanFormFieldProps) => {
const [field, meta, helpers] = useField<boolean>(name);
const { setValue } = helpers;
const { setFieldValue } = useFormikContext<any>();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.checked);
if (onChange) {
onChange(e);
}
};
const handleChange = useCallback(
(checked: CheckedState) => {
if (!disabled) {
setFieldValue(name, checked);
}
},
[disabled, name, setFieldValue]
);
return (
<div>
<label className="flex text-sm">
<Field
type="checkbox"
{...field}
checked={checked !== undefined ? checked : field.value}
disabled={disabled}
onChange={handleChange}
className={`${removeIndent ? "mr-2" : "mx-3"}
px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`}
/>
<label className="flex items-center text-sm cursor-pointer">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<CheckboxField
name={name}
size="sm"
className={`
${disabled ? "opacity-50" : ""}
${removeIndent ? "mr-2" : "mx-3"}`}
onCheckedChange={handleChange}
/>
</TooltipTrigger>
{disabled && disabledTooltip && (
<TooltipContent side="top" align="center">
<p className="bg-background-900 max-w-[200px] mb-1 text-sm rounded-lg p-1.5 text-white">
{disabledTooltip}
</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
{!noLabel && (
<div>
<div className="flex items-center gap-x-2">

View File

@ -139,7 +139,7 @@ export function AssistantIcon({
alt={assistant.name}
src={buildImgUrl(assistant.uploaded_image_id)}
loading="lazy"
className={`h-[${dimension}px] w-[${dimension}px] object-cover object-center rounded-sm transition-opacity duration-300 ${wrapperClass}`}
className={`h-[${dimension}px] w-[${dimension}px] rounded-full object-cover object-center transition-opacity duration-300 ${wrapperClass}`}
style={style}
/>
) : (

View File

@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}