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:
parent
84e3703832
commit
b27ec87fe9
5 changed files with 109 additions and 104 deletions
|
|
@ -1,8 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Loader2, Plus } from "lucide-react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
useGetFiltersSearchQuery,
|
||||
|
|
@ -65,95 +64,102 @@ export function KnowledgeFilterList({
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1 px-3 !mb-12 mt-0 h-full overflow-y-auto">
|
||||
<div className="flex items-center w-full justify-between pl-3">
|
||||
<div className="text-sm font-medium text-muted-foreground">
|
||||
Knowledge Filters
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleCreateNew}
|
||||
title="Create New Filter"
|
||||
className="h-8 px-3 text-muted-foreground"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center p-4">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<span className="ml-2 text-sm text-muted-foreground">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
) : filters.length === 0 ? (
|
||||
<div className="py-2 px-4 text-sm text-muted-foreground">
|
||||
{searchQuery ? "No filters found" : "No saved filters"}
|
||||
</div>
|
||||
) : (
|
||||
filters.map((filter) => (
|
||||
<div
|
||||
key={filter.id}
|
||||
onClick={() => handleFilterSelect(filter)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors",
|
||||
selectedFilter?.id === filter.id &&
|
||||
"active bg-accent text-accent-foreground"
|
||||
)}
|
||||
<div className="flex-1 min-h-0 flex flex-col">
|
||||
<div className="px-3 flex-1 min-h-0 flex flex-col">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="flex items-center justify-between mb-3 ml-3 mr-2">
|
||||
<h3 className="text-xs font-medium text-muted-foreground">
|
||||
Knowledge Filters
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 hover:bg-accent rounded"
|
||||
onClick={handleCreateNew}
|
||||
title="Create New Filter"
|
||||
>
|
||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const parsed = parseQueryData(filter.query_data) as ParsedQueryData;
|
||||
const Icon = iconKeyToComponent(parsed.icon);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center w-5 h-5 rounded transition-colors",
|
||||
filterAccentClasses[parsed.color],
|
||||
parsed.color === "zinc" &&
|
||||
"group-hover:bg-background group-[.active]:bg-background"
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-3 w-3" />}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div className="text-sm font-medium truncate group-hover:text-accent-foreground">
|
||||
{filter.name}
|
||||
</div>
|
||||
</div>
|
||||
{filter.description && (
|
||||
<div className="text-xs text-muted-foreground line-clamp-2">
|
||||
{filter.description}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{new Date(filter.created_at).toLocaleDateString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</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">
|
||||
{(() => {
|
||||
const dataSources = parseQueryData(filter.query_data)
|
||||
.filters.data_sources;
|
||||
if (dataSources[0] === "*") return "All sources";
|
||||
const count = dataSources.length;
|
||||
return `${count} ${count === 1 ? "source" : "sources"}`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
<Plus className="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto scrollbar-hide space-y-1">
|
||||
{loading ? (
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-1">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
) : filters.length === 0 ? (
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-1">
|
||||
{searchQuery ? "No filters found" : "No saved filters"}
|
||||
</div>
|
||||
) : (
|
||||
filters.map(filter => (
|
||||
<div
|
||||
key={filter.id}
|
||||
onClick={() => handleFilterSelect(filter)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors",
|
||||
selectedFilter?.id === filter.id &&
|
||||
"active bg-accent text-accent-foreground"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const parsed = parseQueryData(
|
||||
filter.query_data
|
||||
) as ParsedQueryData;
|
||||
const Icon = iconKeyToComponent(parsed.icon);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center w-5 h-5 rounded transition-colors",
|
||||
filterAccentClasses[parsed.color],
|
||||
parsed.color === "zinc" &&
|
||||
"group-hover:bg-background group-[.active]:bg-background"
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-3 w-3" />}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div className="text-sm font-medium truncate group-hover:text-accent-foreground">
|
||||
{filter.name}
|
||||
</div>
|
||||
</div>
|
||||
{filter.description && (
|
||||
<div className="text-xs text-muted-foreground line-clamp-2">
|
||||
{filter.description}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{new Date(filter.created_at).toLocaleDateString(
|
||||
undefined,
|
||||
{
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
}
|
||||
)}
|
||||
</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">
|
||||
{(() => {
|
||||
const dataSources = parseQueryData(filter.query_data)
|
||||
.filters.data_sources;
|
||||
if (dataSources[0] === "*") return "All sources";
|
||||
const count = dataSources.length;
|
||||
return `${count} ${
|
||||
count === 1 ? "source" : "sources"
|
||||
}`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Create flow moved to panel create mode */}
|
||||
</div>
|
||||
{/* Create flow moved to panel create mode */}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
FileText,
|
||||
Library,
|
||||
MessageSquare,
|
||||
MoreHorizontal,
|
||||
Plus,
|
||||
Settings2,
|
||||
Trash2,
|
||||
|
|
@ -397,7 +396,7 @@ export function Navigation({
|
|||
{/* 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">
|
||||
{loadingNewConversation || isConversationsLoading ? (
|
||||
<div className="text-sm text-muted-foreground p-2">
|
||||
<div className="text-[13px] text-muted-foreground p-2">
|
||||
Loading...
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -406,7 +405,7 @@ export function Navigation({
|
|||
{placeholderConversation && (
|
||||
<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={() => {
|
||||
// Don't load placeholder as a real conversation, just focus the input
|
||||
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}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
|
|
@ -425,7 +424,7 @@ export function Navigation({
|
|||
|
||||
{/* Show regular conversations */}
|
||||
{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
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -531,16 +530,16 @@ export function Navigation({
|
|||
className="hidden"
|
||||
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 ? (
|
||||
<div className="text-[13px] text-muted-foreground p-2">
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-1">
|
||||
No documents yet
|
||||
</div>
|
||||
) : (
|
||||
conversationDocs.map(doc => (
|
||||
<div
|
||||
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" />
|
||||
<div className="flex-1 min-w-0">
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
|||
</div>
|
||||
</div>
|
||||
</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
|
||||
conversations={conversations}
|
||||
isConversationsLoading={isConversationsLoading}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function UserNav() {
|
|||
}
|
||||
|
||||
// In no-auth mode, show a simple theme switcher instead of auth UI
|
||||
if (!isNoAuthMode) {
|
||||
if (isNoAuthMode) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
|||
const refreshConversations = useCallback((force = false) => {
|
||||
if (force) {
|
||||
// Immediate refresh for important updates like new conversations
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
setRefreshTrigger(prev => prev + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
|||
|
||||
// Set a new timeout to debounce multiple rapid refresh calls
|
||||
refreshTimeoutRef.current = setTimeout(() => {
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
setRefreshTrigger(prev => prev + 1);
|
||||
}, 250); // 250ms debounce
|
||||
}, []);
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
|||
// Silent refresh - updates data without loading states
|
||||
const refreshConversationsSilent = useCallback(async () => {
|
||||
// Trigger silent refresh that updates conversation data without showing loading states
|
||||
setRefreshTriggerSilent((prev) => prev + 1);
|
||||
setRefreshTriggerSilent(prev => prev + 1);
|
||||
}, []);
|
||||
|
||||
const loadConversation = useCallback((conversation: ConversationData) => {
|
||||
|
|
@ -164,7 +164,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
|||
}, [endpoint, refreshConversations]);
|
||||
|
||||
const addConversationDoc = useCallback((filename: string) => {
|
||||
setConversationDocs((prev) => [
|
||||
setConversationDocs(prev => [
|
||||
...prev,
|
||||
{ filename, uploadTime: new Date() },
|
||||
]);
|
||||
|
|
@ -180,7 +180,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
|||
setCurrentConversationId(null); // Clear current conversation to indicate new conversation
|
||||
setConversationData(null); // Clear conversation data to prevent reloading
|
||||
// Set the response ID that we're forking from as the previous response ID
|
||||
setPreviousResponseIds((prev) => ({
|
||||
setPreviousResponseIds(prev => ({
|
||||
...prev,
|
||||
[endpoint]: responseId,
|
||||
}));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue