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";
|
"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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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")}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue