chat-list.tsx 4.4 KB

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