chat-list.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import DeleteIcon from "../icons/delete.svg";
  2. import BotIcon from "../icons/bot.svg";
  3. import styles from "./home.module.scss";
  4. import {
  5. DragDropContext,
  6. Droppable,
  7. Draggable,
  8. OnDragEndResponder,
  9. } from "@hello-pangea/dnd";
  10. import { useChatStore } from "../store";
  11. import Locale from "../locales";
  12. import { Link, useNavigate } from "react-router-dom";
  13. import { Path } from "../constant";
  14. import { MaskAvatar } from "./mask";
  15. import { Mask } from "../store/mask";
  16. import { useRef, useEffect } from "react";
  17. import { showConfirm } from "./ui-lib";
  18. import { useMobileScreen } from "../utils";
  19. export function ChatItem(props: {
  20. onClick?: () => void;
  21. onDelete?: () => void;
  22. title: string;
  23. count: number;
  24. time: string;
  25. selected: boolean;
  26. id: string;
  27. index: number;
  28. narrow?: boolean;
  29. mask: Mask;
  30. }) {
  31. const draggableRef = useRef<HTMLDivElement | null>(null);
  32. useEffect(() => {
  33. if (props.selected && draggableRef.current) {
  34. draggableRef.current?.scrollIntoView({
  35. block: "center",
  36. });
  37. }
  38. }, [props.selected]);
  39. return (
  40. <Draggable draggableId={`${props.id}`} index={props.index}>
  41. {(provided) => (
  42. <div
  43. className={`${styles["chat-item"]} ${
  44. props.selected && styles["chat-item-selected"]
  45. }`}
  46. onClick={props.onClick}
  47. ref={(ele) => {
  48. draggableRef.current = ele;
  49. provided.innerRef(ele);
  50. }}
  51. {...provided.draggableProps}
  52. {...provided.dragHandleProps}
  53. title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
  54. props.count,
  55. )}`}
  56. >
  57. {props.narrow ? (
  58. <div className={styles["chat-item-narrow"]}>
  59. <div className={styles["chat-item-avatar"] + " no-dark"}>
  60. <MaskAvatar
  61. avatar={props.mask.avatar}
  62. model={props.mask.modelConfig.model}
  63. />
  64. </div>
  65. <div className={styles["chat-item-narrow-count"]}>
  66. {props.count}
  67. </div>
  68. </div>
  69. ) : (
  70. <>
  71. <div className={styles["chat-item-title"]}>{props.title}</div>
  72. <div className={styles["chat-item-info"]}>
  73. <div className={styles["chat-item-count"]}>
  74. {Locale.ChatItem.ChatItemCount(props.count)}
  75. </div>
  76. <div className={styles["chat-item-date"]}>{props.time}</div>
  77. </div>
  78. </>
  79. )}
  80. <div
  81. className={styles["chat-item-delete"]}
  82. onClickCapture={(e) => {
  83. props.onDelete?.();
  84. e.preventDefault();
  85. e.stopPropagation();
  86. }}
  87. >
  88. <DeleteIcon />
  89. </div>
  90. </div>
  91. )}
  92. </Draggable>
  93. );
  94. }
  95. export function ChatList(props: { narrow?: boolean }) {
  96. const [sessions, selectedIndex, selectSession, moveSession] = useChatStore(
  97. (state) => [
  98. state.sessions,
  99. state.currentSessionIndex,
  100. state.selectSession,
  101. state.moveSession,
  102. ],
  103. );
  104. const chatStore = useChatStore();
  105. const navigate = useNavigate();
  106. const isMobileScreen = useMobileScreen();
  107. const onDragEnd: OnDragEndResponder = (result) => {
  108. const { destination, source } = result;
  109. if (!destination) {
  110. return;
  111. }
  112. if (
  113. destination.droppableId === source.droppableId &&
  114. destination.index === source.index
  115. ) {
  116. return;
  117. }
  118. moveSession(source.index, destination.index);
  119. };
  120. return (
  121. <DragDropContext onDragEnd={onDragEnd}>
  122. <Droppable droppableId="chat-list">
  123. {(provided) => (
  124. <div
  125. className={styles["chat-list"]}
  126. ref={provided.innerRef}
  127. {...provided.droppableProps}
  128. >
  129. {sessions.map((item, i) => (
  130. <ChatItem
  131. title={item.topic}
  132. time={new Date(item.lastUpdate).toLocaleString()}
  133. count={item.messages.length}
  134. key={item.id}
  135. id={item.id}
  136. index={i}
  137. selected={i === selectedIndex}
  138. onClick={() => {
  139. navigate(Path.Chat);
  140. selectSession(i);
  141. }}
  142. onDelete={async () => {
  143. if (
  144. (!props.narrow && !isMobileScreen) ||
  145. (await showConfirm(Locale.Home.DeleteChat))
  146. ) {
  147. chatStore.deleteSession(i);
  148. }
  149. }}
  150. narrow={props.narrow}
  151. mask={item.mask}
  152. />
  153. ))}
  154. {provided.placeholder}
  155. </div>
  156. )}
  157. </Droppable>
  158. </DragDropContext>
  159. );
  160. }