mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
fix: refine project navigation affordances
This commit is contained in:
parent
4b2f78520d
commit
ec9f3d6661
6 changed files with 196 additions and 70 deletions
|
|
@ -13,6 +13,7 @@ export interface ChatsHeaderControls {
|
|||
onOrganizationModeChange: (mode: SidebarOrganizationMode) => void;
|
||||
onChatSortByChange: (sortBy: SidebarChatSort) => void;
|
||||
onNewProject: () => void;
|
||||
onOpenProjects: () => void;
|
||||
}
|
||||
|
||||
interface ChatsHeaderProps extends ChatsHeaderControls {
|
||||
|
|
@ -78,6 +79,7 @@ const ChatsHeader: FC<ChatsHeaderProps> = ({
|
|||
onOrganizationModeChange,
|
||||
onChatSortByChange,
|
||||
onNewProject,
|
||||
onOpenProjects,
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const menuId = useId();
|
||||
|
|
@ -144,6 +146,10 @@ const ChatsHeader: FC<ChatsHeaderProps> = ({
|
|||
],
|
||||
[chatSortBy, localize, onChatSortByChange, onOrganizationModeChange, organizationMode],
|
||||
);
|
||||
const headerLabel =
|
||||
organizationMode === 'chronological'
|
||||
? localize('com_ui_all_chats')
|
||||
: localize('com_ui_projects');
|
||||
|
||||
return (
|
||||
<div className="flex h-8 w-full items-center gap-0.5">
|
||||
|
|
@ -153,7 +159,7 @@ const ChatsHeader: FC<ChatsHeaderProps> = ({
|
|||
type="button"
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
<span className="select-none truncate">{localize('com_ui_all_chats')}</span>
|
||||
<span className="select-none truncate">{headerLabel}</span>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-3 w-3 shrink-0 transition-transform duration-200',
|
||||
|
|
@ -193,6 +199,20 @@ const ChatsHeader: FC<ChatsHeaderProps> = ({
|
|||
items={dropdownItems}
|
||||
/>
|
||||
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_all_projects')}
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
aria-label={localize('com_ui_all_projects')}
|
||||
className={headerIconButtonClassName}
|
||||
onClick={onOpenProjects}
|
||||
>
|
||||
<Folder className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_new_project')}
|
||||
render={
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { memo, useCallback, useEffect, useMemo, useRef, useState, type FC } from 'react';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { ChevronDown, Folder, FolderOpen } from 'lucide-react';
|
||||
import { ChevronDown, Folder, FolderOpen, SquarePen } from 'lucide-react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Spinner, useMediaQuery } from '@librechat/client';
|
||||
import { useQueries } from '@tanstack/react-query';
|
||||
import { useQueries, useQueryClient } from '@tanstack/react-query';
|
||||
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
|
||||
import { dataService, QueryKeys } from 'librechat-data-provider';
|
||||
import type {
|
||||
|
|
@ -21,8 +21,8 @@ import {
|
|||
useConversationsInfiniteQuery,
|
||||
useProjectsInfiniteQuery,
|
||||
} from '~/data-provider';
|
||||
import { useFavorites, useLocalize, useShowMarketplace } from '~/hooks';
|
||||
import { groupConversationsByDate, cn } from '~/utils';
|
||||
import { useFavorites, useLocalize, useNewConvo, useShowMarketplace } from '~/hooks';
|
||||
import { clearMessagesCache, groupConversationsByDate, cn } from '~/utils';
|
||||
import { DateLabel } from './Conversations';
|
||||
import ChatsHeader from './Header';
|
||||
import Convo from './Convo';
|
||||
|
|
@ -38,6 +38,7 @@ type Section = {
|
|||
type FlattenedItem =
|
||||
| { type: 'favorites' }
|
||||
| { type: 'chats-header' }
|
||||
| { type: 'projects-overview' }
|
||||
| { type: 'section'; section: Section; isExpanded: boolean }
|
||||
| { type: 'date'; sectionId?: string; groupName: string; isFirst?: boolean }
|
||||
| { type: 'convo'; sectionId?: string; convo: TConversation }
|
||||
|
|
@ -113,10 +114,12 @@ const SectionHeader = memo(
|
|||
section,
|
||||
isExpanded,
|
||||
onToggle,
|
||||
onStartProjectChat,
|
||||
}: {
|
||||
section: Section;
|
||||
isExpanded: boolean;
|
||||
onToggle: () => void;
|
||||
onStartProjectChat: (project: TChatProject, event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const Icon = isExpanded ? FolderOpen : Folder;
|
||||
|
|
@ -126,33 +129,55 @@ const SectionHeader = memo(
|
|||
: undefined;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={isExpanded}
|
||||
onClick={onToggle}
|
||||
<div
|
||||
className={cn(
|
||||
'group flex h-8 w-full items-center gap-2 rounded-lg px-1 text-left text-sm outline-none transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-black dark:focus-visible:ring-white',
|
||||
'group/project-row flex h-8 w-full items-center rounded-lg text-sm outline-none transition-colors duration-150',
|
||||
isExpanded ? 'text-text-primary' : 'text-text-secondary',
|
||||
'hover:bg-surface-active-alt hover:text-text-primary',
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
className={cn(
|
||||
'h-4 w-4 shrink-0 transition-colors duration-150',
|
||||
isExpanded ? 'text-text-primary' : 'text-text-secondary group-hover:text-text-primary',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate">{section.title}</span>
|
||||
{countLabel && <span className="shrink-0 text-xs text-text-tertiary">{countLabel}</span>}
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-3 w-3 shrink-0 text-text-tertiary transition-transform duration-200',
|
||||
isExpanded ? 'rotate-180' : '',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={isExpanded}
|
||||
onClick={onToggle}
|
||||
className="flex h-full min-w-0 flex-1 items-center gap-2 rounded-lg px-1 text-left outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-black dark:focus-visible:ring-white"
|
||||
>
|
||||
<Icon
|
||||
className={cn(
|
||||
'h-4 w-4 shrink-0 transition-colors duration-150',
|
||||
isExpanded
|
||||
? 'text-text-primary'
|
||||
: 'text-text-secondary group-hover/project-row:text-text-primary',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate">{section.title}</span>
|
||||
{countLabel && <span className="shrink-0 text-xs text-text-tertiary">{countLabel}</span>}
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-3 w-3 shrink-0 text-text-tertiary transition-transform duration-200',
|
||||
isExpanded ? 'rotate-180' : '',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
{section.project && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={localize('com_ui_new_chat_in_project', { name: section.project.name })}
|
||||
title={localize('com_ui_new_chat_in_project', { name: section.project.name })}
|
||||
className={cn(
|
||||
'mr-1 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-text-secondary outline-none transition-all duration-150 hover:bg-surface-hover hover:text-text-primary focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-black dark:focus-visible:ring-white',
|
||||
isExpanded
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 group-focus-within/project-row:opacity-100 group-hover/project-row:opacity-100',
|
||||
)}
|
||||
onClick={(event) => onStartProjectChat(section.project as TChatProject, event)}
|
||||
>
|
||||
<SquarePen className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -165,6 +190,22 @@ const EmptyRow = memo(({ title }: { title: string }) => {
|
|||
|
||||
EmptyRow.displayName = 'ProjectEmptyRow';
|
||||
|
||||
const ProjectsOverviewRow = memo(({ onOpenProjects }: { onOpenProjects: () => void }) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="group flex h-8 w-full items-center gap-2 rounded-lg px-1 text-left text-sm text-text-secondary outline-none transition-colors duration-150 hover:bg-surface-active-alt hover:text-text-primary focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-black dark:focus-visible:ring-white"
|
||||
onClick={onOpenProjects}
|
||||
>
|
||||
<Folder className="h-4 w-4 shrink-0 text-text-secondary group-hover:text-text-primary" />
|
||||
<span className="min-w-0 flex-1 truncate">{localize('com_ui_all_projects')}</span>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
ProjectsOverviewRow.displayName = 'ProjectsOverviewRow';
|
||||
|
||||
const ProjectConversations: FC<ProjectConversationsProps> = ({
|
||||
mode,
|
||||
chatSortBy,
|
||||
|
|
@ -177,8 +218,11 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
|
|||
chatsHeaderControls,
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const search = useRecoilValue(store.search);
|
||||
const conversation = useRecoilValue(store.conversationByIndex(0));
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const { newConversation } = useNewConvo();
|
||||
const { favorites, isLoading: isFavoritesLoading } = useFavorites();
|
||||
const showAgentMarketplace = useShowMarketplace();
|
||||
const favoritesContentKeyRef = useRef('');
|
||||
|
|
@ -403,6 +447,10 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
|
|||
};
|
||||
}
|
||||
|
||||
if (!isSearching) {
|
||||
items.push({ type: 'projects-overview' });
|
||||
}
|
||||
|
||||
if (isSearching) {
|
||||
if (isSearchConversationsLoading) {
|
||||
items.push({ type: 'loading', key: 'loading-search-conversations' });
|
||||
|
|
@ -557,6 +605,9 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
|
|||
if (item.type === 'chats-header') {
|
||||
return 'project-chats-header';
|
||||
}
|
||||
if (item.type === 'projects-overview') {
|
||||
return 'project-overview';
|
||||
}
|
||||
if (item.type === 'date') {
|
||||
return `project-date-${item.sectionId ?? 'search'}-${item.groupName}-${
|
||||
item.isFirst ? 'first' : 'next'
|
||||
|
|
@ -601,6 +652,22 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
|
|||
containerRef.current?.scrollToPosition(0);
|
||||
}, [containerRef]);
|
||||
|
||||
const startProjectChat = useCallback(
|
||||
(project: TChatProject, event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.stopPropagation();
|
||||
const path = `/c/new?projectId=${encodeURIComponent(project._id)}`;
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
window.open(path, '_blank');
|
||||
return;
|
||||
}
|
||||
clearMessagesCache(queryClient, conversation?.conversationId);
|
||||
queryClient.invalidateQueries([QueryKeys.messages]);
|
||||
newConversation({ template: { chatProjectId: project._id } });
|
||||
toggleNav();
|
||||
},
|
||||
[conversation?.conversationId, newConversation, queryClient, toggleNav],
|
||||
);
|
||||
|
||||
const toggleSection = useCallback((sectionId: string) => {
|
||||
setExpandedSectionIds((current) => {
|
||||
const next = new Set(current);
|
||||
|
|
@ -746,6 +813,14 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (item.type === 'projects-overview') {
|
||||
return (
|
||||
<MeasuredRow key={key} {...rowProps}>
|
||||
<ProjectsOverviewRow onOpenProjects={chatsHeaderControls.onOpenProjects} />
|
||||
</MeasuredRow>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.type === 'section') {
|
||||
return (
|
||||
<MeasuredRow key={key} {...rowProps}>
|
||||
|
|
@ -753,6 +828,7 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
|
|||
section={item.section}
|
||||
isExpanded={item.isExpanded}
|
||||
onToggle={() => toggleSection(item.section.id)}
|
||||
onStartProjectChat={startProjectChat}
|
||||
/>
|
||||
</MeasuredRow>
|
||||
);
|
||||
|
|
@ -800,6 +876,7 @@ const ProjectConversations: FC<ProjectConversationsProps> = ({
|
|||
isSmallScreen,
|
||||
setIsChatsExpanded,
|
||||
chatsHeaderControls,
|
||||
startProjectChat,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { useCallback, useId, useMemo, useState } from 'react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { ArrowLeft, ArrowUpDown, Check, Folder, Plus, SlidersHorizontal } from 'lucide-react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import type { ConversationListResponse } from 'librechat-data-provider';
|
||||
import { Button, Spinner, DropdownPopup } from '@librechat/client';
|
||||
import type { MenuItemProps, RenderProp } from '~/common';
|
||||
|
|
@ -11,8 +13,14 @@ import {
|
|||
useGetStartupConfig,
|
||||
useProjectQuery,
|
||||
} from '~/data-provider';
|
||||
import { useAgentsMap, useLocalize } from '~/hooks';
|
||||
import { cn, getDefaultModelSpec, getLocalStorageItems, getModelSpec } from '~/utils';
|
||||
import { useAgentsMap, useLocalize, useNewConvo } from '~/hooks';
|
||||
import {
|
||||
cn,
|
||||
clearMessagesCache,
|
||||
getDefaultModelSpec,
|
||||
getLocalStorageItems,
|
||||
getModelSpec,
|
||||
} from '~/utils';
|
||||
import ProjectChatList from './ProjectChatList';
|
||||
import store from '~/store';
|
||||
|
||||
|
|
@ -36,6 +44,7 @@ function renderSortMenuItem(label: string, isSelected: boolean): RenderProp {
|
|||
export default function ProjectWorkspace() {
|
||||
const localize = useLocalize();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { projectId = '' } = useParams();
|
||||
const [sortBy, setSortBy] = useState<ChatSortField>('updatedAt');
|
||||
const sortMenuId = useId();
|
||||
|
|
@ -43,6 +52,7 @@ export default function ProjectWorkspace() {
|
|||
const { data: startupConfig } = useGetStartupConfig();
|
||||
const { data: project, isLoading: isProjectLoading } = useProjectQuery(projectId);
|
||||
const conversation = useRecoilValue(store.conversationByIndex(0));
|
||||
const { newConversation } = useNewConvo();
|
||||
const agentsMap = useAgentsMap({ isAuthenticated: true });
|
||||
const activeProjectId = project?._id;
|
||||
const activeSettingsLabel = useMemo(() => {
|
||||
|
|
@ -145,10 +155,10 @@ export default function ProjectWorkspace() {
|
|||
if (!activeProjectId) {
|
||||
return;
|
||||
}
|
||||
navigate(`/c/new?projectId=${encodeURIComponent(activeProjectId)}`, {
|
||||
state: { focusChat: true },
|
||||
});
|
||||
}, [activeProjectId, navigate]);
|
||||
clearMessagesCache(queryClient, conversation?.conversationId);
|
||||
queryClient.invalidateQueries([QueryKeys.messages]);
|
||||
newConversation({ template: { chatProjectId: activeProjectId } });
|
||||
}, [activeProjectId, conversation?.conversationId, newConversation, queryClient]);
|
||||
|
||||
if (isProjectLoading) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -94,18 +94,50 @@ export default function ProjectsView() {
|
|||
|
||||
return (
|
||||
<main className="flex h-full min-h-0 flex-col overflow-auto bg-presentation text-text-primary">
|
||||
<div className="container mx-auto flex w-full max-w-4xl flex-1 flex-col gap-6 px-4 py-8 md:px-6">
|
||||
<div className="container mx-auto flex w-full max-w-4xl flex-1 flex-col gap-6 px-4 py-8 md:px-6 lg:pt-12">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-text-primary md:text-3xl">
|
||||
{localize('com_ui_projects')}
|
||||
</h1>
|
||||
<Button type="button" variant="submit" size="sm" onClick={() => setIsCreating(true)}>
|
||||
<Plus className="h-4 w-4" aria-hidden="true" />
|
||||
{localize('com_ui_new_project')}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="hidden text-sm text-text-secondary sm:inline">
|
||||
{localize('com_ui_sort_by')}
|
||||
</span>
|
||||
<DropdownPopup
|
||||
portal={true}
|
||||
focusLoop={true}
|
||||
unmountOnHide={true}
|
||||
menuId={sortMenuId}
|
||||
isOpen={isSortMenuOpen}
|
||||
setIsOpen={setIsSortMenuOpen}
|
||||
className="z-[125] min-w-56"
|
||||
trigger={
|
||||
<Ariakit.MenuButton
|
||||
aria-label={localize('com_ui_sort_projects_by')}
|
||||
className={cn(
|
||||
'inline-flex h-10 items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-border-medium bg-surface-primary px-3 text-sm font-medium text-text-primary transition-colors hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring-primary disabled:pointer-events-none disabled:opacity-50 sm:w-44',
|
||||
isSortMenuOpen && 'bg-surface-hover text-text-primary',
|
||||
)}
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<ArrowUpDown
|
||||
className="h-4 w-4 shrink-0 text-text-secondary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="truncate">{selectedSortLabel}</span>
|
||||
</span>
|
||||
</Ariakit.MenuButton>
|
||||
}
|
||||
items={sortMenuItems}
|
||||
/>
|
||||
<Button type="button" variant="submit" size="sm" onClick={() => setIsCreating(true)}>
|
||||
<Plus className="h-4 w-4" aria-hidden="true" />
|
||||
{localize('com_ui_new_project')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
<div className="flex flex-col gap-5">
|
||||
<label className="relative min-w-0 flex-1">
|
||||
<span className="sr-only">{localize('com_ui_search_projects')}</span>
|
||||
<Search
|
||||
|
|
@ -116,36 +148,14 @@ export default function ProjectsView() {
|
|||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder={localize('com_ui_search_projects')}
|
||||
className="border-border-medium bg-transparent pl-9 text-text-primary placeholder:text-text-secondary focus-visible:ring-2 focus-visible:ring-ring-primary"
|
||||
className="border-border-medium bg-surface-primary pl-9 text-text-primary placeholder:text-text-secondary focus-visible:ring-2 focus-visible:ring-ring-primary"
|
||||
/>
|
||||
</label>
|
||||
<DropdownPopup
|
||||
portal={true}
|
||||
focusLoop={true}
|
||||
unmountOnHide={true}
|
||||
menuId={sortMenuId}
|
||||
isOpen={isSortMenuOpen}
|
||||
setIsOpen={setIsSortMenuOpen}
|
||||
className="z-[125] min-w-56"
|
||||
trigger={
|
||||
<Ariakit.MenuButton
|
||||
aria-label={localize('com_ui_sort_projects_by')}
|
||||
className={cn(
|
||||
'inline-flex h-10 items-center justify-between gap-2 whitespace-nowrap rounded-lg border border-border-medium bg-transparent px-3 text-sm font-medium text-text-primary ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 sm:w-56',
|
||||
isSortMenuOpen && 'bg-surface-hover text-text-primary',
|
||||
)}
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<ArrowUpDown
|
||||
className="h-4 w-4 shrink-0 text-text-secondary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="truncate">{selectedSortLabel}</span>
|
||||
</span>
|
||||
</Ariakit.MenuButton>
|
||||
}
|
||||
items={sortMenuItems}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<span className="rounded-full bg-surface-active-alt px-4 py-2 text-sm font-medium text-text-primary">
|
||||
{localize('com_ui_your_projects')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProjectCreateDialog
|
||||
|
|
@ -159,14 +169,14 @@ export default function ProjectsView() {
|
|||
<Spinner className="text-text-primary" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2 md:grid-cols-2">
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
{projects.map((project) => (
|
||||
<button
|
||||
key={project._id}
|
||||
type="button"
|
||||
className={cn(
|
||||
'group/project min-h-32 rounded-xl border border-border-light bg-transparent p-4 text-left transition-colors',
|
||||
'hover:bg-surface-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring-primary',
|
||||
'group/project min-h-32 rounded-lg border border-border-light bg-transparent p-4 text-left transition-colors',
|
||||
'hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring-primary',
|
||||
)}
|
||||
onClick={() => navigate(`/projects/${project._id}`)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ const ConversationsSection = memo(() => {
|
|||
setIsProjectCreateOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleOpenProjects = useCallback(() => {
|
||||
navigate('/projects');
|
||||
toggleNav();
|
||||
}, [navigate, toggleNav]);
|
||||
|
||||
const chatsHeaderControls = useMemo<ChatsHeaderControls>(
|
||||
() => ({
|
||||
organizationMode,
|
||||
|
|
@ -125,6 +130,7 @@ const ConversationsSection = memo(() => {
|
|||
onOrganizationModeChange: handleOrganizationModeChange,
|
||||
onChatSortByChange: handleChatSortByChange,
|
||||
onNewProject: handleNewProject,
|
||||
onOpenProjects: handleOpenProjects,
|
||||
}),
|
||||
[
|
||||
organizationMode,
|
||||
|
|
@ -132,6 +138,7 @@ const ConversationsSection = memo(() => {
|
|||
handleOrganizationModeChange,
|
||||
handleChatSortByChange,
|
||||
handleNewProject,
|
||||
handleOpenProjects,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1528,6 +1528,7 @@
|
|||
"com_ui_sidebar_mode_chronological": "Chronological",
|
||||
"com_ui_sidebar_mode_chronological_list": "Chronological list",
|
||||
"com_ui_sidebar_mode_recent_projects": "Recent projects",
|
||||
"com_ui_sort_by": "Sort by",
|
||||
"com_ui_sort_chats_by": "Sort chats by",
|
||||
"com_ui_sort_created": "Created",
|
||||
"com_ui_sort_projects_by": "Sort projects by",
|
||||
|
|
@ -1773,6 +1774,7 @@
|
|||
"com_ui_yes": "Yes",
|
||||
"com_ui_you": "You",
|
||||
"com_ui_your_api_key": "Your API Key",
|
||||
"com_ui_your_projects": "Your projects",
|
||||
"com_ui_zoom": "Zoom",
|
||||
"com_ui_zoom_in": "Zoom in",
|
||||
"com_ui_zoom_level": "Zoom level",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue