Browse Source

Merge branch 'Yidadaa:main' into main

Hopelwj 1 year ago
parent
commit
b9895ecadd

+ 14 - 0
.babelrc

@@ -0,0 +1,14 @@
+{
+  "presets": [
+    [
+      "next/babel",
+      {
+        "preset-env": {
+          "targets": {
+            "browsers": ["> 0.25%, not dead"]
+          }
+        }
+      }
+    ]
+  ]
+}

+ 1 - 1
README.md

@@ -14,7 +14,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
 [![MacOS][MacOS-image]][download-url]
 [![Linux][Linux-image]][download-url]
 
-[Web App](https://chatgpt.nextweb.fun/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
+[Web App](https://chatgpt.nextweb.fun/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discord](https://discord.gg/YCkeafCafC) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
 
 [网页版](https://chatgpt.nextweb.fun/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
 

+ 2 - 2
app/api/common.ts

@@ -2,8 +2,8 @@ import { NextRequest, NextResponse } from "next/server";
 
 export const OPENAI_URL = "api.openai.com";
 const DEFAULT_PROTOCOL = "https";
-const PROTOCOL = process.env.PROTOCOL ?? DEFAULT_PROTOCOL;
-const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
+const PROTOCOL = process.env.PROTOCOL || DEFAULT_PROTOCOL;
+const BASE_URL = process.env.BASE_URL || OPENAI_URL;
 const DISABLE_GPT4 = !!process.env.DISABLE_GPT4;
 
 export async function requestOpenai(req: NextRequest) {

+ 1 - 1
app/client/api.ts

@@ -99,7 +99,7 @@ export class ClientApi {
     // 敬告二开开发者们,为了开源大模型的发展,请不要修改上述消息,此消息用于后续数据清洗使用
     // Please do not modify this message
 
-    console.log("[Share]", msgs);
+    console.log("[Share]", messages, msgs);
     const clientConfig = getClientConfig();
     const proxyUrl = "/sharegpt";
     const rawUrl = "https://sharegpt.com/api/conversations";

+ 13 - 0
app/components/chat.module.scss

@@ -101,6 +101,19 @@
     width: 100%;
     margin-bottom: 10px;
 
+    &:hover {
+      .context-drag {
+        opacity: 1;
+      }
+    }
+
+    .context-drag {
+      display: flex;
+      align-items: center;
+      opacity: 0.5;
+      transition: all ease 0.3s;
+    }
+
     .context-role {
       margin-right: 10px;
     }

+ 3 - 3
app/components/chat.tsx

@@ -5,6 +5,7 @@ import React, {
   useEffect,
   useMemo,
   useCallback,
+  Fragment,
 } from "react";
 
 import SendWhiteIcon from "../icons/send-white.svg";
@@ -975,9 +976,8 @@ export function Chat() {
           const shouldShowClearContextDivider = i === clearContextIndex - 1;
 
           return (
-            <>
+            <Fragment key={i}>
               <div
-                key={i}
                 className={
                   isUser ? styles["chat-message-user"] : styles["chat-message"]
                 }
@@ -1082,7 +1082,7 @@ export function Chat() {
                 </div>
               </div>
               {shouldShowClearContextDivider && <ClearContextDivider />}
-            </>
+            </Fragment>
           );
         })}
       </div>

+ 27 - 3
app/components/exporter.tsx

@@ -8,6 +8,7 @@ import {
   Modal,
   Select,
   showImageModal,
+  showModal,
   showToast,
 } from "./ui-lib";
 import { IconButton } from "./button";
@@ -244,11 +245,11 @@ export function RenderExport(props: {
     }
 
     const renderMsgs = messages.map((v, i) => {
-      const [_, role] = v.id.split(":");
+      const [role, _] = v.id.split(":");
       return {
         id: i.toString(),
         role: role as any,
-        content: v.innerHTML,
+        content: role === "user" ? v.textContent ?? "" : v.innerHTML,
         date: "",
       };
     });
@@ -287,7 +288,30 @@ export function PreviewActions(props: {
       .share(msgs)
       .then((res) => {
         if (!res) return;
-        copyToClipboard(res);
+        showModal({
+          title: Locale.Export.Share,
+          children: [
+            <input
+              type="text"
+              value={res}
+              key="input"
+              style={{
+                width: "100%",
+                maxWidth: "unset",
+              }}
+              readOnly
+              onClick={(e) => e.currentTarget.select()}
+            ></input>,
+          ],
+          actions: [
+            <IconButton
+              icon={<CopyIcon />}
+              text={Locale.Chat.Actions.Copy}
+              key="copy"
+              onClick={() => copyToClipboard(res)}
+            />,
+          ],
+        });
         setTimeout(() => {
           window.open(res, "_blank");
         }, 800);

+ 18 - 6
app/components/home.module.scss

@@ -61,24 +61,36 @@
       }
     }
   }
+
+  &:hover,
+  &:active {
+    .sidebar-drag {
+      background-color: rgba($color: #000000, $alpha: 0.01);
+
+      svg {
+        opacity: 0.2;
+      }
+    }
+  }
 }
 
 .sidebar-drag {
-  $width: 10px;
+  $width: 14px;
 
   position: absolute;
   top: 0;
   right: 0;
   height: 100%;
   width: $width;
-  background-color: var(--black);
+  background-color: rgba($color: #000000, $alpha: 0);
   cursor: ew-resize;
-  opacity: 0;
   transition: all ease 0.3s;
+  display: flex;
+  align-items: center;
 
-  &:hover,
-  &:active {
-    opacity: 0.2;
+  svg {
+    opacity: 0;
+    margin-left: -2px;
   }
 }
 

+ 107 - 53
app/components/mask.tsx

@@ -11,6 +11,7 @@ import CloseIcon from "../icons/close.svg";
 import DeleteIcon from "../icons/delete.svg";
 import EyeIcon from "../icons/eye.svg";
 import CopyIcon from "../icons/copy.svg";
+import DragIcon from "../icons/drag.svg";
 
 import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
 import {
@@ -42,6 +43,20 @@ import { ModelConfigList } from "./model-config";
 import { FileName, Path } from "../constant";
 import { BUILTIN_MASK_STORE } from "../masks";
 import { nanoid } from "nanoid";
+import {
+  DragDropContext,
+  Droppable,
+  Draggable,
+  OnDragEndResponder,
+} from "@hello-pangea/dnd";
+
+// drag and drop helper function
+function reorder<T>(list: T[], startIndex: number, endIndex: number): T[] {
+  const result = [...list];
+  const [removed] = result.splice(startIndex, 1);
+  result.splice(endIndex, 0, removed);
+  return result;
+}
 
 export function MaskAvatar(props: { mask: Mask }) {
   return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
@@ -192,6 +207,7 @@ export function MaskConfig(props: {
 }
 
 function ContextPromptItem(props: {
+  index: number;
   prompt: ChatMessage;
   update: (prompt: ChatMessage) => void;
   remove: () => void;
@@ -199,53 +215,67 @@ function ContextPromptItem(props: {
   const [focusingInput, setFocusingInput] = useState(false);
 
   return (
-    <div className={chatStyle["context-prompt-row"]}>
-      {!focusingInput && (
-        <Select
-          value={props.prompt.role}
-          className={chatStyle["context-role"]}
-          onChange={(e) =>
-            props.update({
-              ...props.prompt,
-              role: e.target.value as any,
-            })
-          }
+    <Draggable draggableId={props.prompt.id || props.index.toString()} index={props.index}>
+      {(provided) => (
+        <div
+          className={chatStyle["context-prompt-row"]}
+          ref={provided.innerRef}
+          {...provided.draggableProps}
+          {...provided.dragHandleProps}
         >
-          {ROLES.map((r) => (
-            <option key={r} value={r}>
-              {r}
-            </option>
-          ))}
-        </Select>
-      )}
-      <Input
-        value={props.prompt.content}
-        type="text"
-        className={chatStyle["context-content"]}
-        rows={focusingInput ? 5 : 1}
-        onFocus={() => setFocusingInput(true)}
-        onBlur={() => {
-          setFocusingInput(false);
-          // If the selection is not removed when the user loses focus, some
-          // extensions like "Translate" will always display a floating bar
-          window?.getSelection()?.removeAllRanges();
-        }}
-        onInput={(e) =>
-          props.update({
-            ...props.prompt,
-            content: e.currentTarget.value as any,
-          })
-        }
-      />
-      {!focusingInput && (
-        <IconButton
-          icon={<DeleteIcon />}
-          className={chatStyle["context-delete-button"]}
-          onClick={() => props.remove()}
-          bordered
-        />
+          {!focusingInput && (
+            <>
+              <div className={chatStyle["context-drag"]}>
+                <DragIcon />
+              </div>
+              <Select
+                value={props.prompt.role}
+                className={chatStyle["context-role"]}
+                onChange={(e) =>
+                  props.update({
+                    ...props.prompt,
+                    role: e.target.value as any,
+                  })
+                }
+              >
+                {ROLES.map((r) => (
+                  <option key={r} value={r}>
+                    {r}
+                  </option>
+                ))}
+              </Select>
+            </>
+          )}
+          <Input
+            value={props.prompt.content}
+            type="text"
+            className={chatStyle["context-content"]}
+            rows={focusingInput ? 5 : 1}
+            onFocus={() => setFocusingInput(true)}
+            onBlur={() => {
+              setFocusingInput(false);
+              // If the selection is not removed when the user loses focus, some
+              // extensions like "Translate" will always display a floating bar
+              window?.getSelection()?.removeAllRanges();
+            }}
+            onInput={(e) =>
+              props.update({
+                ...props.prompt,
+                content: e.currentTarget.value as any,
+              })
+            }
+          />
+          {!focusingInput && (
+            <IconButton
+              icon={<DeleteIcon />}
+              className={chatStyle["context-delete-button"]}
+              onClick={() => props.remove()}
+              bordered
+            />
+          )}
+        </div>
       )}
-    </div>
+    </Draggable>
   );
 }
 
@@ -267,17 +297,41 @@ export function ContextPrompts(props: {
     props.updateContext((context) => (context[i] = prompt));
   };
 
+  const onDragEnd: OnDragEndResponder = (result) => {
+    if (!result.destination) {
+      return;
+    }
+    const newContext = reorder(
+      context,
+      result.source.index,
+      result.destination.index,
+    );
+    props.updateContext((context) => {
+      context.splice(0, context.length, ...newContext);
+    });
+  };
+
   return (
     <>
       <div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
-        {context.map((c, i) => (
-          <ContextPromptItem
-            key={i}
-            prompt={c}
-            update={(prompt) => updateContextPrompt(i, prompt)}
-            remove={() => removeContextPrompt(i)}
-          />
-        ))}
+        <DragDropContext onDragEnd={onDragEnd}>
+          <Droppable droppableId="context-prompt-list">
+            {(provided) => (
+              <div ref={provided.innerRef} {...provided.droppableProps}>
+                {context.map((c, i) => (
+                  <ContextPromptItem
+                    index={i}
+                    key={c.id}
+                    prompt={c}
+                    update={(prompt) => updateContextPrompt(i, prompt)}
+                    remove={() => removeContextPrompt(i)}
+                  />
+                ))}
+                {provided.placeholder}
+              </div>
+            )}
+          </Droppable>
+        </DragDropContext>
 
         <div className={chatStyle["context-prompt-row"]}>
           <IconButton

+ 4 - 1
app/components/sidebar.tsx

@@ -10,6 +10,7 @@ import AddIcon from "../icons/add.svg";
 import CloseIcon from "../icons/close.svg";
 import MaskIcon from "../icons/mask.svg";
 import PluginIcon from "../icons/plugin.svg";
+import DragIcon from "../icons/drag.svg";
 
 import Locale from "../locales";
 
@@ -198,7 +199,9 @@ export function SideBar(props: { className?: string }) {
       <div
         className={styles["sidebar-drag"]}
         onMouseDown={(e) => onDragMouseDown(e as any)}
-      ></div>
+      >
+        <DragIcon />
+      </div>
     </div>
   );
 }

+ 1 - 0
app/icons/drag.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1"  transform="translate(0 0)  rotate(0)"><mask id="bg-mask-0" fill="white"><use transform="translate(0 0)  rotate(0)" xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" fill-rule="evenodd" style="fill:#333333" opacity="1" d="M6.33663,3.33c0,0.74 -0.6,1.34 -1.34,1.34c-0.73,0 -1.33,-0.6 -1.33,-1.34c0,-0.73 0.6,-1.33 1.33,-1.33c0.74,0 1.34,0.6 1.34,1.33zM4.99663,9.33c-0.73,0 -1.33,-0.59 -1.33,-1.33c0,-0.74 0.6,-1.33 1.33,-1.33c0.74,0 1.34,0.59 1.34,1.33c0,0.74 -0.6,1.33 -1.34,1.33zM4.99663,14c-0.73,0 -1.33,-0.6 -1.33,-1.33c0,-0.74 0.6,-1.34 1.33,-1.34c0.74,0 1.34,0.6 1.34,1.34c0,0.73 -0.6,1.33 -1.34,1.33z"></path><path id="路径 2" fill-rule="evenodd" style="fill:#333333" opacity="1" d="M12.3366,3.33c0,0.74 -0.6,1.34 -1.34,1.34c-0.73,0 -1.32997,-0.6 -1.32997,-1.34c0,-0.73 0.59997,-1.33 1.32997,-1.33c0.74,0 1.34,0.6 1.34,1.33zM10.9966,9.33c-0.73,0 -1.32997,-0.59 -1.32997,-1.33c0,-0.74 0.59997,-1.33 1.32997,-1.33c0.74,0 1.34,0.59 1.34,1.33c0,0.74 -0.6,1.33 -1.34,1.33zM10.9966,14c-0.73,0 -1.32997,-0.6 -1.32997,-1.33c0,-0.74 0.59997,-1.34 1.32997,-1.34c0.74,0 1.34,0.6 1.34,1.34c0,0.73 -0.6,1.33 -1.34,1.33z"></path></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>

+ 2 - 1
app/layout.tsx

@@ -3,8 +3,9 @@ import "./styles/globals.scss";
 import "./styles/markdown.scss";
 import "./styles/highlight.scss";
 import { getClientConfig } from "./config/client";
+import { type Metadata } from 'next';
 
-export const metadata = {
+export const metadata: Metadata = {
   title: "ChatGPT Next Web",
   description: "Your personal ChatGPT Chat Bot.",
   viewport: {

+ 18 - 1
app/store/chat.ts

@@ -589,7 +589,7 @@ export const useChatStore = create<ChatStore>()(
     }),
     {
       name: StoreKey.Chat,
-      version: 3,
+      version: 3.1,
       migrate(persistedState, version) {
         const state = persistedState as any;
         const newState = JSON.parse(JSON.stringify(state)) as ChatStore;
@@ -617,6 +617,23 @@ export const useChatStore = create<ChatStore>()(
           });
         }
 
+        // Enable `enableInjectSystemPrompts` attribute for old sessions.
+        // Resolve issue of old sessions not automatically enabling.
+        if (version < 3.1) {
+          newState.sessions.forEach((s) => {
+            if (
+              // Exclude those already set by user
+              !s.mask.modelConfig.hasOwnProperty("enableInjectSystemPrompts")
+            ) {
+              // Because users may have changed this configuration,
+              // the user's current configuration is used instead of the default
+              const config = useAppConfig.getState();
+              s.mask.modelConfig.enableInjectSystemPrompts =
+                config.modelConfig.enableInjectSystemPrompts;
+            }
+          });
+        }
+
         return newState;
       },
     },

+ 3 - 0
next.config.mjs

@@ -30,6 +30,9 @@ const nextConfig = {
   images: {
     unoptimized: mode === "export",
   },
+  experimental: {
+    forceSwcTransforms: true,
+  },
 };
 
 if (mode !== "export") {

+ 1 - 1
src-tauri/tauri.conf.json

@@ -9,7 +9,7 @@
   },
   "package": {
     "productName": "ChatGPT Next Web",
-    "version": "2.8.9"
+    "version": "2.9.0"
   },
   "tauri": {
     "allowlist": {

+ 6 - 6
yarn.lock

@@ -5322,14 +5322,14 @@ schema-utils@^3.1.1, schema-utils@^3.2.0:
     ajv-keywords "^3.5.2"
 
 semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
-  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+  version "6.3.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+  integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
 
 semver@^7.3.7:
-  version "7.3.8"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
-  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+  version "7.5.4"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
+  integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
   dependencies:
     lru-cache "^6.0.0"