diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx
index 8ad2b7dc..8d02805f 100644
--- a/app/components/chat-list.tsx
+++ b/app/components/chat-list.tsx
@@ -1,14 +1,13 @@
-import { useState, useRef, useEffect, useLayoutEffect } from "react";
import DeleteIcon from "../icons/delete.svg";
import styles from "./home.module.scss";
-
import {
- Message,
- SubmitKey,
- useChatStore,
- ChatSession,
- BOT_HELLO,
-} from "../store";
+ DragDropContext,
+ Droppable,
+ Draggable,
+ OnDragEndResponder,
+} from "@hello-pangea/dnd";
+
+import { useChatStore } from "../store";
import Locale from "../locales";
import { isMobileScreen } from "../utils";
@@ -20,54 +19,92 @@ export function ChatItem(props: {
count: number;
time: string;
selected: boolean;
+ id: number;
+ index: number;
}) {
return (
-
-
{props.title}
-
-
- {Locale.ChatItem.ChatItemCount(props.count)}
+
+ {(provided) => (
+
+
{props.title}
+
+
+ {Locale.ChatItem.ChatItemCount(props.count)}
+
+
{props.time}
+
+
+
+
- {props.time}
-
-
-
-
-
+ )}
+
);
}
export function ChatList() {
- const [sessions, selectedIndex, selectSession, removeSession] = useChatStore(
- (state) => [
+ const [sessions, selectedIndex, selectSession, removeSession, moveSession] =
+ useChatStore((state) => [
state.sessions,
state.currentSessionIndex,
state.selectSession,
state.removeSession,
- ],
- );
+ state.moveSession,
+ ]);
+
+ const onDragEnd: OnDragEndResponder = (result) => {
+ const { destination, source } = result;
+ if (!destination) {
+ return;
+ }
+
+ if (
+ destination.droppableId === source.droppableId &&
+ destination.index === source.index
+ ) {
+ return;
+ }
+
+ moveSession(source.index, destination.index);
+ };
return (
-
- {sessions.map((item, i) => (
- selectSession(i)}
- onDelete={() =>
- (!isMobileScreen() || confirm(Locale.Home.DeleteChat)) &&
- removeSession(i)
- }
- />
- ))}
-
+
+
+ {(provided) => (
+
+ {sessions.map((item, i) => (
+ selectSession(i)}
+ onDelete={() =>
+ (!isMobileScreen() || confirm(Locale.Home.DeleteChat)) &&
+ removeSession(i)
+ }
+ />
+ ))}
+ {provided.placeholder}
+
+ )}
+
+
);
}
diff --git a/app/components/home.module.scss b/app/components/home.module.scss
index 64ac2363..da954dc1 100644
--- a/app/components/home.module.scss
+++ b/app/components/home.module.scss
@@ -125,7 +125,7 @@
border-radius: 10px;
margin-bottom: 10px;
box-shadow: var(--card-shadow);
- transition: all 0.3s ease;
+ transition: background-color 0.3s ease;
cursor: pointer;
user-select: none;
border: 2px solid transparent;
diff --git a/app/components/home.tsx b/app/components/home.tsx
index 66f86348..9e57cb87 100644
--- a/app/components/home.tsx
+++ b/app/components/home.tsx
@@ -19,7 +19,6 @@ import CloseIcon from "../icons/close.svg";
import { useChatStore } from "../store";
import { isMobileScreen } from "../utils";
import Locale from "../locales";
-import { ChatList } from "./chat-list";
import { Chat } from "./chat";
import dynamic from "next/dynamic";
@@ -39,6 +38,10 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, {
loading: () =>
,
});
+const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
+ loading: () =>
,
+});
+
function useSwitchTheme() {
const config = useChatStore((state) => state.config);
diff --git a/app/store/app.ts b/app/store/app.ts
index 64faa31d..c63fa9d4 100644
--- a/app/store/app.ts
+++ b/app/store/app.ts
@@ -201,6 +201,7 @@ interface ChatStore {
currentSessionIndex: number;
clearSessions: () => void;
removeSession: (index: number) => void;
+ moveSession: (from: number, to: number) => void;
selectSession: (index: number) => void;
newSession: () => void;
currentSession: () => ChatSession;
@@ -291,6 +292,31 @@ export const useChatStore = create
()(
});
},
+ moveSession(from: number, to: number) {
+ set((state) => {
+ const { sessions, currentSessionIndex: oldIndex } = state;
+
+ // move the session
+ const newSessions = [...sessions];
+ const session = newSessions[from];
+ newSessions.splice(from, 1);
+ newSessions.splice(to, 0, session);
+
+ // modify current session id
+ let newIndex = oldIndex === from ? to : oldIndex;
+ if (oldIndex > from && oldIndex <= to) {
+ newIndex -= 1;
+ } else if (oldIndex < from && oldIndex >= to) {
+ newIndex += 1;
+ }
+
+ return {
+ currentSessionIndex: newIndex,
+ sessions: newSessions,
+ };
+ });
+ },
+
newSession() {
set((state) => ({
currentSessionIndex: 0,
diff --git a/package.json b/package.json
index 69dec164..9fcb74e7 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"prepare": "husky install"
},
"dependencies": {
+ "@hello-pangea/dnd": "^16.2.0",
"@svgr/webpack": "^6.5.1",
"@vercel/analytics": "^0.1.11",
"emoji-picker-react": "^4.4.7",
diff --git a/yarn.lock b/yarn.lock
index 9a937276..88aa5982 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -954,7 +954,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
-"@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4":
+"@babel/runtime@^7.12.1", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@@ -1027,6 +1027,19 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d"
integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==
+"@hello-pangea/dnd@^16.2.0":
+ version "16.2.0"
+ resolved "https://registry.npmmirror.com/@hello-pangea/dnd/-/dnd-16.2.0.tgz#58cbadeb56f8c7a381da696bb7aa3bfbb87876ec"
+ integrity sha512-inACvMcvvLr34CG0P6+G/3bprVKhwswxjcsFUSJ+fpOGjhvDj9caiA9X3clby0lgJ6/ILIJjyedHZYECB7GAgA==
+ dependencies:
+ "@babel/runtime" "^7.19.4"
+ css-box-model "^1.2.1"
+ memoize-one "^6.0.0"
+ raf-schd "^4.0.3"
+ react-redux "^8.0.4"
+ redux "^4.2.0"
+ use-memo-one "^1.1.3"
+
"@humanwhocodes/config-array@^0.11.8":
version "0.11.8"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
@@ -1333,6 +1346,14 @@
dependencies:
"@types/unist" "*"
+"@types/hoist-non-react-statics@^3.3.1":
+ version "3.3.1"
+ resolved "https://registry.npmmirror.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -1408,6 +1429,11 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
+"@types/use-sync-external-store@^0.0.3":
+ version "0.0.3"
+ resolved "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
+ integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
+
"@typescript-eslint/parser@^5.42.0":
version "5.57.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.57.0.tgz#f675bf2cd1a838949fd0de5683834417b757e4fa"
@@ -1912,6 +1938,13 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
+css-box-model@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmmirror.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
css-select@^4.1.3:
version "4.3.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
@@ -2885,6 +2918,13 @@ highlight.js@~11.7.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
human-signals@^4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2"
@@ -3527,6 +3567,11 @@ mdn-data@2.0.14:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
+memoize-one@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
+ integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -4211,6 +4256,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+raf-schd@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmmirror.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+ integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
@@ -4219,7 +4269,7 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
-react-is@^16.13.1:
+react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -4250,6 +4300,18 @@ react-markdown@^8.0.5:
unist-util-visit "^4.0.0"
vfile "^5.0.0"
+react-redux@^8.0.4:
+ version "8.0.5"
+ resolved "https://registry.npmmirror.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
+ integrity sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==
+ dependencies:
+ "@babel/runtime" "^7.12.1"
+ "@types/hoist-non-react-statics" "^3.3.1"
+ "@types/use-sync-external-store" "^0.0.3"
+ hoist-non-react-statics "^3.3.2"
+ react-is "^18.0.0"
+ use-sync-external-store "^1.0.0"
+
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -4264,6 +4326,13 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
+redux@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197"
+ integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
regenerate-unicode-properties@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
@@ -4774,6 +4843,11 @@ tiny-glob@^0.2.9:
globalyzer "0.1.0"
globrex "^0.1.2"
+tiny-invariant@^1.0.6:
+ version "1.3.1"
+ resolved "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
+ integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
+
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -4979,7 +5053,12 @@ use-debounce@^9.0.3:
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.3.tgz#bac660c19ab7b38662e08608fee23c7ad303f532"
integrity sha512-FhtlbDtDXILJV7Lix5OZj5yX/fW1tzq+VrvK1fnT2bUrPOGruU9Rw8NCEn+UI9wopfERBEZAOQ8lfeCJPllgnw==
-use-sync-external-store@1.2.0:
+use-memo-one@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
+ integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
+
+use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==