useConversations.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. "use client";
  2. import { useCallback, useEffect, useMemo, useRef, useState } from "react";
  3. import {
  4. fetchConversations,
  5. searchConversations,
  6. } from "../../agent/services/conversationApi";
  7. import { ConversationSummary } from "../../agent/types";
  8. const sortByUpdatedAtDesc = (list: ConversationSummary[]) =>
  9. [...list].sort(
  10. (a, b) =>
  11. new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
  12. );
  13. export function useConversations() {
  14. const [conversations, setConversations] = useState<ConversationSummary[]>([]);
  15. const [filteredConversations, setFilteredConversations] = useState<
  16. ConversationSummary[]
  17. >([]);
  18. const [selectedConversationId, setSelectedConversationId] = useState<
  19. number | null
  20. >(null);
  21. const [searchQuery, setSearchQuery] = useState("");
  22. const [loading, setLoading] = useState(true);
  23. const [isInitialLoad, setIsInitialLoad] = useState(true);
  24. const searchRef = useRef("");
  25. const loadConversations = useCallback(async () => {
  26. setLoading(true);
  27. try {
  28. const data = await fetchConversations();
  29. setConversations(data);
  30. if (!searchRef.current.trim()) {
  31. setFilteredConversations(data);
  32. }
  33. setSelectedConversationId((prev) => {
  34. if (prev) {
  35. return prev;
  36. }
  37. return data.length > 0 ? data[0].id : null;
  38. });
  39. } catch (error) {
  40. console.error(error);
  41. } finally {
  42. setLoading(false);
  43. setIsInitialLoad(false);
  44. }
  45. }, []);
  46. useEffect(() => {
  47. loadConversations();
  48. }, [loadConversations]);
  49. useEffect(() => {
  50. if (isInitialLoad) {
  51. return;
  52. }
  53. const handler = setTimeout(async () => {
  54. const query = searchQuery.trim();
  55. searchRef.current = query;
  56. if (!query) {
  57. setFilteredConversations(sortByUpdatedAtDesc(conversations));
  58. return;
  59. }
  60. try {
  61. setLoading(true);
  62. const data = await searchConversations(query);
  63. setFilteredConversations(sortByUpdatedAtDesc(data));
  64. } catch (error) {
  65. console.error(error);
  66. setFilteredConversations([]);
  67. } finally {
  68. setLoading(false);
  69. }
  70. }, 300);
  71. return () => clearTimeout(handler);
  72. }, [searchQuery, conversations, isInitialLoad]);
  73. const selectConversation = useCallback((conversationId: number) => {
  74. setSelectedConversationId((prev) =>
  75. prev === conversationId ? prev : conversationId
  76. );
  77. }, []);
  78. const updateConversation = useCallback(
  79. (
  80. conversationId: number,
  81. updater: (conversation: ConversationSummary) => ConversationSummary,
  82. options?: { skipResort?: boolean }
  83. ) => {
  84. const applyUpdate = (list: ConversationSummary[]) => {
  85. let changed = false;
  86. const next = list.map((conv) => {
  87. if (conv.id === conversationId) {
  88. changed = true;
  89. return updater(conv);
  90. }
  91. return conv;
  92. });
  93. if (!changed) {
  94. return list;
  95. }
  96. if (options?.skipResort) {
  97. return next;
  98. }
  99. return sortByUpdatedAtDesc(next);
  100. };
  101. setConversations((prev) => applyUpdate(prev));
  102. setFilteredConversations((prev) => {
  103. if (searchRef.current && !prev.some((item) => item.id === conversationId)) {
  104. return prev;
  105. }
  106. return applyUpdate(prev);
  107. });
  108. },
  109. []
  110. );
  111. const setAllConversations = useCallback((data: ConversationSummary[]) => {
  112. setConversations(data);
  113. if (!searchRef.current.trim()) {
  114. setFilteredConversations(data);
  115. }
  116. }, []);
  117. const contextValue = useMemo(
  118. () => ({
  119. conversations,
  120. filteredConversations,
  121. selectedConversationId,
  122. searchQuery,
  123. loading,
  124. isInitialLoad,
  125. setSearchQuery,
  126. selectConversation,
  127. refresh: loadConversations,
  128. updateConversation,
  129. setAllConversations,
  130. }),
  131. [
  132. conversations,
  133. filteredConversations,
  134. selectedConversationId,
  135. searchQuery,
  136. loading,
  137. isInitialLoad,
  138. selectConversation,
  139. loadConversations,
  140. updateConversation,
  141. setAllConversations,
  142. setSearchQuery,
  143. ]
  144. );
  145. return contextValue;
  146. }