فهرست منبع

feat: 弃用旧的聊天配置

1808837298@qq.com 1 سال پیش
والد
کامیت
89ddf83b44

+ 34 - 0
constant/chat.go

@@ -0,0 +1,34 @@
+package constant
+
+import (
+	"encoding/json"
+	"one-api/common"
+)
+
+var Chats = []map[string]string{
+	{
+		"ChatGPT Next Web 官方示例": "https://app.nextchat.dev/#/?settings={\"key\":\"{key}\",\"url\":\"{address}\"}",
+	},
+	{
+		"Lobe Chat 官方示例": "https://chat-preview.lobehub.com/?settings={\"keyVaults\":{\"openai\":{\"apiKey\":\"{key}\",\"baseURL\":\"{address}/v1\"}}}",
+	},
+	{
+		"AMA 问天": "ama://set-api-key?server={address}&key={key}",
+	},
+	{
+		"OpenCat": "opencat://team/join?domain={address}&token={key}",
+	},
+}
+
+func UpdateChatsByJsonString(jsonString string) error {
+	return json.Unmarshal([]byte(jsonString), &Chats)
+}
+
+func Chats2JsonString() string {
+	jsonBytes, err := json.Marshal(Chats)
+	if err != nil {
+		common.SysError("error marshalling chats: " + err.Error())
+		return "[]"
+	}
+	return string(jsonBytes)
+}

+ 1 - 0
controller/misc.go

@@ -63,6 +63,7 @@ func GetStatus(c *gin.Context) {
 			"default_collapse_sidebar": common.DefaultCollapseSidebar,
 			"default_collapse_sidebar": common.DefaultCollapseSidebar,
 			"enable_online_topup":      constant.PayAddress != "" && constant.EpayId != "" && constant.EpayKey != "",
 			"enable_online_topup":      constant.PayAddress != "" && constant.EpayId != "" && constant.EpayKey != "",
 			"mj_notify_enabled":        constant.MjNotifyEnabled,
 			"mj_notify_enabled":        constant.MjNotifyEnabled,
+			"chats":                    constant.Chats,
 		},
 		},
 	})
 	})
 	return
 	return

+ 3 - 0
model/option.go

@@ -69,6 +69,7 @@ func InitOptionMap() {
 	common.OptionMap["Price"] = strconv.FormatFloat(constant.Price, 'f', -1, 64)
 	common.OptionMap["Price"] = strconv.FormatFloat(constant.Price, 'f', -1, 64)
 	common.OptionMap["MinTopUp"] = strconv.Itoa(constant.MinTopUp)
 	common.OptionMap["MinTopUp"] = strconv.Itoa(constant.MinTopUp)
 	common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
 	common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
+	common.OptionMap["Chats"] = constant.Chats2JsonString()
 	common.OptionMap["GitHubClientId"] = ""
 	common.OptionMap["GitHubClientId"] = ""
 	common.OptionMap["GitHubClientSecret"] = ""
 	common.OptionMap["GitHubClientSecret"] = ""
 	common.OptionMap["TelegramBotToken"] = ""
 	common.OptionMap["TelegramBotToken"] = ""
@@ -248,6 +249,8 @@ func updateOptionMap(key string, value string) (err error) {
 		constant.WorkerValidKey = value
 		constant.WorkerValidKey = value
 	case "PayAddress":
 	case "PayAddress":
 		constant.PayAddress = value
 		constant.PayAddress = value
+	case "Chats":
+		err = constant.UpdateChatsByJsonString(value)
 	case "CustomCallbackAddress":
 	case "CustomCallbackAddress":
 		constant.CustomCallbackAddress = value
 		constant.CustomCallbackAddress = value
 	case "EpayId":
 	case "EpayId":

+ 6 - 0
web/src/components/OperationSetting.js

@@ -10,6 +10,7 @@ import SettingsCreditLimit from '../pages/Setting/Operation/SettingsCreditLimit.
 import SettingsMagnification from '../pages/Setting/Operation/SettingsMagnification.js';
 import SettingsMagnification from '../pages/Setting/Operation/SettingsMagnification.js';
 
 
 import { API, showError, showSuccess } from '../helpers';
 import { API, showError, showSuccess } from '../helpers';
+import SettingsChats from '../pages/Setting/Operation/SettingsChats.js';
 
 
 const OperationSetting = () => {
 const OperationSetting = () => {
   let [inputs, setInputs] = useState({
   let [inputs, setInputs] = useState({
@@ -50,6 +51,7 @@ const OperationSetting = () => {
     DataExportInterval: 5,
     DataExportInterval: 5,
     DefaultCollapseSidebar: false, // 默认折叠侧边栏
     DefaultCollapseSidebar: false, // 默认折叠侧边栏
     RetryTimes: 0,
     RetryTimes: 0,
+    Chats: "[]",
   });
   });
 
 
   let [loading, setLoading] = useState(false);
   let [loading, setLoading] = useState(false);
@@ -131,6 +133,10 @@ const OperationSetting = () => {
         <Card style={{ marginTop: '10px' }}>
         <Card style={{ marginTop: '10px' }}>
           <SettingsCreditLimit options={inputs} refresh={onRefresh} />
           <SettingsCreditLimit options={inputs} refresh={onRefresh} />
         </Card>
         </Card>
+        {/* 聊天设置 */}
+        <Card style={{ marginTop: '10px' }}>
+          <SettingsChats options={inputs} refresh={onRefresh} />
+        </Card>
         {/* 倍率设置 */}
         {/* 倍率设置 */}
         <Card style={{ marginTop: '10px' }}>
         <Card style={{ marginTop: '10px' }}>
           <SettingsMagnification options={inputs} refresh={onRefresh} />
           <SettingsMagnification options={inputs} refresh={onRefresh} />

+ 185 - 202
web/src/components/TokensTable.js

@@ -24,17 +24,6 @@ import {
 import { IconTreeTriangleDown } from '@douyinfe/semi-icons';
 import { IconTreeTriangleDown } from '@douyinfe/semi-icons';
 import EditToken from '../pages/Token/EditToken';
 import EditToken from '../pages/Token/EditToken';
 
 
-const COPY_OPTIONS = [
-  { key: 'next', text: 'ChatGPT Next Web', value: 'next' },
-  { key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama' },
-  { key: 'opencat', text: 'OpenCat', value: 'opencat' },
-];
-
-const OPEN_LINK_OPTIONS = [
-  { key: 'ama', text: 'ChatGPT Web & Midjourney', value: 'ama' },
-  { key: 'opencat', text: 'OpenCat', value: 'opencat' },
-];
-
 function renderTimestamp(timestamp) {
 function renderTimestamp(timestamp) {
   return <>{timestamp2string(timestamp)}</>;
   return <>{timestamp2string(timestamp)}</>;
 }
 }
@@ -87,27 +76,6 @@ function renderStatus(status, model_limits_enabled = false) {
 }
 }
 
 
 const TokensTable = () => {
 const TokensTable = () => {
-  const link_menu = [
-    {
-      node: 'item',
-      key: 'next',
-      name: 'ChatGPT Next Web',
-      onClick: () => {
-        onOpenLink('next');
-      },
-    },
-    { node: 'item', key: 'ama', name: 'AMA 问天', value: 'ama' },
-    {
-      node: 'item',
-      key: 'next-mj',
-      name: 'ChatGPT Web & Midjourney',
-      value: 'next-mj',
-      onClick: () => {
-        onOpenLink('next-mj');
-      },
-    },
-    { node: 'item', key: 'opencat', name: 'OpenCat', value: 'opencat' },
-  ];
 
 
   const columns = [
   const columns = [
     {
     {
@@ -174,149 +142,171 @@ const TokensTable = () => {
     {
     {
       title: '',
       title: '',
       dataIndex: 'operate',
       dataIndex: 'operate',
-      render: (text, record, index) => (
-        <div>
-          <Popover
-            content={'sk-' + record.key}
-            style={{ padding: 20 }}
-            position='top'
-          >
-            <Button theme='light' type='tertiary' style={{ marginRight: 1 }}>
-              查看
-            </Button>
-          </Popover>
-          <Button
-            theme='light'
-            type='secondary'
-            style={{ marginRight: 1 }}
-            onClick={async (text) => {
-              await copyText('sk-' + record.key);
-            }}
-          >
-            复制
-          </Button>
-          <SplitButtonGroup
-            style={{ marginRight: 1 }}
-            aria-label='项目操作按钮组'
-          >
+      render: (text, record, index) => {
+        let chats = localStorage.getItem('chats');
+        let chatsArray = []
+        let chatLink = localStorage.getItem('chat_link');
+        let mjLink = localStorage.getItem('chat_link2');
+        let shouldUseCustom = true;
+        if (chatLink) {
+          shouldUseCustom = false;
+          chatLink += `/#/?settings={"key":"{key}","url":"{address}"}`;
+          chatsArray.push({
+            node: 'item',
+            key: 'default',
+            name: 'ChatGPT Next Web',
+            onClick: () => {
+              onOpenLink('default', chatLink, record);
+            },
+          });
+        }
+        if (mjLink) {
+          shouldUseCustom = false;
+          mjLink += `/#/?settings={"key":"{key}","url":"{address}"}`;
+          chatsArray.push({
+            node: 'item',
+            key: 'mj',
+            name: 'ChatGPT Next Midjourney',
+            onClick: () => {
+              onOpenLink('mj', mjLink, record);
+            },
+          });
+        }
+        if (shouldUseCustom) {
+          try {
+            // console.log(chats);
+            chats = JSON.parse(chats);
+            // check chats is array
+            if (Array.isArray(chats)) {
+              for (let i = 0; i < chats.length; i++) {
+                let chat = {}
+                chat.node = 'item';
+                // c is a map
+                // chat.key = chats[i].name;
+                // console.log(chats[i])
+                for (let key in chats[i]) {
+                  if (chats[i].hasOwnProperty(key)) {
+                    chat.key = i;
+                    chat.name = key;
+                    chat.onClick = () => {
+                      onOpenLink(key, chats[i][key], record);
+                    }
+                  }
+                }
+                chatsArray.push(chat);
+              }
+            }
+
+          } catch (e) {
+            console.log(e);
+            showError('聊天链接配置错误,请联系管理员');
+          }
+        }
+        return (
+          <div>
+            <Popover
+              content={'sk-' + record.key}
+              style={{ padding: 20 }}
+              position='top'
+            >
+              <Button theme='light' type='tertiary' style={{ marginRight: 1 }}>
+                查看
+              </Button>
+            </Popover>
             <Button
             <Button
               theme='light'
               theme='light'
-              style={{ color: 'rgba(var(--semi-teal-7), 1)' }}
-              onClick={() => {
-                onOpenLink('next', record.key);
+              type='secondary'
+              style={{ marginRight: 1 }}
+              onClick={async (text) => {
+                await copyText('sk-' + record.key);
               }}
               }}
             >
             >
-              聊天
+              复制
             </Button>
             </Button>
-            <Dropdown
-              trigger='click'
-              position='bottomRight'
-              menu={[
-                {
-                  node: 'item',
-                  key: 'next',
-                  disabled: !localStorage.getItem('chat_link'),
-                  name: 'ChatGPT Next Web',
-                  onClick: () => {
-                    onOpenLink('next', record.key);
-                  },
-                },
-                {
-                  node: 'item',
-                  key: 'next-mj',
-                  disabled: !localStorage.getItem('chat_link2'),
-                  name: 'ChatGPT Web & Midjourney',
-                  onClick: () => {
-                    onOpenLink('next-mj', record.key);
-                  },
-                },
-                // {
-                //   node: 'item',
-                //   key: 'lobe',
-                //   name: 'Lobe Chat',
-                //   onClick: () => {
-                //     onOpenLink('lobe', record.key);
-                //   },
-                // },
-                {
-                  node: 'item',
-                  key: 'ama',
-                  name: 'AMA 问天(BotGem)',
-                  onClick: () => {
-                    onOpenLink('ama', record.key);
-                  },
-                },
-                {
-                  node: 'item',
-                  key: 'opencat',
-                  name: 'OpenCat',
-                  onClick: () => {
-                    onOpenLink('opencat', record.key);
-                  },
-                },
-              ]}
+            <SplitButtonGroup
+              style={{ marginRight: 1 }}
+              aria-label='项目操作按钮组'
             >
             >
               <Button
               <Button
-                style={{
-                  padding: '8px 4px',
-                  color: 'rgba(var(--semi-teal-7), 1)',
+                theme='light'
+                style={{ color: 'rgba(var(--semi-teal-7), 1)' }}
+                onClick={() => {
+                  if (chatsArray.length === 0) {
+                    showError('请联系管理员配置聊天链接');
+                  } else {
+                    onOpenLink('default', chats[0][Object.keys(chats[0])[0]], record);
+                  }
                 }}
                 }}
-                type='primary'
-                icon={<IconTreeTriangleDown />}
-              ></Button>
-            </Dropdown>
-          </SplitButtonGroup>
-          <Popconfirm
-            title='确定是否要删除此令牌?'
-            content='此修改将不可逆'
-            okType={'danger'}
-            position={'left'}
-            onConfirm={() => {
-              manageToken(record.id, 'delete', record).then(() => {
-                removeRecord(record.key);
-              });
-            }}
-          >
-            <Button theme='light' type='danger' style={{ marginRight: 1 }}>
-              删除
-            </Button>
-          </Popconfirm>
-          {record.status === 1 ? (
-            <Button
-              theme='light'
-              type='warning'
-              style={{ marginRight: 1 }}
-              onClick={async () => {
-                manageToken(record.id, 'disable', record);
+              >
+                聊天
+              </Button>
+              <Dropdown
+                trigger='click'
+                position='bottomRight'
+                menu={chatsArray}
+              >
+                <Button
+                  style={{
+                    padding: '8px 4px',
+                    color: 'rgba(var(--semi-teal-7), 1)',
+                  }}
+                  type='primary'
+                  icon={<IconTreeTriangleDown />}
+                ></Button>
+              </Dropdown>
+            </SplitButtonGroup>
+            <Popconfirm
+              title='确定是否要删除此令牌?'
+              content='此修改将不可逆'
+              okType={'danger'}
+              position={'left'}
+              onConfirm={() => {
+                manageToken(record.id, 'delete', record).then(() => {
+                  removeRecord(record.key);
+                });
               }}
               }}
             >
             >
-              禁用
-            </Button>
-          ) : (
+              <Button theme='light' type='danger' style={{ marginRight: 1 }}>
+                删除
+              </Button>
+            </Popconfirm>
+            {record.status === 1 ? (
+              <Button
+                theme='light'
+                type='warning'
+                style={{ marginRight: 1 }}
+                onClick={async () => {
+                  manageToken(record.id, 'disable', record);
+                }}
+              >
+                禁用
+              </Button>
+            ) : (
+              <Button
+                theme='light'
+                type='secondary'
+                style={{ marginRight: 1 }}
+                onClick={async () => {
+                  manageToken(record.id, 'enable', record);
+                }}
+              >
+                启用
+              </Button>
+            )}
             <Button
             <Button
               theme='light'
               theme='light'
-              type='secondary'
+              type='tertiary'
               style={{ marginRight: 1 }}
               style={{ marginRight: 1 }}
-              onClick={async () => {
-                manageToken(record.id, 'enable', record);
+              onClick={() => {
+                setEditingToken(record);
+                setShowEdit(true);
               }}
               }}
             >
             >
-              启用
+              编辑
             </Button>
             </Button>
-          )}
-          <Button
-            theme='light'
-            type='tertiary'
-            style={{ marginRight: 1 }}
-            onClick={() => {
-              setEditingToken(record);
-              setShowEdit(true);
-            }}
-          >
-            编辑
-          </Button>
-        </div>
-      ),
+          </div>
+        );
+      },
     },
     },
   ];
   ];
 
 
@@ -330,8 +320,7 @@ const TokensTable = () => {
   const [searchKeyword, setSearchKeyword] = useState('');
   const [searchKeyword, setSearchKeyword] = useState('');
   const [searchToken, setSearchToken] = useState('');
   const [searchToken, setSearchToken] = useState('');
   const [searching, setSearching] = useState(false);
   const [searching, setSearching] = useState(false);
-  const [showTopUpModal, setShowTopUpModal] = useState(false);
-  const [targetTokenIdx, setTargetTokenIdx] = useState(0);
+  const [chats, setChats] = useState([]);
   const [editingToken, setEditingToken] = useState({
   const [editingToken, setEditingToken] = useState({
     id: undefined,
     id: undefined,
   });
   });
@@ -376,16 +365,6 @@ const TokensTable = () => {
     setLoading(false);
     setLoading(false);
   };
   };
 
 
-  const onPaginationChange = (e, { activePage }) => {
-    (async () => {
-      if (activePage === Math.ceil(tokens.length / pageSize) + 1) {
-        // In this case we have to load more data and then append them.
-        await loadTokens(activePage - 1);
-      }
-      setActivePage(activePage);
-    })();
-  };
-
   const refresh = async () => {
   const refresh = async () => {
     await loadTokens(activePage - 1);
     await loadTokens(activePage - 1);
   };
   };
@@ -402,7 +381,8 @@ const TokensTable = () => {
     }
     }
   };
   };
 
 
-  const onOpenLink = async (type, key) => {
+  const onOpenLink = async (type, url, record) => {
+    // console.log(type, url, key);
     let status = localStorage.getItem('status');
     let status = localStorage.getItem('status');
     let serverAddress = '';
     let serverAddress = '';
     if (status) {
     if (status) {
@@ -413,36 +393,39 @@ const TokensTable = () => {
       serverAddress = window.location.origin;
       serverAddress = window.location.origin;
     }
     }
     let encodedServerAddress = encodeURIComponent(serverAddress);
     let encodedServerAddress = encodeURIComponent(serverAddress);
-    const chatLink = localStorage.getItem('chat_link');
-    const mjLink = localStorage.getItem('chat_link2');
-    let defaultUrl;
-
-    if (chatLink) {
-      defaultUrl =
-        chatLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
-    }
-    let url;
-    switch (type) {
-      case 'ama':
-        url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`;
-        break;
-      case 'opencat':
-        url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`;
-        break;
-      case 'lobe':
-        url = `https://chat-preview.lobehub.com/?settings={"keyVaults":{"openai":{"apiKey":"sk-${key}","baseURL":"${encodedServerAddress}/v1"}}}`;
-        break;
-      case 'next-mj':
-        url =
-          mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
-        break;
-      default:
-        if (!chatLink) {
-          showError('管理员未设置聊天链接');
-          return;
-        }
-        url = defaultUrl;
-    }
+    url = url.replace('{address}', encodedServerAddress);
+    url = url.replace('{key}', 'sk-' + record.key);
+    // console.log(url);
+    // const chatLink = localStorage.getItem('chat_link');
+    // const mjLink = localStorage.getItem('chat_link2');
+    // let defaultUrl;
+    //
+    // if (chatLink) {
+    //   defaultUrl =
+    //     chatLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
+    // }
+    // let url;
+    // switch (type) {
+    //   case 'ama':
+    //     url = `ama://set-api-key?server=${encodedServerAddress}&key=sk-${key}`;
+    //     break;
+    //   case 'opencat':
+    //     url = `opencat://team/join?domain=${encodedServerAddress}&token=sk-${key}`;
+    //     break;
+    //   case 'lobe':
+    //     url = `https://chat-preview.lobehub.com/?settings={"keyVaults":{"openai":{"apiKey":"sk-${key}","baseURL":"${encodedServerAddress}/v1"}}}`;
+    //     break;
+    //   case 'next-mj':
+    //     url =
+    //       mjLink + `/#/?settings={"key":"sk-${key}","url":"${serverAddress}"}`;
+    //     break;
+    //   default:
+    //     if (!chatLink) {
+    //       showError('管理员未设置聊天链接');
+    //       return;
+    //     }
+    //     url = defaultUrl;
+    // }
 
 
     window.open(url, '_blank');
     window.open(url, '_blank');
   };
   };

+ 1 - 0
web/src/helpers/data.js

@@ -8,6 +8,7 @@ export function setStatusData(data) {
   localStorage.setItem('enable_drawing', data.enable_drawing);
   localStorage.setItem('enable_drawing', data.enable_drawing);
   localStorage.setItem('enable_task', data.enable_task);
   localStorage.setItem('enable_task', data.enable_task);
   localStorage.setItem('enable_data_export', data.enable_data_export);
   localStorage.setItem('enable_data_export', data.enable_data_export);
+  localStorage.setItem('chats', JSON.stringify(data.chats));
   localStorage.setItem(
   localStorage.setItem(
     'data_export_default_time',
     'data_export_default_time',
     data.data_export_default_time,
     data.data_export_default_time,

+ 148 - 0
web/src/pages/Setting/Operation/SettingsChats.js

@@ -0,0 +1,148 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Banner, Button, Col, Form, Popconfirm, Row, Space, Spin } from '@douyinfe/semi-ui';
+import {
+  compareObjects,
+  API,
+  showError,
+  showSuccess,
+  showWarning,
+  verifyJSON,
+  verifyJSONPromise
+} from '../../../helpers';
+
+export default function SettingsChats(props) {
+  const [loading, setLoading] = useState(false);
+  const [inputs, setInputs] = useState({
+    Chats: "[]",
+  });
+  const refForm = useRef();
+  const [inputsRow, setInputsRow] = useState(inputs);
+
+  async function onSubmit() {
+    try {
+      console.log('Starting validation...');
+      await refForm.current.validate().then(() => {
+        console.log('Validation passed');
+        const updateArray = compareObjects(inputs, inputsRow);
+        if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+        const requestQueue = updateArray.map((item) => {
+          let value = '';
+          if (typeof inputs[item.key] === 'boolean') {
+            value = String(inputs[item.key]);
+          } else {
+            value = inputs[item.key];
+          }
+          return API.put('/api/option/', {
+            key: item.key,
+            value
+          });
+        });
+        setLoading(true);
+        Promise.all(requestQueue)
+          .then((res) => {
+            if (requestQueue.length === 1) {
+              if (res.includes(undefined)) return;
+            } else if (requestQueue.length > 1) {
+              if (res.includes(undefined))
+                return showError('部分保存失败,请重试');
+            }
+            showSuccess('保存成功');
+            props.refresh();
+          })
+          .catch(() => {
+            showError('保存失败,请重试');
+          })
+          .finally(() => {
+            setLoading(false);
+          });
+      }).catch((error) => {
+        console.error('Validation failed:', error);
+        showError('请检查输入');
+      });
+    } catch (error) {
+      showError('请检查输入');
+      console.error(error);
+    }
+  }
+
+  async function resetModelRatio() {
+    try {
+      let res = await API.post(`/api/option/rest_model_ratio`);
+      // return {success, message}
+      if (res.data.success) {
+        showSuccess(res.data.message);
+        props.refresh();
+      } else {
+        showError(res.data.message);
+      }
+    } catch (error) {
+      showError(error);
+    }
+  }
+
+  useEffect(() => {
+    const currentInputs = {};
+    for (let key in props.options) {
+      if (Object.keys(inputs).includes(key)) {
+        if (key === 'Chats') {
+          const obj = JSON.parse(props.options[key]);
+          currentInputs[key] = JSON.stringify(obj, null, 2);
+        } else {
+          currentInputs[key] = props.options[key];
+        }
+      }
+    }
+    setInputs(currentInputs);
+    setInputsRow(structuredClone(currentInputs));
+    refForm.current.setValues(currentInputs);
+  }, [props.options]);
+
+  return (
+    <Spin spinning={loading}>
+      <Form
+        values={inputs}
+        getFormApi={(formAPI) => (refForm.current = formAPI)}
+        style={{ marginBottom: 15 }}
+      >
+        <Form.Section text={'令牌聊天设置'}>
+          <Banner
+            type='warning'
+            description={'必须将上方聊天链接全部设置为空,才能使用下方聊天设置功能'}
+          />
+          <Banner
+            type='info'
+            description={'链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1'}
+          />
+          <Form.TextArea
+            label={'聊天配置'}
+            extraText={''}
+            placeholder={'为一个 JSON 文本'}
+            field={'Chats'}
+            autosize={{ minRows: 6, maxRows: 12 }}
+            trigger='blur'
+            stopValidateWithError
+            rules={[
+              {
+                validator: (rule, value) => {
+                  return verifyJSON(value);
+                },
+                message: '不是合法的 JSON 字符串'
+              }
+            ]}
+            onChange={(value) =>
+              setInputs({
+                ...inputs,
+                Chats: value
+              })
+            }
+          />
+        </Form.Section>
+      </Form>
+      <Space>
+        <Button onClick={onSubmit}>
+          保存聊天设置
+        </Button>
+      </Space>
+    </Spin>
+  );
+}

+ 5 - 1
web/src/pages/Setting/Operation/SettingsGeneral.js

@@ -1,5 +1,5 @@
 import React, { useEffect, useState, useRef } from 'react';
 import React, { useEffect, useState, useRef } from 'react';
-import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
+import { Banner, Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
 import {
 import {
   compareObjects,
   compareObjects,
   API,
   API,
@@ -74,6 +74,10 @@ export default function GeneralSettings(props) {
   return (
   return (
     <>
     <>
       <Spin spinning={loading}>
       <Spin spinning={loading}>
+        <Banner
+          type='warning'
+          description={'聊天链接功能已经弃用,请使用下方聊天设置功能'}
+        />
         <Form
         <Form
           values={inputs}
           values={inputs}
           getFormApi={(formAPI) => (refForm.current = formAPI)}
           getFormApi={(formAPI) => (refForm.current = formAPI)}

+ 1 - 1
web/src/pages/Token/EditToken.js

@@ -49,7 +49,7 @@ const EditToken = (props) => {
     group
     group
   } = inputs;
   } = inputs;
   // const [visible, setVisible] = useState(false);
   // const [visible, setVisible] = useState(false);
-  const [models, setModels] = useState({});
+  const [models, setModels] = useState([]);
   const [groups, setGroups] = useState([]);
   const [groups, setGroups] = useState([]);
   const navigate = useNavigate();
   const navigate = useNavigate();
   const handleInputChange = (name, value) => {
   const handleInputChange = (name, value) => {