chat-list.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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. export function ChatItem(props: {
  18. onClick?: () => void;
  19. onDelete?: () => void;
  20. title: string;
  21. count: number;
  22. time: string;
  23. selected: boolean;
  24. id: number;
  25. index: number;
  26. narrow?: boolean;
  27. mask: Mask;
  28. }) {
  29. const draggableRef = useRef<HTMLDivElement | null>(null);
  30. useEffect(() => {
  31. if (props.selected && draggableRef.current) {
  32. draggableRef.current?.scrollIntoView({
  33. block: "center",
  34. });
  35. }
  36. }, [props.selected]);
  37. return (
  38. <Draggable draggableId={`${props.id}`} index={props.index}>
  39. {(provided) => (
  40. <div
  41. className={`${styles["chat-item"]} ${
  42. props.selected && styles["chat-item-selected"]
  43. }`}
  44. onClick={props.onClick}
  45. ref={(ele) => {
  46. draggableRef.current = ele;
  47. provided.innerRef(ele);
  48. }}
  49. {...provided.draggableProps}
  50. {...provided.dragHandleProps}
  51. title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
  52. props.count,
  53. )}`}
  54. >
  55. {props.narrow ? (
  56. <div className={styles["chat-item-narrow"]}>
  57. <div className={styles["chat-item-avatar"] + " no-dark"}>
  58. <MaskAvatar mask={props.mask} />
  59. </div>
  60. <div className={styles["chat-item-narrow-count"]}>
  61. {props.count}
  62. </div>
  63. </div>
  64. ) : (
  65. <>
  66. <div className={styles["chat-item-title"]}>{props.title}</div>
  67. <div className={styles["chat-item-info"]}>
  68. <div className={styles["chat-item-count"]}>
  69. {Locale.ChatItem.ChatItemCount(props.count)}
  70. </div>
  71. <div className={styles["chat-item-date"]}>
  72. {new Date(props.time).toLocaleString()}
  73. </div>
  74. </div>
  75. </>
  76. )}
  77. <div
  78. className={styles["chat-item-delete"]}
  79. onClickCapture={props.onDelete}
  80. >
  81. <DeleteIcon />
  82. </div>
  83. </div>
  84. )}
  85. </Draggable>
  86. );
  87. }
  88. export function ChatList(props: { narrow?: boolean }) {
  89. const [sessions, selectedIndex, selectSession, moveSession] = useChatStore(
  90. (state) => [
  91. state.sessions,
  92. state.currentSessionIndex,
  93. state.selectSession,
  94. state.moveSession,
  95. ],
  96. );
  97. const chatStore = useChatStore();
  98. const navigate = useNavigate();
  99. const onDragEnd: OnDragEndResponder = (result) => {
  100. const { destination, source } = result;
  101. if (!destination) {
  102. return;
  103. }
  104. if (
  105. destination.droppableId === source.droppableId &&
  106. destination.index === source.index
  107. ) {
  108. return;
  109. }
  110. moveSession(source.index, destination.index);
  111. };
  112. return (
  113. <DragDropContext onDragEnd={onDragEnd}>
  114. <Droppable droppableId="chat-list">
  115. {(provided) => (
  116. <div
  117. className={styles["chat-list"]}
  118. ref={provided.innerRef}
  119. {...provided.droppableProps}
  120. >
  121. {sessions.map((item, i) => (
  122. <ChatItem
  123. title={item.topic}
  124. time={new Date(item.lastUpdate).toLocaleString()}
  125. count={item.messages.length}
  126. key={item.id}
  127. id={item.id}
  128. index={i}
  129. selected={i === selectedIndex}
  130. onClick={() => {
  131. navigate(Path.Chat);
  132. selectSession(i);
  133. }}
  134. onDelete={() => {
  135. if (!props.narrow || confirm(Locale.Home.DeleteChat)) {
  136. chatStore.deleteSession(i);
  137. }
  138. }}
  139. narrow={props.narrow}
  140. mask={item.mask}
  141. />
  142. ))}
  143. {provided.placeholder}
  144. </div>
  145. )}
  146. </Droppable>
  147. </DragDropContext>
  148. );
  149. }