MCPcopy Create free account
hub / github.com/TanStack/ai / useThreadIndex

Function useThreadIndex

examples/ts-react-chat/src/routes/threads.tsx:167–227  ·  view source on GitHub ↗

* Owns the thread catalog in localStorage. This is the layer the persistence * adapter doesn't provide: the list of conversations, their titles, and recency.

()

Source from the content-addressed store, hash-verified

165 * adapter doesn't provide: the list of conversations, their titles, and recency.
166 */
167function useThreadIndex() {
168 const [threads, setThreads] = useState<Array<ThreadMeta>>([])
169 // SSR-safety: the server has no localStorage, so we render an empty list and
170 // load the real catalog on the client after mount. `loaded` gates bootstrap
171 // logic so we don't auto-create a spurious thread before the real one loads.
172 const [loaded, setLoaded] = useState(false)
173 // Mirror of the catalog read synchronously by mutators. State closures are
174 // stale between two calls in the same event handler (e.g. set-title then
175 // bump-recency), which would make the second call clobber the first; the ref
176 // is always current, so sequential mutations compose correctly.
177 const threadsRef = useRef<Array<ThreadMeta>>([])
178
179 useEffect(() => {
180 const initial = readIndex().sort(byRecency)
181 threadsRef.current = initial
182 setThreads(initial)
183 setLoaded(true)
184 }, [])
185
186 const commit = (next: Array<ThreadMeta>) => {
187 const sorted = [...next].sort(byRecency)
188 threadsRef.current = sorted
189 setThreads(sorted)
190 writeIndex(sorted)
191 }
192
193 const createThread = (): ThreadMeta => {
194 const thread: ThreadMeta = {
195 id: crypto.randomUUID(),
196 title: NEW_CHAT_TITLE,
197 updatedAt: Date.now(),
198 }
199 commit([thread, ...threadsRef.current])
200 return thread
201 }
202
203 const deleteThread = (id: string) => {
204 threadPersistence.removeItem(id)
205 commit(threadsRef.current.filter((t) => t.id !== id))
206 }
207
208 /** Bump recency, and set the title the first time a thread gets one. */
209 const touchThread = (id: string, title?: string) => {
210 commit(
211 threadsRef.current.map((t) =>
212 t.id === id
213 ? {
214 ...t,
215 updatedAt: Date.now(),
216 title:
217 title && t.title === NEW_CHAT_TITLE
218 ? truncateTitle(title)
219 : t.title,
220 }
221 : t,
222 ),
223 )
224 }

Callers 1

ThreadsRouteFunction · 0.85

Calls 1

readIndexFunction · 0.85

Tested by

no test coverage detected