mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-06-01 18:49:27 +02:00
Improved linking + scrolling (#3744)
* nits * quick nit * update various components * quick nit * update * chat nits * minor linear check fix
This commit is contained in:
parent
b12c51f56c
commit
d6863ec775
4
.github/workflows/pr-linear-check.yml
vendored
4
.github/workflows/pr-linear-check.yml
vendored
@ -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."
|
||||
|
@ -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]]'
|
||||
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -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;
|
||||
|
@ -694,6 +694,7 @@ export function ChatInputBar({
|
||||
flexPriority="stiff"
|
||||
name="Filters"
|
||||
Icon={FiFilter}
|
||||
toggle
|
||||
tooltipContent="Filter your search"
|
||||
/>
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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) =>
|
||||
|
@ -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}
|
||||
|
@ -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"); */
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -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}
|
||||
/>
|
||||
) : (
|
||||
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user