Преглед изворни кода

个人中心页面重新布局,对接积分接口

masoft пре 2 месеци
родитељ
комит
b441f0a077

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
web/public/sw.js


+ 9 - 0
web/src/api/client.ts

@@ -10,6 +10,7 @@ import {
     UserLoginResult,
     UserPasswordParams,
     UserInfoResult,
+    UserBalanceResult,
     ChatSubmitParams,
     ChatSubmitResult,
     ChatMessageParams,
@@ -68,6 +69,14 @@ export const getUserInfo = (): Promise<Result<UserInfoResult>> => {
 };
 
 /**
+ * 查看用户积分
+ */
+export const getUserBalance = (): Promise<Result<UserBalanceResult>> => {
+    const fetchService = useFetch();
+    return fetchService.post('/gpts/user/balance');
+};
+
+/**
  * 聊天提交内容
  */
 export const chatSubmit = (params: ChatSubmitParams) => {

+ 573 - 427
web/src/app/dialogue/page.tsx

@@ -2,7 +2,8 @@
 import { getApiUrl } from "@/utils/common";
 import { getUserSession } from "@/utils/user";
 import { useState, useEffect } from "react";
-import { theme } from "antd";
+import type { PopconfirmProps } from "antd";
+import { theme, Popconfirm } from "antd";
 import { ThemeProvider, css, cx } from "antd-style";
 import { useTheme } from "next-themes";
 import { ProChat, ProChatInstance } from "@ant-design/pro-chat";
@@ -11,11 +12,15 @@ import {
   RiCalendarCloseLine,
   RiLoader2Fill,
   RiBookReadLine,
+  RiVipDiamondFill,
+  RiDeleteBinLine,
+  RiCloseFill,
 } from "react-icons/ri";
 import {
   getChatSession,
   getChatMessage,
   getEmployeeDetail,
+  getUserBalance,
 } from "@/api/client";
 import { ChatSessionResult, EmployeeSearchResult } from "@/utils/clientsApis";
 
@@ -24,27 +29,23 @@ import {
   ChatMessage,
   MessageChange,
   ChatAiModel,
-  SetChatModelListForLocalStorage,
   GetChatModelListForLocalStorage,
-  SetChatLastUsedForLocalStorage,
+  DeleteLocalChatModelDetails,
   UpdateLocalChatListForConversationId,
   GetLocalChatModelDetails,
+  Storage_setListType,
+  Storage_getListType,
+  Storage_setAgentId,
+  Storage_getAgentId,
+  Storage_setConversationId,
+  Storage_getConversationId,
 } from "@/utils/chat";
 import { useGlobalContext } from "@/providers/GlobalProvider";
+import VipPage from "@/app/vip/page";
 
 export default function Dialogue() {
   const themeBig = useTheme();
-  const {
-    changeMenuIndex,
-    loginShow,
-    changeLoginShow,
-    listType,
-    changeListType,
-    agentId,
-    changeAgentId,
-    conversationId,
-    changeConversationId,
-  } = useGlobalContext();
+  const { changeMenuIndex, loginShow, changeLoginShow } = useGlobalContext();
   const [employeeSearch, setEmployeeSearch] = useState<EmployeeSearchResult>();
 
   const [showLeftLetter, setShowLeftLetter] = useState(true);
@@ -58,24 +59,29 @@ export default function Dialogue() {
   const [chatSessionList, setChatSessionList] = useState<ChatSessionResult[]>(
     []
   );
+  const [showVip, setShowVip] = useState(false);
+  const [balance, setBalance] = useState<number>(0);
 
+  //历史记录和智能体列表 显示隐藏
   const handlShowLeftLetter = () => {
     setShowLeftLetter(!showLeftLetter);
   };
 
   //对话框加载聊天历史记录
-  const chatInit = (id: string) => {
+  const chatInit = (id: string, agentId: number) => {
     if (id !== "" && !id.includes("guid_0000_")) {
       getChatMessage({
         conversationId: id,
         firstId: "",
         limit: 100,
-        agentId,
+        agentId: agentId,
       }).then((data) => {
         if (data.data.data) {
           setIniChatMessage(MessageChange(data.data.data));
         }
-        setShowComponent(true);
+        setTimeout(() => {
+          setShowComponent(true);
+        }, 500);
       });
     } else {
       setTimeout(() => {
@@ -85,47 +91,38 @@ export default function Dialogue() {
   };
   //历史会话 - 点击
   const changeChatSession = (id: string) => {
-    if (conversationId !== id) {
-      SetChatLastUsedForLocalStorage({
-        listType: 0,
-        agentId,
-        conversationId: id,
-      });
-      changeConversationId(id);
-      changeListType(0);
-      setShowComponent(false);
-      if (id == "") {
-        setIniChatMessage([]);
-        setTimeout(() => {
-          setShowComponent(true);
-        }, 200);
-      } else {
-        chatInit(id);
-      }
+    //if (Storage_getConversationId() !== id) {}
+    Storage_setListType(0);
+    Storage_setConversationId(id);
+    setShowComponent(false);
+    if (id == "") {
+      setIniChatMessage([]);
+      setTimeout(() => {
+        setShowComponent(true);
+      }, 200);
+    } else {
+      chatInit(id, Storage_getAgentId());
     }
   };
   //助手列表 - 点击
   const changeChatListForLocal = (id: number) => {
+    loadSelectChatDetails(id);
     var model = GetLocalChatModelDetails(id);
     if (model !== null) {
       setShowComponent(false);
       setIniChatMessage([]);
-      SetChatLastUsedForLocalStorage({
-        listType: 1,
-        agentId: id,
-        conversationId: model.ConversationId,
-      });
-      changeConversationId(model.ConversationId);
-      changeListType(1);
-      changeAgentId(id);
+      Storage_setListType(1);
+      Storage_setListType(1);
+      Storage_setAgentId(id);
+      Storage_setConversationId(model.ConversationId);
       if (model.ConversationId == "") {
         setTimeout(() => {
           setShowComponent(true);
         }, 500);
       } else {
-        chatInit(model.ConversationId);
+        chatInit(model.ConversationId, id);
       }
-      loadChatSession("");
+      loadChatSession(id, "");
     }
   };
   //新对话
@@ -142,17 +139,9 @@ export default function Dialogue() {
       ],
       ...chatSessionList,
     ]);
-
-    SetChatLastUsedForLocalStorage({
-      listType: 0,
-      agentId,
-      conversationId: newConversationId,
-    });
-
+    Storage_setListType(0);
+    Storage_setConversationId(newConversationId);
     setHistoryOrChatList(0);
-    changeListType(0);
-    changeConversationId(newConversationId);
-
     setShowComponent(false);
     setIniChatMessage([]);
     setTimeout(() => {
@@ -161,6 +150,8 @@ export default function Dialogue() {
   };
   //设置新会话的ID
   const setConversationId = (id: string) => {
+    let listType = Storage_getListType();
+    let conversationId = Storage_getConversationId();
     if (listType === 0) {
       if (conversationId.includes("guid_0000_") || conversationId == "") {
         if (chatSessionList !== null && chatSessionList.length > 0) {
@@ -175,41 +166,62 @@ export default function Dialogue() {
       }
     }
     if (listType === 1) {
-      console.log(id);
-      var list = UpdateLocalChatListForConversationId(agentId, id);
+      var list = UpdateLocalChatListForConversationId(Storage_getAgentId(), id);
       setChatAiModelList(list);
     }
-    changeConversationId(id);
-    SetChatLastUsedForLocalStorage({
-      listType: listType,
-      agentId,
-      conversationId: id,
-    });
+
+    Storage_setConversationId(id);
   };
   //加载助手对应的历史会话
-  const loadChatSession = (last_id: string) => {
-    setShowComponent(false);
-    getChatSession({ agentId: agentId, last_id: last_id, limit: 100 }).then(
-      (data) => {
-        if (data.code === 0 && data.data.data !== null) {
-          if (last_id == "") {
-            setChatSessionList(data.data.data);
+  const loadChatSession = (agentId: number, last_id: string) => {
+    setTimeout(() => {
+      setShowComponent(false);
+      getChatSession({ agentId: agentId, last_id: last_id, limit: 100 }).then(
+        (data) => {
+          if (data.code === 0 && data.data.data !== null) {
+            if (last_id == "") {
+              setChatSessionList(data.data.data);
+            } else {
+              setChatSessionList((chatSessionList) => [
+                ...data.data.data,
+                ...chatSessionList,
+              ]);
+            }
           } else {
-            setChatSessionList((chatSessionList) => [
-              ...data.data.data,
-              ...chatSessionList,
-            ]);
+            setChatSessionList([]);
           }
-        } else {
-          setChatSessionList([]);
+          setShowComponent(true);
         }
-        setShowComponent(true);
+      );
+    });
+  };
+  //加载当前智能体
+  const loadSelectChatDetails = (id: number) => {
+    getEmployeeDetail({ id: id }).then((res) => {
+      if (res.code == 0 && res.data !== null) {
+        setEmployeeSearch(res.data);
+      } else {
+        setEmployeeSearch(undefined);
       }
-    );
+    });
+  };
+  //实时获取用户积分余额
+  const getBalance = () => {
+    getUserBalance().then((res) => {
+      if (res.code == 0) {
+        setBalance(res.data.balance);
+      } else {
+        setBalance(0);
+      }
+    });
   };
 
   useEffect(() => {
     changeMenuIndex(1);
+    let listType = Storage_getListType();
+    let agentId = Storage_getAgentId();
+    let conversationId = Storage_getConversationId();
+
     if (agentId !== null && agentId > 0) {
       //智能体进入 加载本地记录
       getEmployeeDetail({ id: agentId }).then((res) => {
@@ -218,18 +230,18 @@ export default function Dialogue() {
           setEmployeeSearch(res.data);
           var list = LoadLocalChatList(res.data);
           setChatAiModelList(list);
-          chatInit(conversationId);
+          chatInit(conversationId, agentId);
         } else {
           var list = GetChatModelListForLocalStorage();
           setChatAiModelList(list);
           var model = GetLocalChatModelDetails(agentId);
           if (model === null && list !== null && list.length > 0) {
-            changeListType(1);
-            changeAgentId(list[0].id);
-            changeConversationId(list[0].ConversationId);
-            chatInit(list[0].ConversationId);
+            Storage_setListType(1);
+            Storage_setAgentId(list[0].id);
+            Storage_setConversationId(list[0].ConversationId);
+            chatInit(list[0].ConversationId, agentId);
           } else {
-            chatInit(model.ConversationId);
+            chatInit(model.ConversationId, agentId);
           }
         }
       });
@@ -238,17 +250,24 @@ export default function Dialogue() {
       setChatAiModelList(list);
       var model = GetLocalChatModelDetails(agentId);
       if (model === null && list !== null && list.length > 0) {
-        changeListType(1);
-        changeAgentId(list[0].id);
-        changeConversationId(list[0].ConversationId);
-        chatInit(list[0].ConversationId);
+        Storage_setListType(1);
+        Storage_setAgentId(list[0].id);
+        Storage_setConversationId(list[0].ConversationId);
+        chatInit(list[0].ConversationId, agentId);
       } else {
-        chatInit(model.ConversationId);
+        chatInit(model.ConversationId, agentId);
       }
       setHistoryOrChatList(1);
     }
-    loadChatSession(chatSessionLastId);
-  }, [loginShow, agentId, chatSessionLastId]);
+    if (conversationId !== null && conversationId !== "") {
+      setConversationId(conversationId);
+    }
+    
+    loadChatSession(agentId, chatSessionLastId);
+
+    getBalance();
+
+  }, [loginShow, chatSessionLastId]);
 
   //样式覆盖
   const BarkCustomClassName = cx(
@@ -280,390 +299,517 @@ export default function Dialogue() {
   `)
   );
   //TAGS
-  const readTags = (value:string) => {
-    const items = value.split(',');
+  const readTags = (value: string) => {
+    const items = value.split(",");
     return (
       <ul>
         {items.map((item, index) => (
-          <span key={index} className="text-sm py-1 px-2 mx-1 rounded-md bg-[#fdebaf] text-[#bc900c]">
-              {item.trim().replace('"', '').replace('"', '')}
+          <span
+            key={index}
+            className="text-sm py-1 px-2 mx-1 rounded-md bg-[#fdebaf] text-[#bc900c]"
+          >
+            {item.trim().replace('"', "").replace('"', "")}
           </span>
         ))}
       </ul>
     );
   };
+  //删除智能体列表中的指定记录
+  const confirmDeletLocalChat = (id: number) => {
+    var list = DeleteLocalChatModelDetails(id);
+    setChatAiModelList(list);
+    let listType = Storage_getListType();
+    let agentId = Storage_getAgentId();
+    let conversationId = Storage_getConversationId();
 
-  return (
-    <div
-      className="flex justify-between items-center bg-[slate-200] dark:bg-[#000]"
-      style={{ height: "100%" }}
-    >
-      {showLeftLetter && (
-        <div
-          className={`bg-[#f8f9fa] dark:bg-[#202123] border-r-1 border-indigo-100 dark:border-blue-950 overflow-auto scroll-smooth shrink-0`}
-          style={{
-            height: "calc(100vh - 5rem)",
-            width: "20rem",
-          }}
-        >
-          <div className="sticky top-0 z-50 dark:shadow-[rgba(255,255,255,.15)] backdrop-blur dark:bg-transparent transition-all">
-            <div className="flex justify-between items-center pt-2 px-2">
-              <div>
-                <button
-                  className="inline-flex py-1 px-1 rounded-lg items-center text-white global-bg-color"
-                  onClick={newChat}
-                >
-                  <RiAddCircleFill className="w-6 h-6" />
-                  <span className="ml-1 pr-2 flex items-start flex-col leading-none">
-                    <span className="title-font font-medium">新对话</span>
-                  </span>
-                </button>
-              </div>
-              <div
-                className="cursor-pointer text-gray-400 hover:text-blue-600"
-                onClick={handlShowLeftLetter}
-              >
-                <RiBookReadLine className="text-2xl" />
-              </div>
-            </div>
-            <div className="w-full mt-4">
-              <div className="flex mx-auto flex-wrap justify-center border-b-2 border-blue-50 shadow-sm dark:border-blue-950 pb-2">
-                <a
-                  className={`cursor-pointer py-1 px-4 mx-4 rounded-lg ${
-                    historyOrChatList === 0
-                      ? "global-txt-color"
-                      : "dark:text-gray-500"
-                  }`}
-                  onClick={() => {
-                    setHistoryOrChatList(0);
-                  }}
-                >
-                  对话历史
-                </a>
-                <a
-                  className={`cursor-pointer py-1 px-4 mx-4 rounded-lg ${
-                    historyOrChatList === 1
-                      ? "global-txt-color"
-                      : "dark:text-gray-500"
-                  }`}
-                  onClick={() => {
-                    setHistoryOrChatList(1);
-                  }}
-                >
-                  助手列表
-                </a>
-              </div>
-            </div>
-          </div>
+    if (agentId !== null && agentId > 0) {
+      //智能体进入 加载本地记录
+      getEmployeeDetail({ id: agentId }).then((res) => {
+        setHistoryOrChatList(listType);
+        if (res.code == 0 && res.data !== null) {
+          setEmployeeSearch(res.data);
+          var list = LoadLocalChatList(res.data);
+          setChatAiModelList(list);
+          chatInit(conversationId, agentId);
+        } else {
+          var list = GetChatModelListForLocalStorage();
+          setChatAiModelList(list);
+          var model = GetLocalChatModelDetails(agentId);
+          if (model === null && list !== null && list.length > 0) {
+            Storage_setListType(1);
+            Storage_setAgentId(list[0].id);
+            Storage_setConversationId(list[0].ConversationId);
+            chatInit(list[0].ConversationId, agentId);
+          } else {
+            chatInit(model.ConversationId, agentId);
+          }
+        }
+      });
+    } else {
+      var list = GetChatModelListForLocalStorage();
+      setChatAiModelList(list);
+      var model = GetLocalChatModelDetails(agentId);
+      if (model === null && list !== null && list.length > 0) {
+        Storage_setListType(1);
+        Storage_setAgentId(list[0].id);
+        Storage_setConversationId(list[0].ConversationId);
+        chatInit(list[0].ConversationId, agentId);
+      } else {
+        chatInit(model.ConversationId, agentId);
+      }
+      setHistoryOrChatList(1);
+    }
+    if (conversationId !== null && conversationId !== "") {
+      setConversationId(conversationId);
+    }
+    loadChatSession(agentId, chatSessionLastId);
+  };
+  const closeVipBox = () => {
+    setShowVip(false);
+  };
 
-          {historyOrChatList === 0 &&
-            chatSessionList !== null &&
-            chatSessionList.length > 0 && (
-              <div className="py-4 px-2">
-                <div className="mx-auto flex flex-col">
-                  {chatSessionList.map((item) => (
-                    <div
-                      className="w-full mt-3 cursor-pointer"
-                      key={item.id}
-                      onClick={() => changeChatSession(item.id)}
+  return (
+    <>
+      <div
+        className="flex justify-between items-center bg-[slate-200] dark:bg-[#000]"
+        style={{ height: "100%" }}
+      >
+        {showLeftLetter && (
+          <div
+            className="bg-[#f8f9fa] dark:bg-[#202123] border-r-1 border-indigo-100 dark:border-blue-950"
+            style={{
+              height: "calc(100vh - 5rem)",
+              width: "20rem",
+            }}
+          >
+            <div
+              className={`bg-[#f8f9fa] dark:bg-[#202123] border-r-1 border-indigo-100 dark:border-blue-950 overflow-auto scroll-smooth shrink-0`}
+              style={{
+                height: "calc(100vh - 10rem)",
+                width: "20rem",
+              }}
+            >
+              <div className="sticky top-0 z-50 dark:shadow-[rgba(255,255,255,.15)] backdrop-blur dark:bg-transparent transition-all">
+                <div className="flex justify-between items-center pt-2 px-2">
+                  <div>
+                    <button
+                      className="inline-flex py-1 px-1 rounded-lg items-center text-white global-bg-color"
+                      onClick={newChat}
                     >
-                      <div
-                        className={`flex rounded border ${
-                          conversationId == item.id &&
-                          listType === historyOrChatList
-                            ? "dark:border-[#0061ff] bg-blue-50 dark:bg-gray-800 text-[#0061ff]"
-                            : "border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400"
-                        } p-3 sm:flex-row flex-col`}
-                      >
-                        <div className="flex-grow text-ellipsis line-clamp-2">
-                          <h2 className="pb-2">{item.name}</h2>
-                          <p className="text-sm text-gray-400">
-                            {new Date(item.createdAt).toLocaleString()}
-                          </p>
-                        </div>
-                      </div>
-                    </div>
-                  ))}
+                      <RiAddCircleFill className="w-6 h-6" />
+                      <span className="ml-1 pr-2 flex items-start flex-col leading-none">
+                        <span className="title-font font-medium">新对话</span>
+                      </span>
+                    </button>
+                  </div>
+                  <div
+                    className="cursor-pointer text-gray-400 hover:text-blue-600"
+                    onClick={handlShowLeftLetter}
+                  >
+                    <RiBookReadLine className="text-2xl" />
+                  </div>
                 </div>
-              </div>
-            )}
-          {historyOrChatList === 0 &&
-            (chatSessionList === null || chatSessionList.length == 0) && (
-              <div className="w-full px-5 text-gray-200 dark:text-gray-600 flex flex-col mt-20">
-                <div className="flex justify-center">
-                  <RiCalendarCloseLine className="w-16 h-16 text-center" />
+                <div className="w-full mt-4">
+                  <div className="flex mx-auto flex-wrap justify-center border-b-2 border-blue-50 shadow-sm dark:border-blue-950 pb-2">
+                    <a
+                      className={`cursor-pointer py-1 px-4 mx-4 rounded-lg ${
+                        historyOrChatList === 0
+                          ? "global-txt-color"
+                          : "dark:text-gray-500"
+                      }`}
+                      onClick={() => {
+                        setHistoryOrChatList(0);
+                      }}
+                    >
+                      对话历史
+                    </a>
+                    <a
+                      className={`cursor-pointer py-1 px-4 mx-4 rounded-lg ${
+                        historyOrChatList === 1
+                          ? "global-txt-color"
+                          : "dark:text-gray-500"
+                      }`}
+                      onClick={() => {
+                        setHistoryOrChatList(1);
+                      }}
+                    >
+                      助手列表
+                    </a>
+                  </div>
                 </div>
-                <div className="text-center mt-4">对话历史空空如也</div>
               </div>
-            )}
 
-          {historyOrChatList === 1 &&
-            chatAiModelList !== null &&
-            chatAiModelList.length > 0 && (
-              <div className="py-4 px-2">
-                <div className="mx-auto flex flex-col">
-                  {chatAiModelList.map((item) => (
-                    <div
-                      className="w-full mt-3 cursor-pointer"
-                      key={item.id}
-                      onClick={() => changeChatListForLocal(item.id)}
-                    >
-                      <div
-                        className={`flex rounded border ${
-                          agentId == item.id && listType === historyOrChatList
-                            ? "dark:border-[#0061ff] bg-blue-50 dark:bg-gray-800 text-[#0061ff]"
-                            : "border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400"
-                        }  p-3 sm:flex-row flex-col`}
-                      >
-                        <div className="flex flex-row">
-                          <div className="shrink-0 inline-flex items-center justify-center h-16 overflow-hidden">
-                            <img src={item.avatar} className="h-16" />
+              {historyOrChatList === 0 &&
+                chatSessionList !== null &&
+                chatSessionList.length > 0 && (
+                  <div className="py-4 px-2">
+                    <div className="mx-auto flex flex-col">
+                      {chatSessionList.map((item) => (
+                        <div
+                          className="w-full mt-3 cursor-pointer"
+                          key={item.id}
+                          onClick={() => changeChatSession(item.id)}
+                        >
+                          <div
+                            className={`flex rounded border ${
+                              Storage_getConversationId() == item.id &&
+                              Storage_getListType() === historyOrChatList
+                                ? "dark:border-[#0061ff] bg-blue-50 dark:bg-gray-800 text-[#0061ff]"
+                                : "border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400"
+                            } p-3 sm:flex-row flex-col`}
+                          >
+                            <div className="flex-grow text-ellipsis line-clamp-2">
+                              <h2 className="pb-2">{item.name}</h2>
+                              <p className="text-sm text-gray-400">
+                                {new Date(item.createdAt).toLocaleString()}
+                              </p>
+                            </div>
                           </div>
-                          <div className="flex-grow pl-2 pt-2">
-                            <h2 className="text-base mb-1 line-clamp-1 text-ellipsis">
-                              {item.name}
-                            </h2>
-                            <p className="text-sm line-clamp-1 text-ellipsis">
-                              {item.estimate}
-                            </p>
+                        </div>
+                      ))}
+                    </div>
+                  </div>
+                )}
+              {historyOrChatList === 0 &&
+                (chatSessionList === null || chatSessionList.length == 0) && (
+                  <div className="w-full px-5 text-gray-200 dark:text-gray-600 flex flex-col mt-20">
+                    <div className="flex justify-center">
+                      <RiCalendarCloseLine className="w-16 h-16 text-center" />
+                    </div>
+                    <div className="text-center mt-4">对话历史空空如也</div>
+                  </div>
+                )}
+
+              {historyOrChatList === 1 &&
+                chatAiModelList !== null &&
+                chatAiModelList.length > 0 && (
+                  <div className="py-4 px-2">
+                    <div className="mx-auto flex flex-col">
+                      {chatAiModelList.map((item) => (
+                        <div className="w-full mt-3" key={item.id}>
+                          <div
+                            className={`flex rounded border group ${
+                              Storage_getAgentId() == item.id &&
+                              Storage_getListType() === historyOrChatList
+                                ? "dark:border-[#0061ff] bg-blue-50 dark:bg-gray-800 text-[#0061ff]"
+                                : "border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400"
+                            }  p-3 sm:flex-row flex-col`}
+                          >
+                            <div className="flex flex-row relative">
+                              <Popconfirm
+                                title="删除提醒"
+                                description="确定删除当前智能助手吗?"
+                                onConfirm={() => confirmDeletLocalChat(item.id)}
+                                okText="确定"
+                                cancelText="取消"
+                              >
+                                <RiDeleteBinLine className="absolute top-0.5 right-0.5 cursor-pointer group-hover:block hidden" />
+                              </Popconfirm>
+                              <div className="shrink-0 inline-flex items-center justify-center h-16 overflow-hidden">
+                                <img
+                                  src={item.avatar}
+                                  className="h-16 cursor-pointer"
+                                  onClick={() =>
+                                    changeChatListForLocal(item.id)
+                                  }
+                                />
+                              </div>
+                              <div className="flex-grow pl-2 pt-2">
+                                <h2
+                                  className="text-base mb-1 line-clamp-1 text-ellipsis pr-5 cursor-pointer"
+                                  onClick={() =>
+                                    changeChatListForLocal(item.id)
+                                  }
+                                >
+                                  {item.name}
+                                </h2>
+                                <p
+                                  className="text-sm line-clamp-1 text-ellipsis cursor-pointer"
+                                  onClick={() =>
+                                    changeChatListForLocal(item.id)
+                                  }
+                                >
+                                  {item.estimate}
+                                </p>
+                              </div>
+                            </div>
                           </div>
                         </div>
-                      </div>
+                      ))}
                     </div>
-                  ))}
-                </div>
-              </div>
-            )}
-          {historyOrChatList === 1 &&
-            (chatAiModelList === null || chatAiModelList.length == 0) && (
-              <div className="w-full px-5 text-gray-200 dark:text-gray-600 flex flex-col mt-20">
-                <div className="flex justify-center">
-                  <RiCalendarCloseLine className="w-16 h-16 text-center" />
-                </div>
-                <div className="text-center mt-4">当前助手空空如也</div>
+                  </div>
+                )}
+              {historyOrChatList === 1 &&
+                (chatAiModelList === null || chatAiModelList.length == 0) && (
+                  <div className="w-full px-5 text-gray-200 dark:text-gray-600 flex flex-col mt-20">
+                    <div className="flex justify-center">
+                      <RiCalendarCloseLine className="w-16 h-16 text-center" />
+                    </div>
+                    <div className="text-center mt-4">当前助手空空如也</div>
+                  </div>
+                )}
+            </div>
+
+            <div
+              className="text-sm"
+              style={{
+                height: "5rem",
+                width: "20rem",
+              }}
+            >
+              <div className="px-3 py-3 border-t-2 border-blue-50 shadow-sm dark:border-blue-950 dark:text-[#bc900c]">
+                <p className="flex items-center">
+                  <RiVipDiamondFill className="text-[#f4a629]" />
+                  <span className="ml-2">普通额度 {balance} 积分</span>
+                </p>
+                <p className="flex items-center mt-2">
+                  <RiVipDiamondFill className="text-[#c81021]" />
+                  <span className="ml-2">高级额度 {balance} 积分</span>
+                </p>
               </div>
-            )}
-        </div>
-      )}
+            </div>
+          </div>
+        )}
 
-      <ThemeProvider
-        appearance={themeBig.theme}
-        theme={{
-          algorithm:
-            themeBig.theme == "dark"
-              ? theme.darkAlgorithm
-              : theme.defaultAlgorithm,
-        }}
-      >
-        <div
-          className={`bg-[#fcfdff] dark:bg-[#202123] grow`}
-          style={{
-            height: "calc(100vh - 5rem)",
+        <ThemeProvider
+          appearance={themeBig.theme}
+          theme={{
+            algorithm:
+              themeBig.theme == "dark"
+                ? theme.darkAlgorithm
+                : theme.defaultAlgorithm,
           }}
         >
-          {showComponent && (
-            <ProChat
-              className={
-                themeBig.theme == "dark"
-                  ? BarkCustomClassName
-                  : LightCustomClassName
-              }
-              style={{
-                height: "100%",
-                width: "100%",
-              }}
-              initialChats={iniChatMessage}
-              //helloMessage ={helloMessage}
-              actions={{
-                render: (defaultDoms) => {
-                  if (showLeftLetter) {
-                    return defaultDoms;
-                  }
-                  return [
-                    <a
-                      key="handlShowLeftLetter"
-                      className="text-gray-400 hover:text-blue-600"
-                      onClick={handlShowLeftLetter}
-                    >
-                      <RiBookReadLine className="text-2xl" />
-                    </a>,
-                    ...defaultDoms,
-                  ];
-                },
-                flexConfig: {
-                  gap: 24,
-                  direction: "horizontal",
-                  justify: "space-start",
-                },
-              }}
-              request={async (messages) => {
-                const response = await fetch(
-                  getApiUrl() + "/gpts/chat/submit",
-                  {
-                    method: "POST",
-                    headers: {
-                      "Content-Type": "application/json;charset=UTF-8",
-                      Authorization: `Bearer ${getUserSession()}`,
-                    },
-                    body: JSON.stringify({
-                      content: messages[messages.length - 1].content,
-                      conversationId: conversationId.includes("guid_0000_")
-                        ? ""
-                        : conversationId,
-                      agentId: agentId,
-                    }),
-                  }
-                );
-                if (!response.ok || !response.body) {
-                  const errorResponseOptions = {
-                    status: 500,
-                    statusText: "网络错误,请重试!",
-                  };
-                  return new Response(null, errorResponseOptions);
-                } else {
-                  const reader = response.body.getReader();
-                  const decoder = new TextDecoder("utf-8");
-                  const encoder = new TextEncoder();
+          <div
+            className={`bg-[#fcfdff] dark:bg-[#202123] grow`}
+            style={{
+              height: "calc(100vh - 5rem)",
+            }}
+          >
+            {showComponent && (
+              <ProChat
+                className={
+                  themeBig.theme == "dark"
+                    ? BarkCustomClassName
+                    : LightCustomClassName
+                }
+                style={{
+                  height: "100%",
+                  width: "100%",
+                }}
+                initialChats={iniChatMessage}
+                //helloMessage ={helloMessage}
+                actions={{
+                  render: (defaultDoms) => {
+                    if (showLeftLetter) {
+                      return defaultDoms;
+                    }
+                    return [
+                      <a
+                        key="handlShowLeftLetter"
+                        className="text-gray-400 hover:text-blue-600"
+                        onClick={handlShowLeftLetter}
+                      >
+                        <RiBookReadLine className="text-2xl" />
+                      </a>,
+                      ...defaultDoms,
+                    ];
+                  },
+                  flexConfig: {
+                    gap: 24,
+                    direction: "horizontal",
+                    justify: "space-start",
+                  },
+                }}
+                request={async (messages) => {
+                  const response = await fetch(
+                    getApiUrl() + "/gpts/chat/submit",
+                    {
+                      method: "POST",
+                      headers: {
+                        "Content-Type": "application/json;charset=UTF-8",
+                        Authorization: `Bearer ${getUserSession()}`,
+                      },
+                      body: JSON.stringify({
+                        content: messages[messages.length - 1].content,
+                        conversationId: Storage_getConversationId().includes(
+                          "guid_0000_"
+                        )
+                          ? ""
+                          : Storage_getConversationId(),
+                        agentId: Storage_getAgentId(),
+                      }),
+                    }
+                  );
+                  if (!response.ok || !response.body) {
+                    const errorResponseOptions = {
+                      status: 500,
+                      statusText: "网络错误,请重试!",
+                    };
+                    return new Response(null, errorResponseOptions);
+                  } else {
+                    const reader = response.body.getReader();
+                    const decoder = new TextDecoder("utf-8");
+                    const encoder = new TextEncoder();
 
-                  const readableStream = new ReadableStream({
-                    async start(controller) {
-                      function push() {
-                        reader
-                          .read()
-                          .then(({ done, value }) => {
-                            if (done) {
-                              controller.close();
-                              return;
-                            }
-                            const chunk = decoder.decode(value, {
-                              stream: true,
-                            });
+                    const readableStream = new ReadableStream({
+                      async start(controller) {
+                        function push() {
+                          reader
+                            .read()
+                            .then(({ done, value }) => {
+                              if (done) {
+                                controller.close();
+                                return;
+                              }
+                              const chunk = decoder.decode(value, {
+                                stream: true,
+                              });
 
-                            var linesArray = chunk.split("\n");
-                            for (var i = 0; i < linesArray.length - 1; i++) {
-                              const line = linesArray[i];
-                              const message = line.replace("data: ", "");
-                              const parsed = JSON.parse(message);
-                              if (!parsed.finish && parsed.answer) {
-                                controller.enqueue(
-                                  encoder.encode(parsed.answer)
-                                );
-                              } else {
-                                if (parsed.finish) {
-                                  setConversationId(parsed.conversation_id);
-                                }
-                                if (parsed.need_login) {
-                                  changeLoginShow(true);
-                                  controller.enqueue(
-                                    encoder.encode("请登录后使用")
-                                  );
-                                } else if (parsed.need_pay) {
+                              var linesArray = chunk.split("\n");
+                              for (var i = 0; i < linesArray.length - 1; i++) {
+                                const line = linesArray[i];
+                                const message = line.replace("data: ", "");
+                                const parsed = JSON.parse(message);
+                                if (!parsed.finish && parsed.answer) {
                                   controller.enqueue(
-                                    encoder.encode(
-                                      "当前智能体需要会员才能使用,去开通:[会员套餐](/vip)"
-                                    )
+                                    encoder.encode(parsed.answer)
                                   );
+                                } else {
+                                  if (parsed.finish) {
+                                    setConversationId(parsed.conversation_id);
+                                    getBalance();
+                                  }
+                                  if (parsed.need_login) {
+                                    changeLoginShow(true);
+                                    controller.enqueue(
+                                      encoder.encode("请登录后使用")
+                                    );
+                                  } else if (parsed.need_pay) {
+                                    controller.enqueue(
+                                      encoder.encode(
+                                        "当前智能体需要会员才能使用,去开通:[会员套餐](/vip)"
+                                      )
+                                    );
+                                    setShowVip(true);
+                                  }
                                 }
                               }
-                            }
-                            push();
-                          })
-                          .catch((err) => {
-                            console.error("读取流中的数据时发生错误", err);
-                            controller.error(err);
-                          });
-                      }
-                      push();
-                    },
-                  });
-                  return new Response(readableStream);
-                }
-              }}
-            />
-          )}
-          {!showComponent && (
-            <div className="w-full px-5 text-gray-300 flex flex-col mt-40">
-              <div className="flex justify-center">
-                <RiLoader2Fill className="animate-spin h-16 w-16" />
+                              push();
+                            })
+                            .catch((err) => {
+                              console.error("读取流中的数据时发生错误", err);
+                              controller.error(err);
+                            });
+                        }
+                        push();
+                      },
+                    });
+                    return new Response(readableStream);
+                  }
+                }}
+              />
+            )}
+            {!showComponent && (
+              <div className="w-full px-5 text-gray-300 flex flex-col mt-40">
+                <div className="flex justify-center">
+                  <RiLoader2Fill className="animate-spin h-16 w-16" />
+                </div>
+                <div className="text-center mt-4">会话加载中</div>
               </div>
-              <div className="text-center mt-4">会话加载中</div>
-            </div>
-          )}
-        </div>
-      </ThemeProvider>
+            )}
+          </div>
+        </ThemeProvider>
 
-      {employeeSearch != null && (
-        <div
-          className={`bg-[#ecf1f9] dark:bg-[#202123] border-l-1 border-indigo-100 dark:border-blue-950 overflow-auto scroll-smooth shrink-0`}
-          style={{
-            height: "calc(100vh - 5rem)",
-            width: "25rem",
-          }}
-        >
-          <div className="">
-            <div className="h-96 w-11/12 mx-auto relative">
-              <div className="h-72 w-10/12 mx-auto absolute rounded-lg bg-[#f6f9fd] top-20 left-0 right-0 shadow-md dark:bg-gray-950"></div>
-              <div className="h-64 w-full absolute rounded-lg bg-[#fff] top-28 text-center shadow-lg dark:bg-gray-900">
-                <div className="mt-16 text-xl p-3 h-12 overflow-hidden  dark:text-gray-500">
-                  {employeeSearch.title}
-                </div>
-                <div className="p-3 h-12 overflow-hidden">
-                  {readTags(employeeSearch.tags)}
-                </div>
-                <div className="p-5 text-sm text-gray-500">
-                  <div className="grid grid-cols-3 gap-4">
-                    <div className="text-center">
-                      <p className="dark:text-gray-700">被雇佣</p>
-                      <p className="text-2xl text-gray-700  dark:text-gray-500">{employeeSearch.hireCount}</p>
-                    </div>
-                    <div className="text-center">
-                      <p className="dark:text-gray-700">已服务</p>
-                      <p className="text-2xl text-gray-700  dark:text-gray-500">{employeeSearch.serviceCount}</p>
-                    </div>
-                    <div className="text-center">
-                      <p className="dark:text-gray-700">业绩单</p>
-                      <p className="text-2xl text-gray-700  dark:text-gray-500">{employeeSearch.achievementCount}w</p>
+        {employeeSearch != null && (
+          <div
+            className={`bg-[#ecf1f9] dark:bg-[#202123] border-l-1 border-indigo-100 dark:border-blue-950 overflow-auto scroll-smooth shrink-0`}
+            style={{
+              height: "calc(100vh - 5rem)",
+              width: "25rem",
+            }}
+          >
+            <div className="">
+              <div className="h-96 w-11/12 mx-auto relative">
+                <div className="h-72 w-10/12 mx-auto absolute rounded-lg bg-[#f6f9fd] top-20 left-0 right-0 shadow-md dark:bg-gray-950"></div>
+                <div className="h-64 w-full absolute rounded-lg bg-[#fff] top-28 text-center shadow-lg dark:bg-gray-900">
+                  <div className="mt-16 text-xl p-3 h-12 overflow-hidden  dark:text-gray-500">
+                    {employeeSearch.title}
+                  </div>
+                  <div className="p-3 h-12 overflow-hidden">
+                    {readTags(employeeSearch.tags)}
+                  </div>
+                  <div className="p-5 text-sm text-gray-500">
+                    <div className="grid grid-cols-3 gap-4">
+                      <div className="text-center">
+                        <p className="dark:text-gray-700">被雇佣</p>
+                        <p className="text-2xl text-gray-700  dark:text-gray-500">
+                          {employeeSearch.hireCount}
+                        </p>
+                      </div>
+                      <div className="text-center">
+                        <p className="dark:text-gray-700">已服务</p>
+                        <p className="text-2xl text-gray-700  dark:text-gray-500">
+                          {employeeSearch.serviceCount}
+                        </p>
+                      </div>
+                      <div className="text-center">
+                        <p className="dark:text-gray-700">业绩单</p>
+                        <p className="text-2xl text-gray-700  dark:text-gray-500">
+                          {employeeSearch.achievementCount}w
+                        </p>
+                      </div>
                     </div>
                   </div>
                 </div>
-              </div>
-              <div className="h-32 w-32 absolute rounded-full bg-[#fff] top-10 left-0 right-0 mx-auto overflow-hidden">
-                <img
-                  className="object-top"
-                  src={employeeSearch.avatar}
-                ></img>
+                <div className="h-32 w-32 absolute rounded-full bg-[#fff] top-10 left-0 right-0 mx-auto overflow-hidden">
+                  <img className="object-top" src={employeeSearch.avatar}></img>
+                </div>
               </div>
             </div>
-          </div>
 
-          <div className="w-11/12 mx-auto rounded-lg bg-[#fff] dark:bg-gray-900 shadow-md mt-4">
-            <div className="p-3">
-              <h2 className="global-txt-color px-3">个人简介</h2>
-              <div className="leading-6 text-sm text-gray-700 m-3 p-3 rounded-lg bg-[#f9fafb] dark:bg-gray-950">
-                {employeeSearch.intro}
+            <div className="w-11/12 mx-auto rounded-lg bg-[#fff] dark:bg-gray-900 shadow-md mt-4">
+              <div className="p-3">
+                <h2 className="global-txt-color px-3">个人简介</h2>
+                <div className="leading-6 text-sm text-gray-700 m-3 p-3 rounded-lg bg-[#f9fafb] dark:bg-gray-950">
+                  {employeeSearch.intro}
+                </div>
               </div>
             </div>
-          </div>
 
-          <div className="w-11/12 mx-auto rounded-lg bg-[#fff] dark:bg-gray-900 shadow-md mt-6 ">
-            <div className="p-3">
-              <h2 className="global-txt-color px-3">自我评价</h2>
-              <div className="leading-6 text-sm text-gray-700 m-3 p-3 rounded-lg bg-[#f9fafb] dark:bg-gray-950">
-              {employeeSearch.estimate}
+            <div className="w-11/12 mx-auto rounded-lg bg-[#fff] dark:bg-gray-900 shadow-md mt-6 ">
+              <div className="p-3">
+                <h2 className="global-txt-color px-3">自我评价</h2>
+                <div className="leading-6 text-sm text-gray-700 m-3 p-3 rounded-lg bg-[#f9fafb] dark:bg-gray-950">
+                  {employeeSearch.estimate}
+                </div>
               </div>
             </div>
-          </div>
 
-          <div className="w-11/12 mx-auto rounded-lg bg-[#fff] dark:bg-gray-900 shadow-md my-6">
-            <div className="p-3">
-              <h2 className="global-txt-color px-3">技能</h2>
-              <div className="leading-6 text-sm text-gray-700 m-3 p-3 rounded-lg bg-[#f9fafb] dark:bg-gray-950">
-              {employeeSearch.skill}
+            <div className="w-11/12 mx-auto rounded-lg bg-[#fff] dark:bg-gray-900 shadow-md my-6">
+              <div className="p-3">
+                <h2 className="global-txt-color px-3">技能</h2>
+                <div className="leading-6 text-sm text-gray-700 m-3 p-3 rounded-lg bg-[#f9fafb] dark:bg-gray-950">
+                  {employeeSearch.skill}
+                </div>
               </div>
             </div>
           </div>
+        )}
+      </div>
+
+      {showVip && (
+        <div className="pay-box">
+          <div className="pay-mask"></div>
+          <div className="pay-div p-7 bg-white dark:bg-gray-900 text-base rounded container">
+            <RiCloseFill
+              className="absolute right-2.5 top-2.5 w-8 h-8 cursor-pointer transform duration-500 ease-in-out hover:scale-125 text-gray-500"
+              onClick={closeVipBox}
+            ></RiCloseFill>
+            <VipPage></VipPage>
+          </div>
         </div>
       )}
-    </div>
+    </>
   );
 }

+ 9 - 1
web/src/app/globals.scss

@@ -319,4 +319,12 @@ select:focus {
   top: 50%;
   transform: translate(-50%, -50%);
   border-radius: 8px;
-}
+}
+
+/* TABS样式覆盖 */
+.ant-tabs-top >.ant-tabs-nav::before{
+  @apply dark:border-b-slate-700
+}
+.ant-tabs .ant-tabs-tab{
+  @apply dark:text-slate-300
+}

+ 1 - 1
web/src/app/layout.tsx

@@ -32,7 +32,7 @@ export default function RootLayout({
   children: React.ReactNode;
 }>) {
   return (
-    <html suppressHydrationWarning>
+    <html suppressHydrationWarning={true}>
       <body className={inter.className}>
         <GlobalProvider>
           <ThemeProvider>{children}</ThemeProvider>

+ 5 - 14
web/src/app/page.tsx

@@ -10,20 +10,12 @@ import {
 import { useGlobalContext } from "@/providers/GlobalProvider";
 import { getCategoryList, getEmployeeSearch } from "@/api/client";
 import { CategoryListResult, EmployeeSearchResult } from "@/utils/clientsApis";
-import { SetChatLastUsedForLocalStorage } from "@/utils/chat";
+import { Storage_setListType,Storage_setAgentId,Storage_setConversationId } from "@/utils/chat";
 
 export default function Home() {
   const router = useRouter();
   const boxRef = useRef<HTMLDivElement | null>(null);
-  const {
-    changeMenuIndex,
-    listType,
-    changeListType,
-    agentId,
-    changeAgentId,
-    conversationId,
-    changeConversationId,
-  } = useGlobalContext();
+  const { changeMenuIndex } = useGlobalContext();
   const [categoryList, setCategoryList] = useState<CategoryListResult[]>([]);
   const [employeeList, setEmployeeList] = useState<EmployeeSearchResult[]>([]);
   const [categoryId, setCategoryId] = useState<number>(0);
@@ -81,10 +73,9 @@ export default function Home() {
   };
 
   const addChat = (id: number) => {
-    changeListType(1);
-    changeAgentId(id);
-    changeConversationId("");
-    SetChatLastUsedForLocalStorage({listType:1,agentId:id,conversationId:''});
+    Storage_setListType(1);
+    Storage_setAgentId(id);
+    Storage_setConversationId('');
     router.push("/dialogue");
   };
 

+ 287 - 109
web/src/app/user-center/page.tsx

@@ -1,14 +1,24 @@
 "use client";
 import { useRef, useState, useEffect } from "react";
-import { message } from "antd";
-import { getUserInfo, userPassword } from "@/api/client";
+import { message, Tabs } from "antd";
+import { css, cx } from "antd-style";
+import { getUserInfo, userPassword, getUserBalance } from "@/api/client";
 import { useGlobalContext } from "@/providers/GlobalProvider";
+import { useTheme } from "next-themes";
+import {
+  RiAiGenerateText,
+  RiAiGenerate2,
+  RiBrushAiLine,
+  RiExchangeCnyLine,
+} from "react-icons/ri";
 
 const UserCenter = () => {
+  const themeBig = useTheme();
   const { changeMenuIndex } = useGlobalContext();
   const [nickName, setNickName] = useState<string>("");
   const [phone, setPhone] = useState<string>("");
   const [email, setEmail] = useState<string>("");
+  const [balance, setBalance] = useState<number>(0);
 
   const [oldPassword, setOldPassword] = useState<string>("");
   const [newPassword, setNewPassword] = useState<string>("");
@@ -16,7 +26,7 @@ const UserCenter = () => {
   const inputRef_nickName = useRef<HTMLInputElement>(null);
   const inputRef_phone = useRef<HTMLInputElement>(null);
   const inputRef_email = useRef<HTMLInputElement>(null);
-  
+
   const inputRef_oldPassword = useRef<HTMLInputElement>(null);
   const inputRef_newPassword = useRef<HTMLInputElement>(null);
 
@@ -37,11 +47,13 @@ const UserCenter = () => {
     e.preventDefault();
     if (oldPassword) {
       if (newPassword) {
-        userPassword({ password: oldPassword, password2: newPassword }).then((data) => {
-          message.success("密码更新成功");
-          setOldPassword("");
-          setNewPassword("");
-        });
+        userPassword({ password: oldPassword, password2: newPassword }).then(
+          (data) => {
+            message.success("密码更新成功");
+            setOldPassword("");
+            setNewPassword("");
+          }
+        );
       } else {
         inputRef_newPassword.current?.focus();
       }
@@ -51,7 +63,9 @@ const UserCenter = () => {
   };
 
   const logout = () => {
-    localStorage.removeItem("chat-last-used");
+    localStorage.removeItem("chat-used-list-type");
+    localStorage.removeItem("chat-used-agent-id");
+    localStorage.removeItem("chat-used-conversation-id");
     localStorage.removeItem("chat-model-list");
     localStorage.removeItem("session");
     message.success("账户退出成功!");
@@ -67,118 +81,282 @@ const UserCenter = () => {
         setNickName(data.data.nickname);
         setPhone(data.data.mobile);
         setEmail(data.data.email);
-      }});
+      }
+    });
+    getUserBalance().then((data) => {
+      if (data.code == 0) {
+        setBalance(data.data.balance);
+      }
+    });
   }, []);
 
   return (
     <>
-      <div className="container px-5 py-7 mx-auto">
-        <div className="w-full border border-slate-100 dark:border-blue-950 rounded-md mb-6 overflow-hidden">
-          <div className="p-3 text-lg border-b-1 border-slate-100 dark:border-slate-900  bg-gray-50 dark:bg-blue-950 text-[#0061ff]">
-            个人信息
-            <span className="text-red-600 float-right cursor-pointer transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 hover:text-red-700 duration-300" onClick={logout}>退出登录</span>
+      <div className="flex flex-col md:flex-row p-5">
+        <div className="w-full md:w-72 flex-none">
+          <h2 className="mb-4">
+            <span className="text-2xl text-[#0061ff]">我的信息</span>
+            <span
+              className="text-sm text-red-700 float-right cursor-pointer bg-gray-200 px-3 py-2 rounded"
+              onClick={logout}
+            >
+              退出登录
+            </span>
+          </h2>
+          <div className="text-center text-sm mt-10">
+            <img
+              className="w-40 h-40 object-cover rounded mx-auto"
+              src="/images/avatar.png"
+              alt="avatar"
+            />
+            <div className="mt-4 text-xl">{nickName}</div>
+            <div className="mt-3 text-gray-400">
+              {email ? email : "extemp@default.com"}
+            </div>
+            <div className="mt-3 text-gray-600 text-left">
+              我是一台基于深度学习和自然语言处理技术的 AI
+              机器人,旨在为用户提供高效、精准、个性化的智能服务。
+            </div>
           </div>
-          <div className="p-5">
-            <div className="flex flex-col items-center justify-center">
-              <img
-                className="w-28 h-28 mb-3 mt-6 rounded-full shadow-lg"
-                src="/images/avatar.png"
-                alt="kakawanzi"
-              />
-              <h5 className="mb-1 text-xl font-medium text-gray-900 dark:text-gray-300">
-                kakawanzi
-              </h5>
-              <div className="container px-5 py-10 mx-auto">
-                <div className="flex lg:w-5/12 w-full flex-col mx-auto px-8 sm:space-x-4 sm:space-y-0 space-y-4 sm:px-0 items-end">
-                  <div className="relative flex-grow w-full">
-                    <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
-                      昵称
-                    </label>
-                    <input
-                      type="text"
-                      autoComplete="new-password"
-                      className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-blue-900 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
-                      ref={inputRef_nickName}
-                      value={nickName}
-                      onChange={(e) => setNickName(e.target.value)}
-                    />
-                  </div>
-                  <div className="relative flex-grow w-full">
-                    <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
-                      手机号码
-                    </label>
-                    <input
-                      type="phone"
-                      autoComplete="new-password"
-                      className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-blue-900 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
-                      ref={inputRef_phone}
-                      value={phone}
-                      onChange={(e) => setPhone(e.target.value)}
-                    />
-                  </div>
-                  <div className="relative flex-grow w-full">
-                    <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
-                      邮箱
-                    </label>
-                    <input
-                      type="email"
-                      autoComplete="new-password"
-                      className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-blue-900 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
-                      ref={inputRef_email}
-                      value={email}
-                      onChange={(e) => setEmail(e.target.value)}
-                    />
-                  </div>
-                  <div className="relative flex-grow w-full pt-6 text-center">
-                    <button className="text-white global-bg-color border-0 py-2 px-12 focus:outline-none hover:bg-indigo-600 rounded-full" onClick={handleSaveUserInfo}>
-                      保存
-                    </button>
-                  </div>
-                </div>
-              </div>
+          <div className="mt-10 text-sm">
+            <div className="text-xl text-[#0061ff]">我的成就</div>
+            <div className="mt-3 pl-3">
+              <span className="text-[#0061ff] pr-3">基础模型使用:</span>
+              {balance} 积分
+            </div>
+            <div className="mt-3 pl-3">
+              <span className="text-[#0061ff] pr-3">高级模型使用:</span>0 积分
+            </div>
+            <div className="mt-3 pl-3">
+              <span className="text-[#0061ff] pr-3">基础模型使用:</span>0 Token
+            </div>
+            <div className="mt-3 pl-3">
+              <span className="text-[#0061ff] pr-3">高级模型使用:</span>0 Token
+            </div>
+            <div className="mt-3 pl-3">
+              <span className="text-[#0061ff] pr-3">专业绘画使用:</span>0 积分
             </div>
           </div>
         </div>
-        <div className="w-full border border-slate-100 dark:border-blue-950 rounded-md mb-6 overflow-hidden">
-          <div className="p-3 text-lg border-b-1 border-slate-100 dark:border-slate-900  bg-gray-50 dark:bg-blue-950 text-[#0061ff]">
-            修改密码
+        <div className="grow mt-10 md:mt-0 md:pl-16">
+          <div className="border-b border-gray-200 dark:border-slate-700 pb-5">
+            <h2 className="text-xl font-bold">个人中心</h2>
+            <div className="text-sm mt-3 dark:text-gray-400">
+              编辑个人信息、查看更多详情
+            </div>
           </div>
-          <div className="p-3">
-            <div className="container px-5 py-10 mx-auto">
-              <div className="flex lg:w-5/12 w-full flex-col mx-auto px-8 sm:space-x-4 sm:space-y-0 space-y-4 sm:px-0 items-end">
-                <div className="relative flex-grow w-full">
-                  <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
-                    旧密码
-                  </label>
-                  <input
-                    type="password"
-                    autoComplete="new-password"
-                    className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-blue-900 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
-                    ref={inputRef_oldPassword}
-                    value={oldPassword}
-                    onChange={(e) => setOldPassword(e.target.value)}
-                  />
+          <div className="mt-6">
+            <Tabs defaultActiveKey="1">
+              <Tabs.TabPane tab="我的积分" key="1">
+                <div className="border border-gray-200 dark:border-slate-700 p-5 rounded-sm">
+                  <div className="text-lg dark:text-white">用户积分余额</div>
+                  <div className="py-5">
+                    <div className="flex flex-wrap -m-2">
+                      <div className="p-2 md:w-1/2 w-full">
+                        <div className="h-full flex items-center border-gray-200 dark:border-slate-700 border p-4 rounded">
+                          <div className="mr-4 text-4xl font-bold text-center">
+                            <RiAiGenerateText className="text-[#0061ff]" />
+                            <p className=" dark:text-white">{balance}</p>
+                          </div>
+                          <div className="flex-grow">
+                            <h2 className="text-[#0061ff] title-font font-medium text-xl">
+                              基础模型余额
+                            </h2>
+                            <p className="text-gray-500 mt-3">
+                              每次对话根据模型消费不同积分!
+                            </p>
+                          </div>
+                        </div>
+                      </div>
+                      <div className="p-2 md:w-1/2 w-full">
+                        <div className="h-full flex items-center border-gray-200 dark:border-slate-700 border p-4 rounded">
+                          <div className="mr-4 text-4xl font-bold text-center">
+                            <RiAiGenerate2 className="text-[#0061ff]" />
+                            <p className=" dark:text-white">{balance}</p>
+                          </div>
+                          <div className="flex-grow">
+                            <h2 className="text-[#0061ff] title-font font-medium text-xl">
+                              高级模型余额
+                            </h2>
+                            <p className="text-gray-500 mt-3">
+                              每次对话根据模型消费不同积分!
+                            </p>
+                          </div>
+                        </div>
+                      </div>
+                      <div className="p-2 md:w-1/2 w-full">
+                        <div className="h-full flex items-center border-gray-200 dark:border-slate-700 border p-4 rounded">
+                          <div className="mr-4 text-4xl font-bold text-center">
+                            <RiBrushAiLine className="text-[#0061ff]" />
+                            <p className=" dark:text-white">{balance}</p>
+                          </div>
+                          <div className="flex-grow">
+                            <h2 className="text-[#0061ff] title-font font-medium text-xl">
+                              专业绘画余额
+                            </h2>
+                            <p className="text-gray-500 mt-3">
+                              根据画图动作消耗不同的积分!
+                            </p>
+                          </div>
+                        </div>
+                      </div>
+                      <div className="p-2 md:w-1/2 w-full">
+                        <div className="h-full flex items-center border-gray-200 dark:border-slate-700 border p-4 rounded">
+                          <div className="mr-4 text-4xl font-bold text-center">
+                            <RiExchangeCnyLine className="text-[#0061ff]" />
+                            <p className=" dark:text-white">{balance}</p>
+                          </div>
+                          <div className="flex-grow">
+                            <h2 className="text-[#0061ff] title-font font-medium text-xl">
+                              会员套餐购买
+                            </h2>
+                            <p className="text-gray-500 mt-3">
+                              <a href="/vip">去购买</a>
+                            </p>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
                 </div>
-                <div className="relative flex-grow w-full">
-                  <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
-                    新密码
-                  </label>
-                  <input
-                    type="password"
-                    autoComplete="new-password"
-                    className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-blue-900 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
-                    ref={inputRef_newPassword}
-                    value={newPassword}
-                    onChange={(e) => setNewPassword(e.target.value)}
-                  />
+                <div className="border border-gray-200 dark:border-slate-700 p-5 rounded-sm mt-6">
+                  <div className="text-lg dark:text-white">充值记录</div>
+                  <div className="py-5">
+                    <table className="border border-slate-200 dark:border-slate-700 w-full text-center">
+                      <thead className="border-b border-slate-200 dark:border-slate-700 bg-gray-100 dark:bg-gray-800 h-12 dark:text-gray-300">
+                        <tr>
+                          <th className="">订单编号</th>
+                          <th className="">充值类型</th>
+                          <th className="">基础模型额度</th>
+                          <th className="">高级模型额度</th>
+                          <th className="">专业绘画额度</th>
+                          <th className="">有效期</th>
+                          <th className="">充值时间</th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        <tr className="h-14 dark:text-gray-300">
+                          <td className="">d3dadc220</td>
+                          <td className="">注册赠送</td>
+                          <td className="">30</td>
+                          <td className="">30</td>
+                          <td className="">0</td>
+                          <td className="">永久</td>
+                          <td className="">2024-12-10 09:50:52</td>
+                        </tr>
+                      </tbody>
+                    </table>
+                  </div>
                 </div>
-                <div className="relative flex-grow w-full pt-6 text-center">
-                  <button className="text-white global-bg-color border-0 py-2 px-12 focus:outline-none hover:bg-indigo-600 rounded-full" onClick={handleUpdatePassword}>
-                    保存
-                  </button>
+              </Tabs.TabPane>
+              <Tabs.TabPane tab="个人信息" className="dark:text-white" key="2">
+                <div className="border border-gray-200 dark:border-slate-700 p-5 rounded-sm">
+                  <div className="text-lg dark:text-white">用户账户信息设置</div>
+                  <div className="py-5">
+                    <div className="flex flex-col items-center justify-center">
+                      <div className="w-full">
+                        <div className="flex w-full flex-col mx-auto px-8 sm:space-x-4 sm:space-y-0 space-y-4 sm:px-0 items-end">
+                          <div className="relative flex-grow w-full">
+                            <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
+                              昵称
+                            </label>
+                            <input
+                              type="text"
+                              autoComplete="new-password"
+                              className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-slate-700 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
+                              ref={inputRef_nickName}
+                              value={nickName}
+                              onChange={(e) => setNickName(e.target.value)}
+                            />
+                          </div>
+                          <div className="relative flex-grow w-full">
+                            <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
+                              手机号码
+                            </label>
+                            <input
+                              type="phone"
+                              autoComplete="new-password"
+                              className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-slate-700 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
+                              ref={inputRef_phone}
+                              value={phone}
+                              onChange={(e) => setPhone(e.target.value)}
+                            />
+                          </div>
+                          <div className="relative flex-grow w-full">
+                            <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
+                              邮箱
+                            </label>
+                            <input
+                              type="email"
+                              autoComplete="new-password"
+                              className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-slate-700 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
+                              ref={inputRef_email}
+                              value={email}
+                              onChange={(e) => setEmail(e.target.value)}
+                            />
+                          </div>
+                          <div className="relative flex-grow w-full pt-6 text-center">
+                            <button
+                              className="text-white global-bg-color border-0 py-2 px-12 focus:outline-none hover:bg-indigo-600 rounded-full"
+                              onClick={handleSaveUserInfo}
+                            >
+                              保存
+                            </button>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
                 </div>
-              </div>
-            </div>
+              </Tabs.TabPane>
+              <Tabs.TabPane tab="账户安全" key="3">
+                <div className="border border-gray-200 dark:border-slate-700 p-5 rounded-sm">
+                  <div className="text-lg dark:text-white">用户账户信息设置</div>
+                  <div className="py-5">
+                    <div className="flex flex-col items-center justify-center">
+                      <div className="w-full">
+                        <div className="flex w-full flex-col mx-auto px-8 sm:space-x-4 sm:space-y-0 space-y-4 sm:px-0 items-end">
+                          <div className="relative flex-grow w-full">
+                            <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
+                              旧密码
+                            </label>
+                            <input
+                              type="password"
+                              autoComplete="new-password"
+                              className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-slate-700 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
+                              ref={inputRef_oldPassword}
+                              value={oldPassword}
+                              onChange={(e) => setOldPassword(e.target.value)}
+                            />
+                          </div>
+                          <div className="relative flex-grow w-full">
+                            <label className="leading-7 text-sm text-gray-600 dark:text-gray-400">
+                              新密码
+                            </label>
+                            <input
+                              type="password"
+                              autoComplete="new-password"
+                              className="w-full bg-gray-50 dark:bg-[#202123] bg-opacity-50 rounded border border-gray-300 dark:border-slate-700 focus:border-blue-950 focus:bg-transparent focus:ring-2 focus:ring-blue-950 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
+                              ref={inputRef_newPassword}
+                              value={newPassword}
+                              onChange={(e) => setNewPassword(e.target.value)}
+                            />
+                          </div>
+                          <div className="relative flex-grow w-full pt-6 text-center">
+                            <button
+                              className="text-white global-bg-color border-0 py-2 px-12 focus:outline-none hover:bg-indigo-600 rounded-full"
+                              onClick={handleUpdatePassword}
+                            >
+                              保存
+                            </button>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </Tabs.TabPane>
+            </Tabs>
           </div>
         </div>
       </div>

+ 1 - 1
web/src/components/Menu/index.tsx

@@ -60,7 +60,7 @@ export default function Header() {
           <p className="text-sm text-center py-1">帮助</p>
         </div>
       </div>
-      <div className="flex flex-col items-center space-4 select-none absolute inset-x-0 bottom-0">
+      <div className="flex flex-col items-center space-4 select-none inset-x-0 mt-20">
         <div
           className={`w-full my-2 py-3 rounded-md text-center text-gray-900 dark:text-gray-500 bg-gray-50 dark:bg-gray-800 border border-slate-200 dark:border-slate-700`}
         >

+ 9 - 27
web/src/providers/GlobalProvider.tsx

@@ -1,6 +1,11 @@
 "use client";
-import React, { createContext, useState, useContext, ReactNode,useEffect } from "react";
-import { GetChatLastUsedForLocalStorage } from "@/utils/chat";
+import React, {
+  createContext,
+  useState,
+  useContext,
+  ReactNode,
+  useEffect,
+} from "react";
 
 const GlobalContext = createContext<any>(null);
 
@@ -12,31 +17,14 @@ export const GlobalProvider: React.FC<GlobalProviderProps> = ({ children }) => {
   const [menuIndex, setMenuIndex] = useState<number>(0);
   const [loginShow, setLoginShow] = useState<boolean>(false);
 
-  const [listType, setListType] = useState<number>(0); //0会话历史 1助手列表
-  const [agentId, setAgentId] = useState<number>(0); //助手ID
-  const [conversationId, setConversationId] = useState<string>(""); //会话ID
-
   const changeMenuIndex = (index: number) => {
     setMenuIndex(index);
   };
   const changeLoginShow = (show: boolean) => {
     setLoginShow(show);
   };
-  const changeListType = (listType: number) => {
-    setListType(listType);
-  };
-  const changeAgentId = (agentId: number) => {
-    setAgentId(agentId);
-  };
-  const changeConversationId = (conversationId: string) => {
-    setConversationId(conversationId);
-  };
-
+  
   useEffect(() => {
-    var lastUsed = GetChatLastUsedForLocalStorage();
-    setListType(lastUsed.listType);
-    setAgentId(lastUsed.agentId);
-    setConversationId(lastUsed.conversationId);
   }, []);
 
   return (
@@ -45,13 +33,7 @@ export const GlobalProvider: React.FC<GlobalProviderProps> = ({ children }) => {
         menuIndex,
         changeMenuIndex,
         loginShow,
-        changeLoginShow,
-        listType,
-        changeListType,
-        agentId,
-        changeAgentId,
-        conversationId,
-        changeConversationId,
+        changeLoginShow
       }}
     >
       {children}

+ 1 - 1
web/src/providers/ThemeProvider.tsx

@@ -20,7 +20,7 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
             style={{ height: "calc(100vh - 3.5rem)" }}
           >
             <div
-              className="shadow-md mx-2 bg-[#ffffff] dark:bg-[#202123] rounded-md p-2"
+              className="shadow-md mx-2 bg-[#ffffff] dark:bg-[#202123] rounded-md p-2  overflow-auto scroll-smooth"
               style={{ height: "calc(100vh - 5rem)", width: "7rem" }}
             >
               <Menu/>

+ 65 - 30
web/src/utils/chat.ts

@@ -138,40 +138,75 @@ export const GetLocalChatModelDetails = (id:number): ChatAiModel => {
     return {id:0,name:"",avatar:"",estimate:"",createdAt:0,ConversationId:"" };
 }
 
-const STORAGE_KEY_CHAT_LAST_USED = 'chat-last-used';
-/**
- * 本地存储 - 上次使用助手设置
- * 
- * @interface ChatLastUsed
- * @property {number} listType - 助手列表类型 0历史列表 1助手列表
- * @property {number} agentId - 智能体ID
- * @property {string} ConversationId - 会话ID
- */
-export interface ChatLastUsed {
-    listType: number;
-    agentId: number;
-    conversationId: string;
-}
-export const DEFAULT_CHATLASTUSED = {
-    listType: 0,
-    agentId: 0,
-    conversationId: '',
-};
-
-export const SetChatLastUsedForLocalStorage = (value: ChatLastUsed) => {
-    localStorage.setItem(STORAGE_KEY_CHAT_LAST_USED, JSON.stringify(value));
+export const DeleteLocalChatModelDetails = (id:number): ChatAiModel[] => {
+    var list = GetChatModelListForLocalStorage();
+    if (list !== null) {
+        var index = list.findIndex((v) => v.id == id);
+        if (index >= 0 ) {
+            list.splice(index, 1);
+        }
+    } 
+    SetChatModelListForLocalStorage(list);
+
+    if(id == Storage_getAgentId()){
+        if(list!=null && list.length > 0){
+            Storage_setListType(1);
+            Storage_setAgentId(list[0].id);
+            Storage_setConversationId(list[0].ConversationId);
+        }
+        else{
+            Storage_setListType(1);
+            Storage_setAgentId(0);
+            Storage_setConversationId('');
+        }
+    }
+    return list;
 }
 
-export const GetChatLastUsedForLocalStorage = (): ChatLastUsed => {
-    let model = DEFAULT_CHATLASTUSED;
-    if (typeof localStorage === 'undefined') return model;
-    const modelJson = localStorage.getItem(STORAGE_KEY_CHAT_LAST_USED);
-    if (modelJson) {
-        let savedModel = JSON.parse(modelJson) as ChatLastUsed;
-        model = Object.assign(model, savedModel);
+
+//存储智能体
+const STORAGE_KEY_CHAT_USED_LIST_TYPE = 'chat-used-list-type';
+const STORAGE_KEY_CHAT_USED_AGENT_ID = 'chat-used-agent-id';
+const STORAGE_KEY_CHAT_USED_CONVERSATION_ID = 'chat-used-conversation-id';
+
+export const Storage_setListType = (value: number) => {
+    localStorage.setItem(STORAGE_KEY_CHAT_USED_LIST_TYPE, JSON.stringify(value));
+}
+export const Storage_getListType = () : number => {
+    let value = 0;
+    if (typeof localStorage === 'undefined') return value;
+    const data = localStorage.getItem(STORAGE_KEY_CHAT_USED_LIST_TYPE);
+    if (data) {
+        value = parseInt(data ?? 0);
+    }
+    return value;
+}
+export const Storage_setAgentId = (value: number) => {
+    localStorage.setItem(STORAGE_KEY_CHAT_USED_AGENT_ID, value.toString());
+}
+export const Storage_getAgentId = () : number => {
+    let value = 0;
+    if (typeof localStorage === 'undefined') return value;
+    const data = localStorage.getItem(STORAGE_KEY_CHAT_USED_AGENT_ID);
+    if (data) {
+        value = parseInt(data ?? 0);
     }
-    return model;
+    return value;
 }
+export const Storage_setConversationId = (value: string) => {
+    localStorage.setItem(STORAGE_KEY_CHAT_USED_CONVERSATION_ID, value);
+}
+export const Storage_getConversationId = () : string => {
+    let value = '';
+    if (typeof localStorage === 'undefined') return value;
+    const data = localStorage.getItem(STORAGE_KEY_CHAT_USED_CONVERSATION_ID);
+    if (data) {
+        value = data;
+    }
+    return value;
+}
+
+
 
 
 

+ 11 - 0
web/src/utils/clientsApis.ts

@@ -179,6 +179,17 @@ export interface UserInfoResult {
 }
 
 /**
+ * 获取用户积分响应结果
+ * 
+ * @interface UserBalanceResult
+ * @property {number} balance - 积分
+ */
+export interface UserBalanceResult {
+    balance: number;
+}
+
+
+/**
  * 聊天提交内容请求参数
  * 
  * @interface ChatSubmitParams

Неке датотеке нису приказане због велике количине промена