Refactor KnowledgeFilterList component layout and improve loading states. Update Navigation component styles for consistency. Adjust LayoutWrapper sidebar positioning. Fix UserNav conditional rendering logic. Clean up ChatProvider state update functions for clarity.

This commit is contained in:
Deon Sanchez 2025-10-01 14:26:08 -06:00
parent 84e3703832
commit b27ec87fe9
5 changed files with 109 additions and 104 deletions

View file

@ -1,8 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Plus } from "lucide-react";
import { Loader2, Plus } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { import {
useGetFiltersSearchQuery, useGetFiltersSearchQuery,
@ -65,35 +64,33 @@ export function KnowledgeFilterList({
}; };
return ( return (
<> <div className="flex-1 min-h-0 flex flex-col">
<div className="flex flex-col gap-1 px-3 !mb-12 mt-0 h-full overflow-y-auto"> <div className="px-3 flex-1 min-h-0 flex flex-col">
<div className="flex items-center w-full justify-between pl-3"> <div className="flex-shrink-0">
<div className="text-sm font-medium text-muted-foreground"> <div className="flex items-center justify-between mb-3 ml-3 mr-2">
<h3 className="text-xs font-medium text-muted-foreground">
Knowledge Filters Knowledge Filters
</div> </h3>
<Button <button
variant="ghost" type="button"
size="sm" className="p-1 hover:bg-accent rounded"
onClick={handleCreateNew} onClick={handleCreateNew}
title="Create New Filter" title="Create New Filter"
className="h-8 px-3 text-muted-foreground"
> >
<Plus className="h-3 w-3" /> <Plus className="h-4 w-4 text-muted-foreground" />
</Button> </button>
</div> </div>
<div className="overflow-y-auto scrollbar-hide space-y-1">
{loading ? ( {loading ? (
<div className="flex items-center justify-center p-4"> <div className="text-[13px] text-muted-foreground p-2 ml-1">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="ml-2 text-sm text-muted-foreground">
Loading... Loading...
</span>
</div> </div>
) : filters.length === 0 ? ( ) : filters.length === 0 ? (
<div className="py-2 px-4 text-sm text-muted-foreground"> <div className="text-[13px] text-muted-foreground p-2 ml-1">
{searchQuery ? "No filters found" : "No saved filters"} {searchQuery ? "No filters found" : "No saved filters"}
</div> </div>
) : ( ) : (
filters.map((filter) => ( filters.map(filter => (
<div <div
key={filter.id} key={filter.id}
onClick={() => handleFilterSelect(filter)} onClick={() => handleFilterSelect(filter)}
@ -106,7 +103,9 @@ export function KnowledgeFilterList({
<div className="flex flex-col gap-1 flex-1 min-w-0"> <div className="flex flex-col gap-1 flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{(() => { {(() => {
const parsed = parseQueryData(filter.query_data) as ParsedQueryData; const parsed = parseQueryData(
filter.query_data
) as ParsedQueryData;
const Icon = iconKeyToComponent(parsed.icon); const Icon = iconKeyToComponent(parsed.icon);
return ( return (
<div <div
@ -132,11 +131,14 @@ export function KnowledgeFilterList({
)} )}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
{new Date(filter.created_at).toLocaleDateString(undefined, { {new Date(filter.created_at).toLocaleDateString(
undefined,
{
month: "short", month: "short",
day: "numeric", day: "numeric",
year: "numeric", year: "numeric",
})} }
)}
</div> </div>
<span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm group-hover:bg-background group-[.active]:bg-background transition-colors"> <span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm group-hover:bg-background group-[.active]:bg-background transition-colors">
{(() => { {(() => {
@ -144,7 +146,9 @@ export function KnowledgeFilterList({
.filters.data_sources; .filters.data_sources;
if (dataSources[0] === "*") return "All sources"; if (dataSources[0] === "*") return "All sources";
const count = dataSources.length; const count = dataSources.length;
return `${count} ${count === 1 ? "source" : "sources"}`; return `${count} ${
count === 1 ? "source" : "sources"
}`;
})()} })()}
</span> </span>
</div> </div>
@ -153,7 +157,9 @@ export function KnowledgeFilterList({
)) ))
)} )}
</div> </div>
</div>
{/* Create flow moved to panel create mode */} {/* Create flow moved to panel create mode */}
</> </div>
</div>
); );
} }

View file

@ -5,7 +5,6 @@ import {
FileText, FileText,
Library, Library,
MessageSquare, MessageSquare,
MoreHorizontal,
Plus, Plus,
Settings2, Settings2,
Trash2, Trash2,
@ -397,7 +396,7 @@ export function Navigation({
{/* Conversations List - grows naturally, doesn't fill all space */} {/* Conversations List - grows naturally, doesn't fill all space */}
<div className="flex-shrink-0 overflow-y-auto scrollbar-hide space-y-1 max-h-full"> <div className="flex-shrink-0 overflow-y-auto scrollbar-hide space-y-1 max-h-full">
{loadingNewConversation || isConversationsLoading ? ( {loadingNewConversation || isConversationsLoading ? (
<div className="text-sm text-muted-foreground p-2"> <div className="text-[13px] text-muted-foreground p-2">
Loading... Loading...
</div> </div>
) : ( ) : (
@ -406,7 +405,7 @@ export function Navigation({
{placeholderConversation && ( {placeholderConversation && (
<button <button
type="button" type="button"
className="w-full p-2 rounded-lg bg-accent/50 border border-dashed border-accent cursor-pointer group text-left" className="w-full px-3 rounded-lg bg-accent border border-dashed border-accent cursor-pointer group text-left h-[44px]"
onClick={() => { onClick={() => {
// Don't load placeholder as a real conversation, just focus the input // Don't load placeholder as a real conversation, just focus the input
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
@ -414,7 +413,7 @@ export function Navigation({
} }
}} }}
> >
<div className="text-sm font-medium text-foreground mb-1 truncate"> <div className="text-[13px] font-medium text-foreground truncate">
{placeholderConversation.title} {placeholderConversation.title}
</div> </div>
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
@ -425,7 +424,7 @@ export function Navigation({
{/* Show regular conversations */} {/* Show regular conversations */}
{conversations.length === 0 && !placeholderConversation ? ( {conversations.length === 0 && !placeholderConversation ? (
<div className="text-[13px] text-muted-foreground p-2"> <div className="text-[13px] text-muted-foreground p-2 ml-1">
No conversations yet No conversations yet
</div> </div>
) : ( ) : (
@ -531,16 +530,16 @@ export function Navigation({
className="hidden" className="hidden"
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt" accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
/> />
<div className="overflow-y-auto scrollbar-hide space-y-1 max-h-40 ml-1"> <div className="overflow-y-auto scrollbar-hide space-y-1">
{conversationDocs.length === 0 ? ( {conversationDocs.length === 0 ? (
<div className="text-[13px] text-muted-foreground p-2"> <div className="text-[13px] text-muted-foreground p-2 ml-1">
No documents yet No documents yet
</div> </div>
) : ( ) : (
conversationDocs.map(doc => ( conversationDocs.map(doc => (
<div <div
key={`${doc.filename}-${doc.uploadTime.getTime()}`} key={`${doc.filename}-${doc.uploadTime.getTime()}`}
className="p-2 rounded-lg hover:bg-accent cursor-pointer group flex items-center" className="w-full px-3 pr-2 h-11 rounded-lg hover:bg-accent cursor-pointer group flex items-center"
> >
<FileText className="h-4 w-4 mr-2 text-muted-foreground flex-shrink-0" /> <FileText className="h-4 w-4 mr-2 text-muted-foreground flex-shrink-0" />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">

View file

@ -119,7 +119,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
</div> </div>
</div> </div>
</header> </header>
<div className="side-bar-arrangement bg-background fixed left-0 top-[53px] bottom-0 md:flex hidden"> <div className="side-bar-arrangement bg-background fixed left-0 top-[40px] bottom-0 md:flex hidden pt-1">
<Navigation <Navigation
conversations={conversations} conversations={conversations}
isConversationsLoading={isConversationsLoading} isConversationsLoading={isConversationsLoading}

View file

@ -23,7 +23,7 @@ export function UserNav() {
} }
// In no-auth mode, show a simple theme switcher instead of auth UI // In no-auth mode, show a simple theme switcher instead of auth UI
if (!isNoAuthMode) { if (isNoAuthMode) {
return ( return (
<button <button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")} onClick={() => setTheme(theme === "dark" ? "light" : "dark")}

View file

@ -96,7 +96,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
const refreshConversations = useCallback((force = false) => { const refreshConversations = useCallback((force = false) => {
if (force) { if (force) {
// Immediate refresh for important updates like new conversations // Immediate refresh for important updates like new conversations
setRefreshTrigger((prev) => prev + 1); setRefreshTrigger(prev => prev + 1);
return; return;
} }
@ -107,7 +107,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
// Set a new timeout to debounce multiple rapid refresh calls // Set a new timeout to debounce multiple rapid refresh calls
refreshTimeoutRef.current = setTimeout(() => { refreshTimeoutRef.current = setTimeout(() => {
setRefreshTrigger((prev) => prev + 1); setRefreshTrigger(prev => prev + 1);
}, 250); // 250ms debounce }, 250); // 250ms debounce
}, []); }, []);
@ -123,7 +123,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
// Silent refresh - updates data without loading states // Silent refresh - updates data without loading states
const refreshConversationsSilent = useCallback(async () => { const refreshConversationsSilent = useCallback(async () => {
// Trigger silent refresh that updates conversation data without showing loading states // Trigger silent refresh that updates conversation data without showing loading states
setRefreshTriggerSilent((prev) => prev + 1); setRefreshTriggerSilent(prev => prev + 1);
}, []); }, []);
const loadConversation = useCallback((conversation: ConversationData) => { const loadConversation = useCallback((conversation: ConversationData) => {
@ -164,7 +164,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
}, [endpoint, refreshConversations]); }, [endpoint, refreshConversations]);
const addConversationDoc = useCallback((filename: string) => { const addConversationDoc = useCallback((filename: string) => {
setConversationDocs((prev) => [ setConversationDocs(prev => [
...prev, ...prev,
{ filename, uploadTime: new Date() }, { filename, uploadTime: new Date() },
]); ]);
@ -180,7 +180,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
setCurrentConversationId(null); // Clear current conversation to indicate new conversation setCurrentConversationId(null); // Clear current conversation to indicate new conversation
setConversationData(null); // Clear conversation data to prevent reloading setConversationData(null); // Clear conversation data to prevent reloading
// Set the response ID that we're forking from as the previous response ID // Set the response ID that we're forking from as the previous response ID
setPreviousResponseIds((prev) => ({ setPreviousResponseIds(prev => ({
...prev, ...prev,
[endpoint]: responseId, [endpoint]: responseId,
})); }));