Browse Source

Merge pull request #2185 from Yidadaa/bugfix-0628

feat: many ux improvments
Yifei Zhang 1 year ago
parent
commit
7748a980f9

+ 8 - 1
app/client/platforms/openai.ts

@@ -1,4 +1,8 @@
-import { OpenaiPath, REQUEST_TIMEOUT_MS } from "@/app/constant";
+import {
+  DEFAULT_API_HOST,
+  OpenaiPath,
+  REQUEST_TIMEOUT_MS,
+} from "@/app/constant";
 import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
 
 import { ChatOptions, getHeaders, LLMApi, LLMUsage } from "../api";
@@ -12,6 +16,9 @@ import { prettyObject } from "@/app/utils/format";
 export class ChatGPTApi implements LLMApi {
   path(path: string): string {
     let openaiUrl = useAccessStore.getState().openaiUrl;
+    if (openaiUrl.length === 0) {
+      openaiUrl = DEFAULT_API_HOST;
+    }
     if (openaiUrl.endsWith("/")) {
       openaiUrl = openaiUrl.slice(0, openaiUrl.length - 1);
     }

+ 24 - 5
app/components/button.module.scss

@@ -27,6 +27,26 @@
       fill: white !important;
     }
   }
+
+  &.danger {
+    color: rgba($color: red, $alpha: 0.8);
+    border-color: rgba($color: red, $alpha: 0.5);
+    background-color: rgba($color: red, $alpha: 0.05);
+
+    &:hover {
+      border-color: red;
+      background-color: rgba($color: red, $alpha: 0.1);
+    }
+
+    path {
+      fill: red !important;
+    }
+  }
+
+  &:hover,
+  &:focus {
+    border-color: var(--primary);
+  }
 }
 
 .shadow {
@@ -37,10 +57,6 @@
   border: var(--border-in-light);
 }
 
-.icon-button:hover {
-  border-color: var(--primary);
-}
-
 .icon-button-icon {
   width: 16px;
   height: 16px;
@@ -56,9 +72,12 @@
 }
 
 .icon-button-text {
-  margin-left: 5px;
   font-size: 12px;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
+
+  &:not(:first-child) {
+    margin-left: 5px;
+  }
 }

+ 7 - 1
app/components/button.tsx

@@ -2,16 +2,20 @@ import * as React from "react";
 
 import styles from "./button.module.scss";
 
+export type ButtonType = "primary" | "danger" | null;
+
 export function IconButton(props: {
   onClick?: () => void;
   icon?: JSX.Element;
-  type?: "primary" | "danger";
+  type?: ButtonType;
   text?: string;
   bordered?: boolean;
   shadow?: boolean;
   className?: string;
   title?: string;
   disabled?: boolean;
+  tabIndex?: number;
+  autoFocus?: boolean;
 }) {
   return (
     <button
@@ -25,6 +29,8 @@ export function IconButton(props: {
       title={props.title}
       disabled={props.disabled}
       role="button"
+      tabIndex={props.tabIndex}
+      autoFocus={props.autoFocus}
     >
       {props.icon && (
         <div

+ 6 - 2
app/components/chat-list.tsx

@@ -17,6 +17,7 @@ import { Path } from "../constant";
 import { MaskAvatar } from "./mask";
 import { Mask } from "../store/mask";
 import { useRef, useEffect } from "react";
+import { showConfirm } from "./ui-lib";
 
 export function ChatItem(props: {
   onClick?: () => void;
@@ -139,8 +140,11 @@ export function ChatList(props: { narrow?: boolean }) {
                   navigate(Path.Chat);
                   selectSession(i);
                 }}
-                onDelete={() => {
-                  if (!props.narrow || confirm(Locale.Home.DeleteChat)) {
+                onDelete={async () => {
+                  if (
+                    !props.narrow ||
+                    (await showConfirm(Locale.Home.DeleteChat))
+                  ) {
                     chatStore.deleteSession(i);
                   }
                 }}

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

@@ -251,6 +251,12 @@
   display: flex;
   flex-direction: column;
   align-items: flex-start;
+
+  &:hover {
+    .chat-message-edit {
+      opacity: 0.9;
+    }
+  }
 }
 
 .chat-message-user > .chat-message-container {
@@ -259,6 +265,23 @@
 
 .chat-message-avatar {
   margin-top: 20px;
+  position: relative;
+
+  .chat-message-edit {
+    position: absolute;
+    height: 100%;
+    width: 100%;
+    overflow: hidden;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    opacity: 0;
+    transition: all ease 0.3s;
+
+    button {
+      padding: 7px;
+    }
+  }
 }
 
 .chat-message-status {

+ 30 - 7
app/components/chat.tsx

@@ -23,6 +23,7 @@ import BreakIcon from "../icons/break.svg";
 import SettingsIcon from "../icons/chat-settings.svg";
 import DeleteIcon from "../icons/clear.svg";
 import PinIcon from "../icons/pin.svg";
+import EditIcon from "../icons/rename.svg";
 
 import LightIcon from "../icons/light.svg";
 import DarkIcon from "../icons/dark.svg";
@@ -61,7 +62,7 @@ import Locale from "../locales";
 import { IconButton } from "./button";
 import styles from "./chat.module.scss";
 
-import { ListItem, Modal, showToast } from "./ui-lib";
+import { ListItem, Modal, showConfirm, showPrompt, showToast } from "./ui-lib";
 import { useLocation, useNavigate } from "react-router-dom";
 import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
 import { Avatar } from "./emoji";
@@ -93,8 +94,8 @@ export function SessionConfigModel(props: { onClose: () => void }) {
             icon={<ResetIcon />}
             bordered
             text={Locale.Chat.Config.Reset}
-            onClick={() => {
-              if (confirm(Locale.Memory.ResetConfirm)) {
+            onClick={async () => {
+              if (await showConfirm(Locale.Memory.ResetConfirm)) {
                 chatStore.updateCurrentSession(
                   (session) => (session.memoryPrompt = ""),
                 );
@@ -778,10 +779,13 @@ export function Chat() {
   const [showPromptModal, setShowPromptModal] = useState(false);
 
   const renameSession = () => {
-    const newTopic = prompt(Locale.Chat.Rename, session.topic);
-    if (newTopic && newTopic !== session.topic) {
-      chatStore.updateCurrentSession((session) => (session.topic = newTopic!));
-    }
+    showPrompt(Locale.Chat.Rename, session.topic).then((newTopic) => {
+      if (newTopic && newTopic !== session.topic) {
+        chatStore.updateCurrentSession(
+          (session) => (session.topic = newTopic!),
+        );
+      }
+    });
   };
 
   const clientConfig = useMemo(() => getClientConfig(), []);
@@ -899,6 +903,25 @@ 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,
+                          );
+                          chatStore.updateCurrentSession((session) => {
+                            const m = session.messages.find(
+                              (m) => m.id === message.id,
+                            );
+                            if (m) {
+                              m.content = newMessage;
+                            }
+                          });
+                        }}
+                      ></IconButton>
+                    </div>
                     {message.role === "user" ? (
                       <Avatar avatar={config.avatar} />
                     ) : (

+ 6 - 4
app/components/error.tsx

@@ -5,6 +5,7 @@ import ResetIcon from "../icons/reload.svg";
 import { ISSUE_URL } from "../constant";
 import Locale from "../locales";
 import { downloadAs } from "../utils";
+import { showConfirm } from "./ui-lib";
 
 interface IErrorBoundaryState {
   hasError: boolean;
@@ -57,10 +58,11 @@ export class ErrorBoundary extends React.Component<any, IErrorBoundaryState> {
             <IconButton
               icon={<ResetIcon />}
               text="Clear All Data"
-              onClick={() =>
-                confirm(Locale.Settings.Actions.ConfirmClearAll) &&
-                this.clearAndSaveData()
-              }
+              onClick={async () => {
+                if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
+                  this.clearAndSaveData();
+                }
+              }}
               bordered
             />
           </div>

+ 13 - 5
app/components/mask.tsx

@@ -15,7 +15,15 @@ import CopyIcon from "../icons/copy.svg";
 import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
 import { ChatMessage, ModelConfig, useAppConfig, useChatStore } from "../store";
 import { ROLES } from "../client/api";
-import { Input, List, ListItem, Modal, Popover, Select } from "./ui-lib";
+import {
+  Input,
+  List,
+  ListItem,
+  Modal,
+  Popover,
+  Select,
+  showConfirm,
+} from "./ui-lib";
 import { Avatar, AvatarPicker } from "./emoji";
 import Locale, { AllLangs, ALL_LANG_OPTIONS, Lang } from "../locales";
 import { useNavigate } from "react-router-dom";
@@ -125,10 +133,10 @@ export function MaskConfig(props: {
             <input
               type="checkbox"
               checked={props.mask.syncGlobalConfig}
-              onChange={(e) => {
+              onChange={async (e) => {
                 if (
                   e.currentTarget.checked &&
-                  confirm(Locale.Mask.Config.Sync.Confirm)
+                  (await showConfirm(Locale.Mask.Config.Sync.Confirm))
                 ) {
                   props.updateMask((mask) => {
                     mask.syncGlobalConfig = e.currentTarget.checked;
@@ -439,8 +447,8 @@ export function MaskPage() {
                     <IconButton
                       icon={<DeleteIcon />}
                       text={Locale.Mask.Item.Delete}
-                      onClick={() => {
-                        if (confirm(Locale.Mask.Item.DeleteConfirm)) {
+                      onClick={async () => {
+                        if (await showConfirm(Locale.Mask.Item.DeleteConfirm)) {
                           maskStore.delete(m.id);
                         }
                       }}

+ 3 - 2
app/components/new-chat.tsx

@@ -14,6 +14,7 @@ import Locale from "../locales";
 import { useAppConfig, useChatStore } from "../store";
 import { MaskAvatar } from "./mask";
 import { useCommand } from "../command";
+import { showConfirm } from "./ui-lib";
 
 function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
   const xmin = Math.max(aRect.x, bRect.x);
@@ -125,8 +126,8 @@ export function NewChat() {
         {!state?.fromHome && (
           <IconButton
             text={Locale.NewChat.NotShow}
-            onClick={() => {
-              if (confirm(Locale.NewChat.ConfirmNoShow)) {
+            onClick={async () => {
+              if (await showConfirm(Locale.NewChat.ConfirmNoShow)) {
                 startChat();
                 config.update(
                   (config) => (config.dontShowMaskSplashScreen = true),

+ 43 - 26
app/components/settings.tsx

@@ -18,6 +18,7 @@ import {
   PasswordInput,
   Popover,
   Select,
+  showConfirm,
 } from "./ui-lib";
 import { ModelConfigList } from "./model-config";
 
@@ -199,6 +200,44 @@ function UserPromptModal(props: { onClose?: () => void }) {
   );
 }
 
+function DangerItems() {
+  const chatStore = useChatStore();
+  const appConfig = useAppConfig();
+
+  return (
+    <List>
+      <ListItem
+        title={Locale.Settings.Danger.Reset.Title}
+        subTitle={Locale.Settings.Danger.Reset.SubTitle}
+      >
+        <IconButton
+          text={Locale.Settings.Danger.Reset.Action}
+          onClick={async () => {
+            if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
+              appConfig.reset();
+            }
+          }}
+          type="danger"
+        />
+      </ListItem>
+      <ListItem
+        title={Locale.Settings.Danger.Clear.Title}
+        subTitle={Locale.Settings.Danger.Clear.SubTitle}
+      >
+        <IconButton
+          text={Locale.Settings.Danger.Clear.Action}
+          onClick={async () => {
+            if (await showConfirm(Locale.Settings.Danger.Clear.Confirm)) {
+              chatStore.clearAllData();
+            }
+          }}
+          type="danger"
+        />
+      </ListItem>
+    </List>
+  );
+}
+
 function SyncItems() {
   const syncStore = useSyncStore();
   const webdav = syncStore.webDavConfig;
@@ -289,7 +328,6 @@ export function Settings() {
   const [showEmojiPicker, setShowEmojiPicker] = useState(false);
   const config = useAppConfig();
   const updateConfig = config.update;
-  const resetConfig = config.reset;
   const chatStore = useChatStore();
 
   const updateStore = useUpdateStore();
@@ -374,36 +412,13 @@ export function Settings() {
           </div>
         </div>
         <div className="window-actions">
-          <div className="window-action-button">
-            <IconButton
-              icon={<ClearIcon />}
-              onClick={() => {
-                if (confirm(Locale.Settings.Actions.ConfirmClearAll)) {
-                  chatStore.clearAllData();
-                }
-              }}
-              bordered
-              title={Locale.Settings.Actions.ClearAll}
-            />
-          </div>
-          <div className="window-action-button">
-            <IconButton
-              icon={<ResetIcon />}
-              onClick={() => {
-                if (confirm(Locale.Settings.Actions.ConfirmResetAll)) {
-                  resetConfig();
-                }
-              }}
-              bordered
-              title={Locale.Settings.Actions.ResetAll}
-            />
-          </div>
+          <div className="window-action-button"></div>
+          <div className="window-action-button"></div>
           <div className="window-action-button">
             <IconButton
               icon={<CloseIcon />}
               onClick={() => navigate(Path.Home)}
               bordered
-              title={Locale.Settings.Actions.Close}
             />
           </div>
         </div>
@@ -686,6 +701,8 @@ export function Settings() {
         {shouldShowPromptModal && (
           <UserPromptModal onClose={() => setShowPromptModal(false)} />
         )}
+
+        <DangerItems />
       </div>
     </ErrorBoundary>
   );

+ 3 - 3
app/components/sidebar.tsx

@@ -26,7 +26,7 @@ import {
 import { Link, useNavigate } from "react-router-dom";
 import { useMobileScreen } from "../utils";
 import dynamic from "next/dynamic";
-import { showToast } from "./ui-lib";
+import { showConfirm, showToast } from "./ui-lib";
 
 const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
   loading: () => null,
@@ -160,8 +160,8 @@ export function SideBar(props: { className?: string }) {
           <div className={styles["sidebar-action"] + " " + styles.mobile}>
             <IconButton
               icon={<CloseIcon />}
-              onClick={() => {
-                if (confirm(Locale.Home.DeleteChat)) {
+              onClick={async () => {
+                if (await showConfirm(Locale.Home.DeleteChat)) {
                   chatStore.deleteSession(chatStore.currentSessionIndex);
                 }
               }}

+ 20 - 0
app/components/ui-lib.module.scss

@@ -228,3 +228,23 @@
     pointer-events: none;
   }
 }
+
+.modal-input {
+  height: 100%;
+  width: 100%;
+  border-radius: 10px;
+  border: var(--border-in-light);
+  box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.03);
+  background-color: var(--white);
+  color: var(--black);
+  font-family: inherit;
+  padding: 10px 90px 10px 14px;
+  resize: none;
+  outline: none;
+  box-sizing: border-box;
+  min-height: 68px;
+
+  &:focus {
+    border: 1px solid var(--primary);
+  }
+}

+ 130 - 1
app/components/ui-lib.tsx

@@ -4,6 +4,10 @@ import CloseIcon from "../icons/close.svg";
 import EyeIcon from "../icons/eye.svg";
 import EyeOffIcon from "../icons/eye-off.svg";
 import DownIcon from "../icons/down.svg";
+import ConfirmIcon from "../icons/confirm.svg";
+import CancelIcon from "../icons/cancel.svg";
+
+import Locale from "../locales";
 
 import { createRoot } from "react-dom/client";
 import React, { HTMLProps, useEffect, useState } from "react";
@@ -87,7 +91,7 @@ export function Loading() {
 
 interface ModalProps {
   title: string;
-  children?: JSX.Element | JSX.Element[];
+  children?: any;
   actions?: JSX.Element[];
   onClose?: () => void;
 }
@@ -262,3 +266,128 @@ export function Select(
     </div>
   );
 }
+
+export function showConfirm(content: any) {
+  const div = document.createElement("div");
+  div.className = "modal-mask";
+  document.body.appendChild(div);
+
+  const root = createRoot(div);
+  const closeModal = () => {
+    root.unmount();
+    div.remove();
+  };
+
+  return new Promise<boolean>((resolve) => {
+    root.render(
+      <Modal
+        title={Locale.UI.Confirm}
+        actions={[
+          <IconButton
+            key="cancel"
+            text={Locale.UI.Cancel}
+            onClick={() => {
+              resolve(false);
+              closeModal();
+            }}
+            icon={<CancelIcon />}
+            tabIndex={0}
+            bordered
+            shadow
+          ></IconButton>,
+          <IconButton
+            key="confirm"
+            text={Locale.UI.Confirm}
+            type="primary"
+            onClick={() => {
+              resolve(true);
+              closeModal();
+            }}
+            icon={<ConfirmIcon />}
+            tabIndex={0}
+            autoFocus
+            bordered
+            shadow
+          ></IconButton>,
+        ]}
+        onClose={closeModal}
+      >
+        {content}
+      </Modal>,
+    );
+  });
+}
+
+function PromptInput(props: {
+  value: string;
+  onChange: (value: string) => void;
+}) {
+  const [input, setInput] = useState(props.value);
+  const onInput = (value: string) => {
+    props.onChange(value);
+    setInput(value);
+  };
+
+  return (
+    <textarea
+      className={styles["modal-input"]}
+      autoFocus
+      value={input}
+      onInput={(e) => onInput(e.currentTarget.value)}
+    ></textarea>
+  );
+}
+
+export function showPrompt(content: any, value = "") {
+  const div = document.createElement("div");
+  div.className = "modal-mask";
+  document.body.appendChild(div);
+
+  const root = createRoot(div);
+  const closeModal = () => {
+    root.unmount();
+    div.remove();
+  };
+
+  return new Promise<string>((resolve) => {
+    let userInput = "";
+
+    root.render(
+      <Modal
+        title={content}
+        actions={[
+          <IconButton
+            key="cancel"
+            text={Locale.UI.Cancel}
+            onClick={() => {
+              closeModal();
+            }}
+            icon={<CancelIcon />}
+            bordered
+            shadow
+            tabIndex={0}
+          ></IconButton>,
+          <IconButton
+            key="confirm"
+            text={Locale.UI.Confirm}
+            type="primary"
+            onClick={() => {
+              resolve(userInput);
+              closeModal();
+            }}
+            icon={<ConfirmIcon />}
+            bordered
+            shadow
+            tabIndex={0}
+          ></IconButton>,
+        ]}
+        onClose={closeModal}
+      >
+        <PromptInput
+          onChange={(val) => (userInput = val)}
+          value={value}
+        ></PromptInput>
+      </Modal>,
+    );
+  });
+}

+ 6 - 0
app/global.d.ts

@@ -9,3 +9,9 @@ declare module "*.scss" {
 }
 
 declare module "*.svg";
+
+declare interface Window {
+  __TAURI__?: {
+    writeText(text: string): Promise<void>;
+  };
+}

File diff suppressed because it is too large
+ 0 - 0
app/icons/cancel.svg


File diff suppressed because it is too large
+ 0 - 0
app/icons/confirm.svg


+ 1 - 7
app/locales/ar.ts

@@ -100,13 +100,7 @@ const ar: PartialLocaleType = {
   Settings: {
     Title: "الإعدادات",
     SubTitle: "جميع الإعدادات",
-    Actions: {
-      ClearAll: "مسح جميع البيانات",
-      ResetAll: "إعادة تعيين جميع الإعدادات",
-      Close: "إغلاق",
-      ConfirmResetAll: "هل أنت متأكد من رغبتك في إعادة تعيين جميع الإعدادات؟",
-      ConfirmClearAll: "هل أنت متأكد من رغبتك في مسح جميع البيانات؟",
-    },
+
     Lang: {
       Name: "Language", // تنبيه: إذا كنت ترغب في إضافة ترجمة جديدة، يرجى عدم ترجمة هذه القيمة وتركها "Language"
       All: "كل اللغات",

+ 16 - 7
app/locales/cn.ts

@@ -29,6 +29,7 @@ const cn = {
       PinToastContent: "已将 2 条对话固定至预设提示词",
       PinToastAction: "查看",
       Delete: "删除",
+      Edit: "编辑",
     },
     Commands: {
       new: "新建聊天",
@@ -108,13 +109,21 @@ const cn = {
   },
   Settings: {
     Title: "设置",
-    SubTitle: "设置选项",
-    Actions: {
-      ClearAll: "清除所有数据",
-      ResetAll: "重置所有选项",
-      Close: "关闭",
-      ConfirmResetAll: "确认重置所有配置?",
-      ConfirmClearAll: "确认清除所有数据?",
+    SubTitle: "所有设置选项",
+
+    Danger: {
+      Reset: {
+        Title: "重置所有设置",
+        SubTitle: "重置所有设置项回默认值",
+        Action: "立即重置",
+        Confirm: "确认重置所有设置?",
+      },
+      Clear: {
+        Title: "清除所有数据",
+        SubTitle: "清除所有聊天、设置数据",
+        Action: "立即清除",
+        Confirm: "确认清除所有聊天、设置数据?",
+      },
     },
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`

+ 1 - 7
app/locales/cs.ts

@@ -61,13 +61,7 @@ const cs: PartialLocaleType = {
   Settings: {
     Title: "Nastavení",
     SubTitle: "Všechna nastavení",
-    Actions: {
-      ClearAll: "Vymazat všechna data",
-      ResetAll: "Obnovit veškeré nastavení",
-      Close: "Zavřít",
-      ConfirmResetAll: "Jste si jisti, že chcete obnovit všechna nastavení?",
-      ConfirmClearAll: "Jste si jisti, že chcete smazat všechna data?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "Všechny jazyky",

+ 1 - 8
app/locales/de.ts

@@ -61,14 +61,7 @@ const de: PartialLocaleType = {
   Settings: {
     Title: "Einstellungen",
     SubTitle: "Alle Einstellungen",
-    Actions: {
-      ClearAll: "Alle Daten löschen",
-      ResetAll: "Alle Einstellungen zurücksetzen",
-      Close: "Schließen",
-      ConfirmResetAll:
-        "Möchten Sie wirklich alle Konfigurationen zurücksetzen?",
-      ConfirmClearAll: "Möchten Sie wirklich alle Chats zurücksetzen?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "Alle Sprachen",

+ 14 - 6
app/locales/en.ts

@@ -30,6 +30,7 @@ const en: LocaleType = {
       PinToastContent: "Pinned 2 messages to contextual prompts",
       PinToastAction: "View",
       Delete: "Delete",
+      Edit: "Edit",
     },
     Commands: {
       new: "Start a new chat",
@@ -111,12 +112,19 @@ const en: LocaleType = {
   Settings: {
     Title: "Settings",
     SubTitle: "All Settings",
-    Actions: {
-      ClearAll: "Clear All Data",
-      ResetAll: "Reset All Settings",
-      Close: "Close",
-      ConfirmResetAll: "Are you sure you want to reset all configurations?",
-      ConfirmClearAll: "Are you sure you want to reset all data?",
+    Danger: {
+      Reset: {
+        Title: "Reset All Settings",
+        SubTitle: "Reset all setting items to default",
+        Action: "Reset",
+        Confirm: "Confirm to reset all settings to default?",
+      },
+      Clear: {
+        Title: "Clear All Data",
+        SubTitle: "Clear all messages and settings",
+        Action: "Clear",
+        Confirm: "Confirm to clear all messages and settings?",
+      },
     },
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`

+ 1 - 7
app/locales/es.ts

@@ -61,13 +61,7 @@ const es: PartialLocaleType = {
   Settings: {
     Title: "Configuración",
     SubTitle: "Todas las configuraciones",
-    Actions: {
-      ClearAll: "Borrar todos los datos",
-      ResetAll: "Restablecer todas las configuraciones",
-      Close: "Cerrar",
-      ConfirmResetAll: "Are you sure you want to reset all configurations?",
-      ConfirmClearAll: "Are you sure you want to reset all chat?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "Todos los idiomas",

+ 1 - 8
app/locales/fr.ts

@@ -61,14 +61,7 @@ const fr: PartialLocaleType = {
   Settings: {
     Title: "Paramètres",
     SubTitle: "Toutes les configurations",
-    Actions: {
-      ClearAll: "Effacer toutes les données",
-      ResetAll: "Réinitialiser les configurations",
-      Close: "Fermer",
-      ConfirmResetAll:
-        "Êtes-vous sûr de vouloir réinitialiser toutes les configurations?",
-      ConfirmClearAll: "Êtes-vous sûr de vouloir supprimer toutes les données?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION : si vous souhaitez ajouter une nouvelle traduction, ne traduisez pas cette valeur, laissez-la sous forme de `Language`
       All: "Toutes les langues",

+ 1 - 7
app/locales/it.ts

@@ -61,13 +61,7 @@ const it: PartialLocaleType = {
   Settings: {
     Title: "Impostazioni",
     SubTitle: "Tutte le impostazioni",
-    Actions: {
-      ClearAll: "Cancella tutti i dati",
-      ResetAll: "Resetta tutte le impostazioni",
-      Close: "Chiudi",
-      ConfirmResetAll: "Sei sicuro vuoi cancellare tutte le impostazioni?",
-      ConfirmClearAll: "Sei sicuro vuoi cancellare tutte le chat?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "Tutte le lingue",

+ 1 - 7
app/locales/jp.ts

@@ -61,13 +61,7 @@ const jp: PartialLocaleType = {
   Settings: {
     Title: "設定",
     SubTitle: "設定オプション",
-    Actions: {
-      ClearAll: "すべてのデータをクリア",
-      ResetAll: "すべてのオプションをリセット",
-      Close: "閉じる",
-      ConfirmResetAll: "すべての設定をリセットしてもよろしいですか?",
-      ConfirmClearAll: "すべてのチャットをリセットしてもよろしいですか?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "全ての言語",

+ 1 - 7
app/locales/ko.ts

@@ -61,13 +61,7 @@ const ko: PartialLocaleType = {
   Settings: {
     Title: "설정",
     SubTitle: "모든 설정",
-    Actions: {
-      ClearAll: "모든 데이터 지우기",
-      ResetAll: "모든 설정 초기화",
-      Close: "닫기",
-      ConfirmResetAll: "모든 설정을 초기화하시겠습니까?",
-      ConfirmClearAll: "모든 데이터를 지우시겠습니까?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "All Languages",

+ 1 - 5
app/locales/no.ts

@@ -56,11 +56,7 @@ const no: PartialLocaleType = {
   Settings: {
     Title: "Innstillinger",
     SubTitle: "Alle innstillinger",
-    Actions: {
-      ClearAll: "Fjern alle data",
-      ResetAll: "Nullstill innstillinger",
-      Close: "Lukk",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
     },

+ 1 - 7
app/locales/ru.ts

@@ -61,13 +61,7 @@ const ru: PartialLocaleType = {
   Settings: {
     Title: "Настройки",
     SubTitle: "Все настройки",
-    Actions: {
-      ClearAll: "Очистить все данные",
-      ResetAll: "Сбросить все настройки",
-      Close: "Закрыть",
-      ConfirmResetAll: "Вы уверены, что хотите сбросить все настройки?",
-      ConfirmClearAll: "Вы уверены, что хотите очистить все данные?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "Все языки",

+ 1 - 7
app/locales/tr.ts

@@ -61,13 +61,7 @@ const tr: PartialLocaleType = {
   Settings: {
     Title: "Ayarlar",
     SubTitle: "Tüm Ayarlar",
-    Actions: {
-      ClearAll: "Tüm Verileri Temizle",
-      ResetAll: "Tüm Ayarları Sıfırla",
-      Close: "Kapat",
-      ConfirmResetAll: "Tüm ayarları sıfırlamak istediğinizden emin misiniz?",
-      ConfirmClearAll: "Tüm sohbeti sıfırlamak istediğinizden emin misiniz?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "Tüm Diller",

+ 1 - 7
app/locales/tw.ts

@@ -59,13 +59,7 @@ const tw: PartialLocaleType = {
   Settings: {
     Title: "設定",
     SubTitle: "設定選項",
-    Actions: {
-      ClearAll: "清除所有資料",
-      ResetAll: "重設所有設定",
-      Close: "關閉",
-      ConfirmResetAll: "您確定要重設所有設定嗎?",
-      ConfirmClearAll: "您確定要清除所有数据嗎?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "所有语言",

+ 1 - 7
app/locales/vi.ts

@@ -61,13 +61,7 @@ const vi: PartialLocaleType = {
   Settings: {
     Title: "Cài đặt",
     SubTitle: "Tất cả cài đặt",
-    Actions: {
-      ClearAll: "Xóa toàn bộ dữ liệu",
-      ResetAll: "Khôi phục cài đặt gốc",
-      Close: "Đóng",
-      ConfirmResetAll: "Bạn chắc chắn muốn thiết lập lại tất cả cài đặt?",
-      ConfirmClearAll: "Bạn chắc chắn muốn thiết lập lại tất cả dữ liệu?",
-    },
+
     Lang: {
       Name: "Language", // ATTENTION: if you wanna add a new translation, please do not translate this value, leave it as `Language`
       All: "Tất cả ngôn ngữ",

+ 3 - 0
app/styles/globals.scss

@@ -304,6 +304,9 @@ pre {
   &:hover {
     filter: brightness(0.9);
   }
+  &:focus {
+    filter: brightness(0.95);
+  }
 }
 
 .error {

+ 6 - 1
app/utils.ts

@@ -8,7 +8,12 @@ export function trimTopic(topic: string) {
 
 export async function copyToClipboard(text: string) {
   try {
-    await navigator.clipboard.writeText(text);
+    if (window.__TAURI__) {
+      window.__TAURI__.writeText(text);
+    } else {
+      await navigator.clipboard.writeText(text);
+    }
+
     showToast(Locale.Copy.Success);
   } catch (error) {
     const textArea = document.createElement("textarea");

+ 1 - 1
src-tauri/Cargo.toml

@@ -17,7 +17,7 @@ tauri-build = { version = "1.3.0", features = [] }
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = ["derive"] }
-tauri = { version = "1.3.0", features = ["clipboard-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-set-icon", "window-set-ignore-cursor-events", "window-set-resizable", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
+tauri = { version = "1.3.0", features = ["clipboard-all", "dialog-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-set-icon", "window-set-ignore-cursor-events", "window-set-resizable", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
 tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
 
 [features]

+ 14 - 3
src-tauri/tauri.conf.json

@@ -4,11 +4,12 @@
     "beforeBuildCommand": "yarn export",
     "beforeDevCommand": "yarn export:dev",
     "devPath": "http://localhost:3000",
-    "distDir": "../out"
+    "distDir": "../out",
+    "withGlobalTauri": true
   },
   "package": {
     "productName": "chatgpt-next-web",
-    "version": "2.8.4"
+    "version": "2.8.5"
   },
   "tauri": {
     "allowlist": {
@@ -17,8 +18,18 @@
         "all": false,
         "open": true
       },
+      "dialog": {
+        "all": true,
+        "ask": true,
+        "confirm": true,
+        "message": true,
+        "open": true,
+        "save": true
+      },
       "clipboard": {
-        "all": true
+        "all": true,
+        "writeText": true,
+        "readText": true
       },
       "window": {
         "all": false,

Some files were not shown because too many files changed in this diff