fix: thread list state order after dragable (#5141)

* fix: thread list state order after dragable

* fix: new chat order

* chore: revert data provider
This commit is contained in:
Faisal Amir 2025-05-30 00:00:26 +07:00 committed by GitHub
parent 27c2a360f0
commit 426dc2ab87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 70 additions and 46 deletions

View File

@ -83,10 +83,10 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => {
const plainTitleForRename = useMemo(() => { const plainTitleForRename = useMemo(() => {
// Basic HTML stripping for simple span tags. // Basic HTML stripping for simple span tags.
// If thread.title is undefined or null, treat as empty string before replace. // If thread.title is undefined or null, treat as empty string before replace.
return (thread.title || '').replace(/<span[^>]*>|<\/span>/g, ''); return (thread.title || '').replace(/<span[^>]*>|<\/span>/g, '')
}, [thread.title]); }, [thread.title])
const [title, setTitle] = useState(plainTitleForRename || 'New Thread'); const [title, setTitle] = useState(plainTitleForRename || 'New Thread')
return ( return (
<div <div
@ -148,7 +148,7 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => {
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) { if (!open) {
setOpenDropdown(false) setOpenDropdown(false)
setTitle(plainTitleForRename || 'New Thread'); setTitle(plainTitleForRename || 'New Thread')
} }
}} }}
> >
@ -268,9 +268,14 @@ function ThreadList({ threads }: ThreadListProps) {
const sortedThreads = useMemo(() => { const sortedThreads = useMemo(() => {
return threads.sort((a, b) => { return threads.sort((a, b) => {
if (a.order && b.order) return a.order - b.order // If both have order, sort by order (ascending, so lower order comes first)
if (a.order != null && b.order != null) {
// Later on top 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) return (b.updated || 0) - (a.updated || 0)
}) })
}, [threads]) }, [threads])
@ -293,17 +298,25 @@ function ThreadList({ threads }: ThreadListProps) {
const { active, over } = event const { active, over } = event
if (active.id !== over?.id && over) { if (active.id !== over?.id && over) {
// Access Global State // Access Global State
const allThreadsMap = useThreads.getState().threads; const allThreadsMap = useThreads.getState().threads
const allThreadsArray = Object.values(allThreadsMap); const allThreadsArray = Object.values(allThreadsMap)
// Calculate Global Indices // Calculate Global Indices
const oldIndexInGlobal = allThreadsArray.findIndex((t) => t.id === active.id); const oldIndexInGlobal = allThreadsArray.findIndex(
const newIndexInGlobal = allThreadsArray.findIndex((t) => t.id === over.id); (t) => t.id === active.id
)
const newIndexInGlobal = allThreadsArray.findIndex(
(t) => t.id === over.id
)
// Reorder Globally and Update State // Reorder Globally and Update State
if (oldIndexInGlobal !== -1 && newIndexInGlobal !== -1) { if (oldIndexInGlobal !== -1 && newIndexInGlobal !== -1) {
const reorderedGlobalThreads = arrayMove(allThreadsArray, oldIndexInGlobal, newIndexInGlobal); const reorderedGlobalThreads = arrayMove(
setThreads(reorderedGlobalThreads); allThreadsArray,
oldIndexInGlobal,
newIndexInGlobal
)
setThreads(reorderedGlobalThreads)
} }
} }
}} }}

View File

@ -46,10 +46,14 @@ export const useThreads = create<ThreadState>()(
(acc: Record<string, Thread>, thread) => { (acc: Record<string, Thread>, thread) => {
acc[thread.id] = thread acc[thread.id] = thread
return acc return acc
}, {} as Record<string, Thread>) },
{} as Record<string, Thread>
)
set({ set({
threads: threadMap, threads: threadMap,
searchIndex: new Fzf<Thread[]>(Object.values(threadMap), { selector: (item: Thread) => item.title }) searchIndex: new Fzf<Thread[]>(Object.values(threadMap), {
selector: (item: Thread) => item.title,
}),
}) })
}, },
getFilteredThreads: (searchTerm: string) => { getFilteredThreads: (searchTerm: string) => {
@ -63,25 +67,26 @@ export const useThreads = create<ThreadState>()(
let currentIndex = searchIndex let currentIndex = searchIndex
if (!currentIndex) { if (!currentIndex) {
currentIndex = new Fzf<Thread[]>( currentIndex = new Fzf<Thread[]>(Object.values(threads), {
Object.values(threads), selector: (item: Thread) => item.title,
{ selector: (item: Thread) => item.title } })
)
set({ searchIndex: currentIndex }) set({ searchIndex: currentIndex })
} }
// Use the index to search and return matching threads // Use the index to search and return matching threads
const fzfResults = currentIndex.find(searchTerm) const fzfResults = currentIndex.find(searchTerm)
return fzfResults.map((result: { item: Thread; positions: Set<number> }) => { return fzfResults.map(
const thread = result.item; // Fzf stores the original item here (result: { item: Thread; positions: Set<number> }) => {
// Ensure result.positions is an array, default to empty if undefined const thread = result.item // Fzf stores the original item here
const positions = Array.from(result.positions) || []; // Ensure result.positions is an array, default to empty if undefined
const highlightedTitle = highlightFzfMatch(thread.title, positions); const positions = Array.from(result.positions) || []
return { const highlightedTitle = highlightFzfMatch(thread.title, positions)
...thread, return {
title: highlightedTitle, // Override title with highlighted version ...thread,
}; title: highlightedTitle, // Override title with highlighted version
}); }
}
)
}, },
toggleFavorite: (threadId) => { toggleFavorite: (threadId) => {
set((state) => { set((state) => {
@ -107,7 +112,9 @@ export const useThreads = create<ThreadState>()(
deleteThread(threadId) deleteThread(threadId)
return { return {
threads: remainingThreads, threads: remainingThreads,
searchIndex: new Fzf<Thread[]>(Object.values(remainingThreads), { selector: (item: Thread) => item.title }) searchIndex: new Fzf<Thread[]>(Object.values(remainingThreads), {
selector: (item: Thread) => item.title,
}),
} }
}) })
}, },
@ -119,7 +126,7 @@ export const useThreads = create<ThreadState>()(
}) })
return { return {
threads: {}, threads: {},
searchIndex: null // Or new Fzf([], {selector...}) searchIndex: null, // Or new Fzf([], {selector...})
} }
}) })
}, },
@ -157,21 +164,24 @@ export const useThreads = create<ThreadState>()(
id: ulid(), id: ulid(),
title: title ?? 'New Thread', title: title ?? 'New Thread',
model, model,
// order: 1, order: 1, // Will be set properly by setThreads
updated: Date.now() / 1000, updated: Date.now() / 1000,
assistants: assistant ? [assistant] : [], assistants: assistant ? [assistant] : [],
} }
return await createThread(newThread).then((createdThread) => { return await createThread(newThread).then((createdThread) => {
set((state) => { set((state) => {
const newThreads = { // Get all existing threads as an array
...state.threads, const existingThreads = Object.values(state.threads)
[createdThread.id]: createdThread,
}; // 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 { return {
threads: newThreads,
currentThreadId: createdThread.id, currentThreadId: createdThread.id,
searchIndex: new Fzf<Thread[]>(Object.values(newThreads), { selector: (item: Thread) => item.title }), }
};
}) })
return createdThread return createdThread
}) })
@ -221,10 +231,12 @@ export const useThreads = create<ThreadState>()(
title: newTitle, title: newTitle,
} }
updateThread(updatedThread) // External call, order is fine updateThread(updatedThread) // External call, order is fine
const newThreads = { ...state.threads, [threadId]: updatedThread }; const newThreads = { ...state.threads, [threadId]: updatedThread }
return { return {
threads: newThreads, threads: newThreads,
searchIndex: new Fzf<Thread[]>(Object.values(newThreads), { selector: (item: Thread) => item.title }), searchIndex: new Fzf<Thread[]>(Object.values(newThreads), {
selector: (item: Thread) => item.title,
}),
} }
}) })
}, },

View File

@ -1,6 +1,6 @@
import { useMessages } from '@/hooks/useMessages' import { useMessages } from '@/hooks/useMessages'
import { useModelProvider } from '@/hooks/useModelProvider' import { useModelProvider } from '@/hooks/useModelProvider'
import { useThreads } from '@/hooks/useThreads'
import { useAppUpdater } from '@/hooks/useAppUpdater' import { useAppUpdater } from '@/hooks/useAppUpdater'
import { fetchMessages } from '@/services/messages' import { fetchMessages } from '@/services/messages'
import { fetchModels } from '@/services/models' import { fetchModels } from '@/services/models'
@ -15,7 +15,7 @@ import { getAssistants } from '@/services/assistants'
export function DataProvider() { export function DataProvider() {
const { setProviders } = useModelProvider() const { setProviders } = useModelProvider()
const { setThreads } = useThreads()
const { setMessages } = useMessages() const { setMessages } = useMessages()
const { checkForUpdate } = useAppUpdater() const { checkForUpdate } = useAppUpdater()
const { setServers } = useMCPServers() const { setServers } = useMCPServers()
@ -35,7 +35,6 @@ export function DataProvider() {
useEffect(() => { useEffect(() => {
fetchThreads().then((threads) => { fetchThreads().then((threads) => {
setThreads(threads)
threads.forEach((thread) => threads.forEach((thread) =>
fetchMessages(thread.id).then((messages) => fetchMessages(thread.id).then((messages) =>
setMessages(thread.id, messages) setMessages(thread.id, messages)

View File

@ -56,7 +56,7 @@ export const createThread = async (thread: Thread): Promise<Thread> => {
}, },
], ],
metadata: { metadata: {
// order: 1, order: thread.order,
}, },
}) })
.then((e) => { .then((e) => {
@ -67,7 +67,7 @@ export const createThread = async (thread: Thread): Promise<Thread> => {
id: e.assistants?.[0]?.model?.id, id: e.assistants?.[0]?.model?.id,
provider: e.assistants?.[0]?.model?.engine, provider: e.assistants?.[0]?.model?.engine,
}, },
// order: 1, order: e.metadata?.order ?? thread.order,
assistants: e.assistants ?? [defaultAssistant], assistants: e.assistants ?? [defaultAssistant],
} as Thread } as Thread
}) })