Browse Source

Merge pull request #2309 from Yidadaa/bugfix-0709

feat: #2308 #2294 #2295
Yifei Zhang 1 year ago
parent
commit
9b61fe9df2
9 changed files with 148 additions and 131 deletions
  1. 4 0
      README.md
  2. 33 34
      app/components/chat.module.scss
  3. 76 83
      app/components/chat.tsx
  4. 2 1
      app/locales/cn.ts
  5. 2 1
      app/locales/en.ts
  6. 13 12
      app/store/config.ts
  7. 12 0
      docs/translation.md
  8. 1 0
      package.json
  9. 5 0
      yarn.lock

+ 4 - 0
README.md

@@ -263,6 +263,10 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s
 
 ![More](./docs/images/more.png)
 
+## Translation
+
+If you want to add a new translation, read this [document](./docs/translation.md).
+
 ## Donation
 
 [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)

+ 33 - 34
app/components/chat.module.scss

@@ -240,24 +240,39 @@
   &:last-child {
     animation: slide-in ease 0.3s;
   }
+}
 
-  &:hover {
-    .chat-message-actions {
-      opacity: 1;
-      transform: translateY(0px);
-      max-width: 100%;
-      height: 40px;
-    }
+.chat-message-user {
+  display: flex;
+  flex-direction: row-reverse;
 
-    .chat-message-action-date {
-      opacity: 0.2;
-    }
+  .chat-message-header {
+    flex-direction: row-reverse;
   }
 }
 
-.chat-message-user {
+.chat-message-header {
+  margin-top: 20px;
   display: flex;
-  flex-direction: row-reverse;
+  align-items: center;
+
+  .chat-message-actions {
+    display: flex;
+    box-sizing: border-box;
+    font-size: 12px;
+    align-items: flex-end;
+    justify-content: space-between;
+    transition: all ease 0.3s;
+    transform: scale(0.9) translateY(5px);
+    margin: 0 10px;
+    opacity: 0;
+    pointer-events: none;
+
+    .chat-input-actions {
+      display: flex;
+      flex-wrap: nowrap;
+    }
+  }
 }
 
 .chat-message-container {
@@ -270,6 +285,12 @@
     .chat-message-edit {
       opacity: 0.9;
     }
+
+    .chat-message-actions {
+      opacity: 1;
+      pointer-events: all;
+      transform: scale(1) translateY(0);
+    }
   }
 }
 
@@ -278,7 +299,6 @@
 }
 
 .chat-message-avatar {
-  margin-top: 20px;
   position: relative;
 
   .chat-message-edit {
@@ -318,27 +338,6 @@
   border: var(--border-in-light);
   position: relative;
   transition: all ease 0.3s;
-
-  .chat-message-actions {
-    display: flex;
-    box-sizing: border-box;
-    font-size: 12px;
-    align-items: flex-end;
-    justify-content: space-between;
-    transition: all ease 0.3s 0.15s;
-    transform: translateX(-5px) scale(0.9) translateY(30px);
-    opacity: 0;
-    height: 0;
-    max-width: 0;
-    position: absolute;
-    left: 0;
-    z-index: 2;
-
-    .chat-input-actions {
-      display: flex;
-      flex-wrap: nowrap;
-    }
-  }
 }
 
 .chat-message-action-date {

+ 76 - 83
app/components/chat.tsx

@@ -708,49 +708,46 @@ export function Chat() {
     let lastUserMessageIndex: number | null = null;
     for (let i = 0; i < session.messages.length; i += 1) {
       const message = session.messages[i];
-      if (message.id === messageId) {
-        break;
-      }
       if (message.role === "user") {
         lastUserMessageIndex = i;
       }
+      if (message.id === messageId) {
+        break;
+      }
     }
 
     return lastUserMessageIndex;
   };
 
-  const deleteMessage = (userIndex: number) => {
-    chatStore.updateCurrentSession((session) =>
-      session.messages.splice(userIndex, 2),
+  const deleteMessage = (msgId?: number) => {
+    chatStore.updateCurrentSession(
+      (session) =>
+        (session.messages = session.messages.filter((m) => m.id !== msgId)),
     );
   };
 
-  const onDelete = (botMessageId: number) => {
-    const userIndex = findLastUserIndex(botMessageId);
-    if (userIndex === null) return;
-    deleteMessage(userIndex);
+  const onDelete = (msgId: number) => {
+    deleteMessage(msgId);
   };
 
-  const onResend = (botMessageId: number) => {
-    // find last user input message and resend
-    const userIndex = findLastUserIndex(botMessageId);
-    if (userIndex === null) return;
+  const onResend = (message: ChatMessage) => {
+    let content = message.content;
+
+    if (message.role === "assistant" && message.id) {
+      const userIndex = findLastUserIndex(message.id);
+      if (userIndex) {
+        content = session.messages.at(userIndex)?.content ?? content;
+      }
+    }
 
     setIsLoading(true);
-    const content = session.messages[userIndex].content;
-    deleteMessage(userIndex);
     chatStore.onUserInput(content).then(() => setIsLoading(false));
     inputRef.current?.focus();
   };
 
-  const onPinMessage = (botMessage: ChatMessage) => {
-    if (!botMessage.id) return;
-    const userMessageIndex = findLastUserIndex(botMessage.id);
-    if (userMessageIndex === null) return;
-
-    const userMessage = session.messages[userMessageIndex];
+  const onPinMessage = (message: ChatMessage) => {
     chatStore.updateCurrentSession((session) =>
-      session.mask.context.push(userMessage, botMessage),
+      session.mask.context.push(message),
     );
 
     showToast(Locale.Chat.Actions.PinToastContent, {
@@ -923,11 +920,11 @@ export function Chat() {
       >
         {messages.map((message, i) => {
           const isUser = message.role === "user";
+          const isContext = i < context.length;
           const showActions =
-            !isUser &&
             i > 0 &&
             !(message.preview || message.content.length === 0) &&
-            i >= context.length; // do not show actions for context prompts
+            !isContext;
           const showTyping = message.preview || message.streaming;
 
           const shouldShowClearContextDivider = i === clearContextIndex - 1;
@@ -941,64 +938,38 @@ export function Chat() {
                 }
               >
                 <div className={styles["chat-message-container"]}>
-                  <div className={styles["chat-message-avatar"]}>
-                    <div className={styles["chat-message-edit"]}>
-                      <IconButton
-                        icon={<EditIcon />}
-                        onClick={async () => {
-                          const newMessage = await showPrompt(
-                            Locale.Chat.Actions.Edit,
-                            message.content,
-                            10,
-                          );
-                          chatStore.updateCurrentSession((session) => {
-                            const m = session.messages.find(
-                              (m) => m.id === message.id,
+                  <div className={styles["chat-message-header"]}>
+                    <div className={styles["chat-message-avatar"]}>
+                      <div className={styles["chat-message-edit"]}>
+                        <IconButton
+                          icon={<EditIcon />}
+                          onClick={async () => {
+                            const newMessage = await showPrompt(
+                              Locale.Chat.Actions.Edit,
+                              message.content,
+                              10,
                             );
-                            if (m) {
-                              m.content = newMessage;
-                            }
-                          });
-                        }}
-                      ></IconButton>
-                    </div>
-                    {isUser ? (
-                      <Avatar avatar={config.avatar} />
-                    ) : (
-                      <MaskAvatar mask={session.mask} />
-                    )}
-                  </div>
-                  {showTyping && (
-                    <div className={styles["chat-message-status"]}>
-                      {Locale.Chat.Typing}
+                            chatStore.updateCurrentSession((session) => {
+                              const m = session.messages.find(
+                                (m) => m.id === message.id,
+                              );
+                              if (m) {
+                                m.content = newMessage;
+                              }
+                            });
+                          }}
+                        ></IconButton>
+                      </div>
+                      {isUser ? (
+                        <Avatar avatar={config.avatar} />
+                      ) : (
+                        <MaskAvatar mask={session.mask} />
+                      )}
                     </div>
-                  )}
-                  <div className={styles["chat-message-item"]}>
-                    <Markdown
-                      content={message.content}
-                      loading={
-                        (message.preview || message.content.length === 0) &&
-                        !isUser
-                      }
-                      onContextMenu={(e) => onRightClick(e, message)}
-                      onDoubleClickCapture={() => {
-                        if (!isMobileScreen) return;
-                        setUserInput(message.content);
-                      }}
-                      fontSize={fontSize}
-                      parentRef={scrollRef}
-                      defaultShow={i >= messages.length - 10}
-                    />
 
                     {showActions && (
                       <div className={styles["chat-message-actions"]}>
-                        <div
-                          className={styles["chat-input-actions"]}
-                          style={{
-                            marginTop: 10,
-                            marginBottom: 0,
-                          }}
-                        >
+                        <div className={styles["chat-input-actions"]}>
                           {message.streaming ? (
                             <ChatAction
                               text={Locale.Chat.Actions.Stop}
@@ -1010,7 +981,7 @@ export function Chat() {
                               <ChatAction
                                 text={Locale.Chat.Actions.Retry}
                                 icon={<ResetIcon />}
-                                onClick={() => onResend(message.id ?? i)}
+                                onClick={() => onResend(message)}
                               />
 
                               <ChatAction
@@ -1035,12 +1006,34 @@ export function Chat() {
                       </div>
                     )}
                   </div>
-
-                  {showActions && (
-                    <div className={styles["chat-message-action-date"]}>
-                      {message.date.toLocaleString()}
+                  {showTyping && (
+                    <div className={styles["chat-message-status"]}>
+                      {Locale.Chat.Typing}
                     </div>
                   )}
+                  <div className={styles["chat-message-item"]}>
+                    <Markdown
+                      content={message.content}
+                      loading={
+                        (message.preview || message.content.length === 0) &&
+                        !isUser
+                      }
+                      onContextMenu={(e) => onRightClick(e, message)}
+                      onDoubleClickCapture={() => {
+                        if (!isMobileScreen) return;
+                        setUserInput(message.content);
+                      }}
+                      fontSize={fontSize}
+                      parentRef={scrollRef}
+                      defaultShow={i >= messages.length - 10}
+                    />
+                  </div>
+
+                  <div className={styles["chat-message-action-date"]}>
+                    {isContext
+                      ? Locale.Chat.IsContext
+                      : message.date.toLocaleString()}
+                  </div>
                 </div>
               </div>
               {shouldShowClearContextDivider && <ClearContextDivider />}

+ 2 - 1
app/locales/cn.ts

@@ -26,7 +26,7 @@ const cn = {
       Stop: "停止",
       Retry: "重试",
       Pin: "固定",
-      PinToastContent: "已将 2 条对话固定至预设提示词",
+      PinToastContent: "已将 1 条对话固定至预设提示词",
       PinToastAction: "查看",
       Delete: "删除",
       Edit: "编辑",
@@ -66,6 +66,7 @@ const cn = {
       Reset: "清除记忆",
       SaveAs: "存为面具",
     },
+    IsContext: "预设提示词",
   },
   Export: {
     Title: "分享聊天记录",

+ 2 - 1
app/locales/en.ts

@@ -28,7 +28,7 @@ const en: LocaleType = {
       Stop: "Stop",
       Retry: "Retry",
       Pin: "Pin",
-      PinToastContent: "Pinned 2 messages to contextual prompts",
+      PinToastContent: "Pinned 1 messages to contextual prompts",
       PinToastAction: "View",
       Delete: "Delete",
       Edit: "Edit",
@@ -68,6 +68,7 @@ const en: LocaleType = {
       Reset: "Reset to Default",
       SaveAs: "Save as Mask",
     },
+    IsContext: "Contextual Prompt",
   },
   Export: {
     Title: "Export Messages",

+ 13 - 12
app/store/config.ts

@@ -139,19 +139,20 @@ export const useAppConfig = create<ChatConfigStore>()(
       name: StoreKey.Config,
       version: 3.4,
       migrate(persistedState, version) {
-        if (version === 3.4) return persistedState as any;
-
         const state = persistedState as ChatConfig;
-        state.modelConfig.sendMemory = true;
-        state.modelConfig.historyMessageCount = 4;
-        state.modelConfig.compressMessageLengthThreshold = 1000;
-        state.modelConfig.frequency_penalty = 0;
-        state.modelConfig.top_p = 1;
-        state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
-        state.dontShowMaskSplashScreen = false;
-        state.hideBuiltinMasks = false;
-
-        return state;
+
+        if (version < 3.4) {
+          state.modelConfig.sendMemory = true;
+          state.modelConfig.historyMessageCount = 4;
+          state.modelConfig.compressMessageLengthThreshold = 1000;
+          state.modelConfig.frequency_penalty = 0;
+          state.modelConfig.top_p = 1;
+          state.modelConfig.template = DEFAULT_INPUT_TEMPLATE;
+          state.dontShowMaskSplashScreen = false;
+          state.hideBuiltinMasks = false;
+        }
+
+        return state as any;
       },
     },
   ),

+ 12 - 0
docs/translation.md

@@ -0,0 +1,12 @@
+# How to add a new translation?
+
+Assume that we are adding a new translation for `new`.
+
+1. copy `app/locales/en.ts` to `app/locales/new.ts`;
+2. edit `new.ts`, change `const en: LocaleType = ` to `const new: PartialLocaleType`, and `export default new;`;
+3. edit `app/locales/index.ts`:
+4. `import new from './new.ts'`;
+5. add `new` to `ALL_LANGS`;
+6. add `new: "new lang"` to `ALL_LANG_OPTIONS`;
+7. translate the strings in `new.ts`;
+8. submit a pull request, and the author will merge it.

+ 1 - 0
package.json

@@ -24,6 +24,7 @@
     "fuse.js": "^6.6.2",
     "html-to-image": "^1.11.11",
     "mermaid": "^10.2.3",
+    "nanoid": "^4.0.2",
     "next": "^13.4.6",
     "node-fetch": "^3.3.1",
     "react": "^18.2.0",

+ 5 - 0
yarn.lock

@@ -4639,6 +4639,11 @@ nanoid@^3.3.4:
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
   integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
 
+nanoid@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
+  integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
+
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"