From 426dc2ab87cfb21e6020117b30a7e7b315c947ee Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Fri, 30 May 2025 00:00:26 +0700 Subject: [PATCH] fix: thread list state order after dragable (#5141) * fix: thread list state order after dragable * fix: new chat order * chore: revert data provider --- web-app/src/containers/ThreadList.tsx | 39 ++++++++++----- web-app/src/hooks/useThreads.ts | 68 +++++++++++++++----------- web-app/src/providers/DataProvider.tsx | 5 +- web-app/src/services/threads.ts | 4 +- 4 files changed, 70 insertions(+), 46 deletions(-) diff --git a/web-app/src/containers/ThreadList.tsx b/web-app/src/containers/ThreadList.tsx index 00130cd69..14b16a0f8 100644 --- a/web-app/src/containers/ThreadList.tsx +++ b/web-app/src/containers/ThreadList.tsx @@ -83,10 +83,10 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => { const plainTitleForRename = useMemo(() => { // Basic HTML stripping for simple span tags. // If thread.title is undefined or null, treat as empty string before replace. - return (thread.title || '').replace(/]*>|<\/span>/g, ''); - }, [thread.title]); + return (thread.title || '').replace(/]*>|<\/span>/g, '') + }, [thread.title]) - const [title, setTitle] = useState(plainTitleForRename || 'New Thread'); + const [title, setTitle] = useState(plainTitleForRename || 'New Thread') return (
{ onOpenChange={(open) => { if (!open) { setOpenDropdown(false) - setTitle(plainTitleForRename || 'New Thread'); + setTitle(plainTitleForRename || 'New Thread') } }} > @@ -268,9 +268,14 @@ function ThreadList({ threads }: ThreadListProps) { const sortedThreads = useMemo(() => { return threads.sort((a, b) => { - if (a.order && b.order) return a.order - b.order - - // Later on top + // If both have order, sort by order (ascending, so lower order comes first) + if (a.order != null && b.order != null) { + return a.order - b.order + } + // If only one has order, prioritize the one with order (order comes first) + if (a.order != null) return -1 + if (b.order != null) return 1 + // If neither has order, sort by updated time (newer threads first) return (b.updated || 0) - (a.updated || 0) }) }, [threads]) @@ -293,17 +298,25 @@ function ThreadList({ threads }: ThreadListProps) { const { active, over } = event if (active.id !== over?.id && over) { // Access Global State - const allThreadsMap = useThreads.getState().threads; - const allThreadsArray = Object.values(allThreadsMap); + const allThreadsMap = useThreads.getState().threads + const allThreadsArray = Object.values(allThreadsMap) // Calculate Global Indices - const oldIndexInGlobal = allThreadsArray.findIndex((t) => t.id === active.id); - const newIndexInGlobal = allThreadsArray.findIndex((t) => t.id === over.id); + const oldIndexInGlobal = allThreadsArray.findIndex( + (t) => t.id === active.id + ) + const newIndexInGlobal = allThreadsArray.findIndex( + (t) => t.id === over.id + ) // Reorder Globally and Update State if (oldIndexInGlobal !== -1 && newIndexInGlobal !== -1) { - const reorderedGlobalThreads = arrayMove(allThreadsArray, oldIndexInGlobal, newIndexInGlobal); - setThreads(reorderedGlobalThreads); + const reorderedGlobalThreads = arrayMove( + allThreadsArray, + oldIndexInGlobal, + newIndexInGlobal + ) + setThreads(reorderedGlobalThreads) } } }} diff --git a/web-app/src/hooks/useThreads.ts b/web-app/src/hooks/useThreads.ts index b8bb58035..b3cdd7951 100644 --- a/web-app/src/hooks/useThreads.ts +++ b/web-app/src/hooks/useThreads.ts @@ -46,10 +46,14 @@ export const useThreads = create()( (acc: Record, thread) => { acc[thread.id] = thread return acc - }, {} as Record) + }, + {} as Record + ) set({ threads: threadMap, - searchIndex: new Fzf(Object.values(threadMap), { selector: (item: Thread) => item.title }) + searchIndex: new Fzf(Object.values(threadMap), { + selector: (item: Thread) => item.title, + }), }) }, getFilteredThreads: (searchTerm: string) => { @@ -63,25 +67,26 @@ export const useThreads = create()( let currentIndex = searchIndex if (!currentIndex) { - currentIndex = new Fzf( - Object.values(threads), - { selector: (item: Thread) => item.title } - ) + currentIndex = new Fzf(Object.values(threads), { + selector: (item: Thread) => item.title, + }) set({ searchIndex: currentIndex }) } // Use the index to search and return matching threads const fzfResults = currentIndex.find(searchTerm) - return fzfResults.map((result: { item: Thread; positions: Set }) => { - const thread = result.item; // Fzf stores the original item here - // Ensure result.positions is an array, default to empty if undefined - const positions = Array.from(result.positions) || []; - const highlightedTitle = highlightFzfMatch(thread.title, positions); - return { - ...thread, - title: highlightedTitle, // Override title with highlighted version - }; - }); + return fzfResults.map( + (result: { item: Thread; positions: Set }) => { + const thread = result.item // Fzf stores the original item here + // Ensure result.positions is an array, default to empty if undefined + const positions = Array.from(result.positions) || [] + const highlightedTitle = highlightFzfMatch(thread.title, positions) + return { + ...thread, + title: highlightedTitle, // Override title with highlighted version + } + } + ) }, toggleFavorite: (threadId) => { set((state) => { @@ -107,7 +112,9 @@ export const useThreads = create()( deleteThread(threadId) return { threads: remainingThreads, - searchIndex: new Fzf(Object.values(remainingThreads), { selector: (item: Thread) => item.title }) + searchIndex: new Fzf(Object.values(remainingThreads), { + selector: (item: Thread) => item.title, + }), } }) }, @@ -119,7 +126,7 @@ export const useThreads = create()( }) return { threads: {}, - searchIndex: null // Or new Fzf([], {selector...}) + searchIndex: null, // Or new Fzf([], {selector...}) } }) }, @@ -157,21 +164,24 @@ export const useThreads = create()( id: ulid(), title: title ?? 'New Thread', model, - // order: 1, + order: 1, // Will be set properly by setThreads updated: Date.now() / 1000, assistants: assistant ? [assistant] : [], } return await createThread(newThread).then((createdThread) => { set((state) => { - const newThreads = { - ...state.threads, - [createdThread.id]: createdThread, - }; + // Get all existing threads as an array + const existingThreads = Object.values(state.threads) + + // Create new array with the new thread at the beginning + const reorderedThreads = [createdThread, ...existingThreads] + + // Use setThreads to handle proper ordering (this will assign order 1, 2, 3...) + get().setThreads(reorderedThreads) + return { - threads: newThreads, currentThreadId: createdThread.id, - searchIndex: new Fzf(Object.values(newThreads), { selector: (item: Thread) => item.title }), - }; + } }) return createdThread }) @@ -221,10 +231,12 @@ export const useThreads = create()( title: newTitle, } updateThread(updatedThread) // External call, order is fine - const newThreads = { ...state.threads, [threadId]: updatedThread }; + const newThreads = { ...state.threads, [threadId]: updatedThread } return { threads: newThreads, - searchIndex: new Fzf(Object.values(newThreads), { selector: (item: Thread) => item.title }), + searchIndex: new Fzf(Object.values(newThreads), { + selector: (item: Thread) => item.title, + }), } }) }, diff --git a/web-app/src/providers/DataProvider.tsx b/web-app/src/providers/DataProvider.tsx index f31a9ad64..7f0d5e1e3 100644 --- a/web-app/src/providers/DataProvider.tsx +++ b/web-app/src/providers/DataProvider.tsx @@ -1,6 +1,6 @@ import { useMessages } from '@/hooks/useMessages' import { useModelProvider } from '@/hooks/useModelProvider' -import { useThreads } from '@/hooks/useThreads' + import { useAppUpdater } from '@/hooks/useAppUpdater' import { fetchMessages } from '@/services/messages' import { fetchModels } from '@/services/models' @@ -15,7 +15,7 @@ import { getAssistants } from '@/services/assistants' export function DataProvider() { const { setProviders } = useModelProvider() - const { setThreads } = useThreads() + const { setMessages } = useMessages() const { checkForUpdate } = useAppUpdater() const { setServers } = useMCPServers() @@ -35,7 +35,6 @@ export function DataProvider() { useEffect(() => { fetchThreads().then((threads) => { - setThreads(threads) threads.forEach((thread) => fetchMessages(thread.id).then((messages) => setMessages(thread.id, messages) diff --git a/web-app/src/services/threads.ts b/web-app/src/services/threads.ts index 52e6ea283..7d124bfd5 100644 --- a/web-app/src/services/threads.ts +++ b/web-app/src/services/threads.ts @@ -56,7 +56,7 @@ export const createThread = async (thread: Thread): Promise => { }, ], metadata: { - // order: 1, + order: thread.order, }, }) .then((e) => { @@ -67,7 +67,7 @@ export const createThread = async (thread: Thread): Promise => { id: e.assistants?.[0]?.model?.id, provider: e.assistants?.[0]?.model?.engine, }, - // order: 1, + order: e.metadata?.order ?? thread.order, assistants: e.assistants ?? [defaultAssistant], } as Thread })