diff --git a/app/components/home.module.scss b/app/components/home.module.scss
index 837c6752..7d011794 100644
--- a/app/components/home.module.scss
+++ b/app/components/home.module.scss
@@ -50,6 +50,8 @@
.window-content {
width: var(--window-content-width);
height: 100%;
+ display: flex;
+ flex-direction: column;
}
.mobile {
@@ -111,7 +113,8 @@
overflow: auto;
}
-.chat-list {}
+.chat-list {
+}
.chat-item {
padding: 10px 14px;
@@ -165,12 +168,12 @@
opacity: 0;
}
-.chat-item:hover>.chat-item-delete {
+.chat-item:hover > .chat-item-delete {
opacity: 0.5;
right: 10px;
}
-.chat-item:hover>.chat-item-delete:hover {
+.chat-item:hover > .chat-item-delete:hover {
opacity: 1;
}
@@ -182,9 +185,11 @@
margin-top: 8px;
}
-.chat-item-count {}
+.chat-item-count {
+}
-.chat-item-date {}
+.chat-item-date {
+}
.sidebar-tail {
display: flex;
@@ -232,7 +237,7 @@
animation: slide-in ease 0.3s;
}
-.chat-message-user>.chat-message-container {
+.chat-message-user > .chat-message-container {
align-items: flex-end;
}
@@ -271,7 +276,7 @@
border: var(--border-in-light);
}
-.chat-message-user>.chat-message-container>.chat-message-item {
+.chat-message-user > .chat-message-container > .chat-message-item {
background-color: var(--second);
}
@@ -346,4 +351,4 @@
align-items: center;
height: 100%;
width: 100%;
-}
\ No newline at end of file
+}
diff --git a/app/components/settings.module.scss b/app/components/settings.module.scss
index 08be1ff2..ad994f68 100644
--- a/app/components/settings.module.scss
+++ b/app/components/settings.module.scss
@@ -2,6 +2,7 @@
.settings {
padding: 20px;
+ overflow: auto;
}
.settings-title {
@@ -9,6 +10,11 @@
font-weight: bolder;
}
+.settings-sub-title {
+ font-size: 12px;
+ font-weight: normal;
+}
+
.avatar {
cursor: pointer;
-}
\ No newline at end of file
+}
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index ebbe8837..4e862f23 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -1,4 +1,4 @@
-import { useState, useRef, useEffect } from "react";
+import { useState } from "react";
import EmojiPicker, { Theme as EmojiTheme } from "emoji-picker-react";
@@ -11,26 +11,50 @@ import ClearIcon from "../icons/clear.svg";
import { List, ListItem, Popover } from "./ui-lib";
import { IconButton } from "./button";
-import { SubmitKey, useChatStore, Theme } from "../store";
+import { SubmitKey, useChatStore, Theme, ALL_MODELS } from "../store";
import { Avatar } from "./home";
-import Locale, { changeLang, getLang } from '../locales'
+import Locale, { changeLang, getLang } from "../locales";
+
+function SettingItem(props: {
+ title: string;
+ subTitle?: string;
+ children: JSX.Element;
+}) {
+ return (
+
+
+
{props.title}
+ {props.subTitle && (
+
{props.subTitle}
+ )}
+
+ {props.children}
+
+ );
+}
export function Settings(props: { closeSettings: () => void }) {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
- const [config, updateConfig, resetConfig, clearAllData] = useChatStore((state) => [
- state.config,
- state.updateConfig,
- state.resetConfig,
- state.clearAllData,
- ]);
+ const [config, updateConfig, resetConfig, clearAllData] = useChatStore(
+ (state) => [
+ state.config,
+ state.updateConfig,
+ state.resetConfig,
+ state.clearAllData,
+ ]
+ );
return (
<>
-
{Locale.Settings.Title}
-
{Locale.Settings.SubTitle}
+
+ {Locale.Settings.Title}
+
+
+ {Locale.Settings.SubTitle}
+
@@ -61,8 +85,7 @@ export function Settings(props: { closeSettings: () => void }) {
-
- {Locale.Settings.Avatar}
+
setShowEmojiPicker(false)}
content={
@@ -84,51 +107,47 @@ export function Settings(props: { closeSettings: () => void }) {
-
+
+
+
+
+
- {Locale.Settings.SendKey}
-
>
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index c7409ca0..dfe12733 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -1,72 +1,93 @@
-
const cn = {
- ChatItem: {
- ChatItemCount: (count: number) => `${count} 条对话`,
+ ChatItem: {
+ ChatItemCount: (count: number) => `${count} 条对话`,
+ },
+ Chat: {
+ SubTitle: (count: number) => `与 ChatGPT 的 ${count} 条对话`,
+ Actions: {
+ ChatList: "查看消息列表",
+ CompressedHistory: "查看压缩后的历史 Prompt",
+ Export: "导出聊天记录",
},
- Chat: {
- SubTitle: (count: number) => `与 ChatGPT 的 ${count} 条对话`,
- Actions: {
- ChatList: '查看消息列表',
- CompressedHistory: '查看压缩后的历史 Prompt',
- Export: '导出聊天记录',
- },
- Typing: '正在输入…',
- Input: (submitKey: string) => `输入消息,${submitKey} 发送`,
- Send: '发送',
+ Typing: "正在输入…",
+ Input: (submitKey: string) => `输入消息,${submitKey} 发送`,
+ Send: "发送",
+ },
+ Export: {
+ Title: "导出聊天记录为 Markdown",
+ Copy: "全部复制",
+ Download: "下载文件",
+ },
+ Memory: {
+ Title: "上下文记忆 Prompt",
+ EmptyContent: "尚未记忆",
+ Copy: "全部复制",
+ },
+ Home: {
+ NewChat: "新的聊天",
+ DeleteChat: "确认删除选中的对话?",
+ },
+ Settings: {
+ Title: "设置",
+ SubTitle: "设置选项",
+ Actions: {
+ ClearAll: "清除所有数据",
+ ResetAll: "重置所有选项",
+ Close: "关闭",
},
- Export: {
- Title: '导出聊天记录为 Markdown',
- Copy: '全部复制',
- Download: '下载文件',
+ Lang: {
+ Name: "Language",
+ Options: {
+ cn: "中文",
+ en: "English",
+ },
},
- Memory: {
- Title: '上下文记忆 Prompt',
- EmptyContent: '尚未记忆',
- Copy: '全部复制',
+ Avatar: "头像",
+ SendKey: "发送键",
+ Theme: "主题",
+ TightBorder: "紧凑边框",
+ HistoryCount: {
+ Title: "附带历史消息数",
+ SubTitle: "每次请求携带的历史消息数",
},
- Home: {
- NewChat: '新的聊天',
- DeleteChat: '确认删除选中的对话?',
+ CompressThreshold: {
+ Title: "历史消息长度压缩阈值",
+ SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
},
- Settings: {
- Title: '设置',
- SubTitle: '设置选项',
- Actions: {
- ClearAll: '清除所有数据',
- ResetAll: '重置所有选项',
- Close: '关闭',
- },
- Lang: {
- Name: 'Language',
- Options: {
- cn: '中文',
- en: 'English'
- }
- },
- Avatar: '头像',
- SendKey: '发送键',
- Theme: '主题',
- TightBorder: '紧凑边框',
- HistoryCount: '附带历史消息数',
- CompressThreshold: '历史消息长度压缩阈值',
+ Model: "模型 (model)",
+ Temperature: {
+ Title: "随机性 (temperature)",
+ SubTitle: "值越大,回复越随机",
},
- Store: {
- DefaultTopic: '新的聊天',
- BotHello: '有什么可以帮你的吗',
- Error: '出错了,稍后重试吧',
- Prompt: {
- History: (content: string) => '这是 ai 和用户的历史聊天总结作为前情提要:' + content,
- Topic: "直接返回这句话的简要主题,不要解释,如果没有主题,请直接返回“闲聊”",
- Summarize: '简要总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 50 字以内',
- },
- ConfirmClearAll: '确认清除所有聊天、设置数据?',
+ MaxTokens: {
+ Title: "单次回复限制 (max_tokens)",
+ SubTitle: "单次交互所用的最大 Token 数",
},
- Copy: {
- Success: '已写入剪切板',
- Failed: '复制失败,请赋予剪切板权限',
- }
-}
+ PresencePenlty: {
+ Title: "话题新鲜度 (presence_penalty)",
+ SubTitle: "值越大,越有可能扩展到新话题",
+ },
+ },
+ Store: {
+ DefaultTopic: "新的聊天",
+ BotHello: "有什么可以帮你的吗",
+ Error: "出错了,稍后重试吧",
+ Prompt: {
+ History: (content: string) =>
+ "这是 ai 和用户的历史聊天总结作为前情提要:" + content,
+ Topic:
+ "直接返回这句话的简要主题,不要解释,如果没有主题,请直接返回“闲聊”",
+ Summarize:
+ "简要总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 50 字以内",
+ },
+ ConfirmClearAll: "确认清除所有聊天、设置数据?",
+ },
+ Copy: {
+ Success: "已写入剪切板",
+ Failed: "复制失败,请赋予剪切板权限",
+ },
+};
export type LocaleType = typeof cn;
-export default cn;
\ No newline at end of file
+export default cn;
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 55de3afd..dde36a09 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -1,71 +1,97 @@
-import type { LocaleType } from './index'
+import type { LocaleType } from "./index";
const en: LocaleType = {
- ChatItem: {
- ChatItemCount: (count: number) => `${count} messages`,
+ ChatItem: {
+ ChatItemCount: (count: number) => `${count} messages`,
+ },
+ Chat: {
+ SubTitle: (count: number) => `${count} messages with ChatGPT`,
+ Actions: {
+ ChatList: "Go To Chat List",
+ CompressedHistory: "Compressed History Memory Prompt",
+ Export: "Export All Messages as Markdown",
},
- Chat: {
- SubTitle: (count: number) => `${count} messages with ChatGPT`,
- Actions: {
- ChatList: 'Go To Chat List',
- CompressedHistory: 'Compressed History Memory Prompt',
- Export: 'Export All Messages as Markdown',
- },
- Typing: 'Typing…',
- Input: (submitKey: string) => `Type something and press ${submitKey} to send`,
- Send: 'Send',
+ Typing: "Typing…",
+ Input: (submitKey: string) =>
+ `Type something and press ${submitKey} to send`,
+ Send: "Send",
+ },
+ Export: {
+ Title: "All Messages",
+ Copy: "Copy All",
+ Download: "Download",
+ },
+ Memory: {
+ Title: "Memory Prompt",
+ EmptyContent: "Nothing yet.",
+ Copy: "Copy All",
+ },
+ Home: {
+ NewChat: "New Chat",
+ DeleteChat: "Confirm to delete the selected conversation?",
+ },
+ Settings: {
+ Title: "Settings",
+ SubTitle: "All Settings",
+ Actions: {
+ ClearAll: "Clear All Data",
+ ResetAll: "Reset All Settings",
+ Close: "Close",
},
- Export: {
- Title: 'All Messages',
- Copy: 'Copy All',
- Download: 'Download',
+ Lang: {
+ Name: "语言",
+ Options: {
+ cn: "中文",
+ en: "English",
+ },
},
- Memory: {
- Title: 'Memory Prompt',
- EmptyContent: 'Nothing yet.',
- Copy: 'Copy All',
+ Avatar: "Avatar",
+ SendKey: "Send Key",
+ Theme: "Theme",
+ TightBorder: "Tight Border",
+ HistoryCount: {
+ Title: "Attached Messages Count",
+ SubTitle: "Number of sent messages attached per request",
},
- Home: {
- NewChat: 'New Chat',
- DeleteChat: 'Confirm to delete the selected conversation?',
+ CompressThreshold: {
+ Title: "History Compression Threshold",
+ SubTitle:
+ "Will compress if uncompressed messages length exceeds the value",
},
- Settings: {
- Title: 'Settings',
- SubTitle: 'All Settings',
- Actions: {
- ClearAll: 'Clear All Data',
- ResetAll: 'Reset All Settings',
- Close: 'Close',
- },
- Lang: {
- Name: '语言',
- Options: {
- cn: '中文',
- en: 'English'
- }
- },
- Avatar: 'Avatar',
- SendKey: 'Send Key',
- Theme: 'Theme',
- TightBorder: 'Tight Border',
- HistoryCount: 'History Message Count',
- CompressThreshold: 'Message Compression Threshold',
+ Model: "Model",
+ Temperature: {
+ Title: "Temperature",
+ SubTitle: "A larger value makes the more random output",
},
- Store: {
- DefaultTopic: 'New Conversation',
- BotHello: 'Hello! How can I assist you today?',
- Error: 'Something went wrong, please try again later.',
- Prompt: {
- History: (content: string) => 'This is a summary of the chat history between the AI and the user as a recap: ' + content,
- Topic: "Provide a brief topic of the sentence without explanation. If there is no topic, return 'Chitchat'.",
- Summarize: 'Summarize our discussion briefly in 50 characters or less to use as a prompt for future context.',
- },
- ConfirmClearAll: 'Confirm to clear all chat and setting data?',
+ MaxTokens: {
+ Title: "Max Tokens",
+ SubTitle: "Maximum length of input tokens and generated tokens",
},
- Copy: {
- Success: 'Copied to clipboard',
- Failed: 'Copy failed, please grant permission to access clipboard',
- }
-}
+ PresencePenlty: {
+ Title: "Presence Penalty",
+ SubTitle:
+ "A larger value increases the likelihood to talk about new topics",
+ },
+ },
+ Store: {
+ DefaultTopic: "New Conversation",
+ BotHello: "Hello! How can I assist you today?",
+ Error: "Something went wrong, please try again later.",
+ Prompt: {
+ History: (content: string) =>
+ "This is a summary of the chat history between the AI and the user as a recap: " +
+ content,
+ Topic:
+ "Provide a brief topic of the sentence without explanation. If there is no topic, return 'Chitchat'.",
+ Summarize:
+ "Summarize our discussion briefly in 50 characters or less to use as a prompt for future context.",
+ },
+ ConfirmClearAll: "Confirm to clear all chat and setting data?",
+ },
+ Copy: {
+ Success: "Copied to clipboard",
+ Failed: "Copy failed, please grant permission to access clipboard",
+ },
+};
-export default en;
\ No newline at end of file
+export default en;
diff --git a/app/requests.ts b/app/requests.ts
index 2d1c4609..87f780b3 100644
--- a/app/requests.ts
+++ b/app/requests.ts
@@ -1,7 +1,7 @@
import type { ChatRequest, ChatReponse } from "./api/chat/typing";
-import { Message } from "./store";
+import { filterConfig, isValidModel, Message, ModelConfig } from "./store";
-const TIME_OUT_MS = 30000
+const TIME_OUT_MS = 30000;
const makeRequestParam = (
messages: Message[],
@@ -44,6 +44,7 @@ export async function requestChatStream(
messages: Message[],
options?: {
filterBot?: boolean;
+ modelConfig?: ModelConfig;
onMessage: (message: string, done: boolean) => void;
onError: (error: Error) => void;
}
@@ -53,6 +54,13 @@ export async function requestChatStream(
filterBot: options?.filterBot,
});
+ // valid and assign model config
+ if (options?.modelConfig) {
+ Object.assign(req, filterConfig(options.modelConfig));
+ }
+
+ console.log("[Request] ", req);
+
const controller = new AbortController();
const reqTimeoutId = setTimeout(() => controller.abort(), TIME_OUT_MS);
diff --git a/app/store.ts b/app/store.ts
index a0808b12..739c052d 100644
--- a/app/store.ts
+++ b/app/store.ts
@@ -5,7 +5,7 @@ import { type ChatCompletionResponseMessage } from "openai";
import { requestChatStream, requestWithPrompt } from "./requests";
import { trimTopic } from "./utils";
-import Locale from './locales'
+import Locale from "./locales";
export type Message = ChatCompletionResponseMessage & {
date: string;
@@ -26,7 +26,7 @@ export enum Theme {
}
export interface ChatConfig {
- maxToken?: number
+ maxToken?: number;
historyMessageCount: number; // -1 means all
compressMessageLengthThreshold: number;
sendBotMessages: boolean; // send bot's message or not
@@ -34,6 +34,78 @@ export interface ChatConfig {
avatar: string;
theme: Theme;
tightBorder: boolean;
+
+ modelConfig: {
+ model: string;
+ temperature: number;
+ max_tokens: number;
+ presence_penalty: number;
+ };
+}
+
+export type ModelConfig = ChatConfig["modelConfig"];
+
+export const ALL_MODELS = [
+ {
+ name: "gpt-4",
+ available: false,
+ },
+ {
+ name: "gpt-4-0314",
+ available: false,
+ },
+ {
+ name: "gpt-4-32k",
+ available: false,
+ },
+ {
+ name: "gpt-4-32k-0314",
+ available: false,
+ },
+ {
+ name: "gpt-3.5-turbo",
+ available: true,
+ },
+ {
+ name: "gpt-3.5-turbo-0301",
+ available: true,
+ },
+];
+
+export function isValidModel(name: string) {
+ return ALL_MODELS.some((m) => m.name === name && m.available);
+}
+
+export function isValidNumber(x: number, min: number, max: number) {
+ return typeof x === "number" && x <= max && x >= min;
+}
+
+export function filterConfig(config: ModelConfig): Partial {
+ const validator: {
+ [k in keyof ModelConfig]: (x: ModelConfig[keyof ModelConfig]) => boolean;
+ } = {
+ model(x) {
+ return isValidModel(x as string);
+ },
+ max_tokens(x) {
+ return isValidNumber(x as number, 100, 4000);
+ },
+ presence_penalty(x) {
+ return isValidNumber(x as number, -2, 2);
+ },
+ temperature(x) {
+ return isValidNumber(x as number, 0, 1);
+ },
+ };
+
+ Object.keys(validator).forEach((k) => {
+ const key = k as keyof ModelConfig;
+ if (!validator[key](config[key])) {
+ delete config[key];
+ }
+ });
+
+ return config;
}
const DEFAULT_CONFIG: ChatConfig = {
@@ -44,6 +116,13 @@ const DEFAULT_CONFIG: ChatConfig = {
avatar: "1f603",
theme: Theme.Auto as Theme,
tightBorder: false,
+
+ modelConfig: {
+ model: "gpt-3.5-turbo",
+ temperature: 1,
+ max_tokens: 2000,
+ presence_penalty: 0,
+ },
};
export interface ChatStat {
@@ -107,7 +186,7 @@ interface ChatStore {
updater: (message?: Message) => void
) => void;
getMessagesWithMemory: () => Message[];
- getMemoryPrompt: () => Message,
+ getMemoryPrompt: () => Message;
getConfig: () => ChatConfig;
resetConfig: () => void;
@@ -193,9 +272,9 @@ export const useChatStore = create()(
},
onNewMessage(message) {
- get().updateCurrentSession(session => {
- session.lastUpdate = new Date().toLocaleString()
- })
+ get().updateCurrentSession((session) => {
+ session.lastUpdate = new Date().toLocaleString();
+ });
get().updateStat(message);
get().summarizeSession();
},
@@ -214,9 +293,9 @@ export const useChatStore = create()(
streaming: true,
};
- // get recent messages
- const recentMessages = get().getMessagesWithMemory()
- const sendMessages = recentMessages.concat(userMessage)
+ // get recent messages
+ const recentMessages = get().getMessagesWithMemory();
+ const sendMessages = recentMessages.concat(userMessage);
// save user's and bot's message
get().updateCurrentSession((session) => {
@@ -224,12 +303,12 @@ export const useChatStore = create()(
session.messages.push(botMessage);
});
- console.log('[User Input] ', sendMessages)
+ console.log("[User Input] ", sendMessages);
requestChatStream(sendMessages, {
onMessage(content, done) {
if (done) {
botMessage.streaming = false;
- get().onNewMessage(botMessage)
+ get().onNewMessage(botMessage);
} else {
botMessage.content = content;
set(() => ({}));
@@ -241,32 +320,35 @@ export const useChatStore = create()(
set(() => ({}));
},
filterBot: !get().config.sendBotMessages,
+ modelConfig: get().config.modelConfig,
});
},
getMemoryPrompt() {
- const session = get().currentSession()
+ const session = get().currentSession();
return {
- role: 'system',
+ role: "system",
content: Locale.Store.Prompt.History(session.memoryPrompt),
- date: ''
- } as Message
+ date: "",
+ } as Message;
},
getMessagesWithMemory() {
- const session = get().currentSession()
- const config = get().config
- const n = session.messages.length
- const recentMessages = session.messages.slice(n - config.historyMessageCount);
+ const session = get().currentSession();
+ const config = get().config;
+ const n = session.messages.length;
+ const recentMessages = session.messages.slice(
+ n - config.historyMessageCount
+ );
- const memoryPrompt = get().getMemoryPrompt()
+ const memoryPrompt = get().getMemoryPrompt();
if (session.memoryPrompt) {
- recentMessages.unshift(memoryPrompt)
+ recentMessages.unshift(memoryPrompt);
}
- return recentMessages
+ return recentMessages;
},
updateMessage(
@@ -286,49 +368,63 @@ export const useChatStore = create()(
if (session.topic === DEFAULT_TOPIC && session.messages.length >= 3) {
// should summarize topic
- requestWithPrompt(
- session.messages,
- Locale.Store.Prompt.Topic
- ).then((res) => {
- get().updateCurrentSession(
- (session) => (session.topic = trimTopic(res))
- );
- });
+ requestWithPrompt(session.messages, Locale.Store.Prompt.Topic).then(
+ (res) => {
+ get().updateCurrentSession(
+ (session) => (session.topic = trimTopic(res))
+ );
+ }
+ );
}
- const config = get().config
- let toBeSummarizedMsgs = session.messages.slice(session.lastSummarizeIndex)
- const historyMsgLength = toBeSummarizedMsgs.reduce((pre, cur) => pre + cur.content.length, 0)
+ const config = get().config;
+ let toBeSummarizedMsgs = session.messages.slice(
+ session.lastSummarizeIndex
+ );
+ const historyMsgLength = toBeSummarizedMsgs.reduce(
+ (pre, cur) => pre + cur.content.length,
+ 0
+ );
if (historyMsgLength > 4000) {
- toBeSummarizedMsgs = toBeSummarizedMsgs.slice(-config.historyMessageCount)
+ toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
+ -config.historyMessageCount
+ );
}
// add memory prompt
- toBeSummarizedMsgs.unshift(get().getMemoryPrompt())
+ toBeSummarizedMsgs.unshift(get().getMemoryPrompt());
- const lastSummarizeIndex = session.messages.length
+ const lastSummarizeIndex = session.messages.length;
- console.log('[Chat History] ', toBeSummarizedMsgs, historyMsgLength, config.compressMessageLengthThreshold)
+ console.log(
+ "[Chat History] ",
+ toBeSummarizedMsgs,
+ historyMsgLength,
+ config.compressMessageLengthThreshold
+ );
if (historyMsgLength > config.compressMessageLengthThreshold) {
- requestChatStream(toBeSummarizedMsgs.concat({
- role: 'system',
- content: Locale.Store.Prompt.Summarize,
- date: ''
- }), {
- filterBot: false,
- onMessage(message, done) {
- session.memoryPrompt = message
- if (done) {
- console.log('[Memory] ', session.memoryPrompt)
- session.lastSummarizeIndex = lastSummarizeIndex
- }
- },
- onError(error) {
- console.error('[Summarize] ', error)
- },
- })
+ requestChatStream(
+ toBeSummarizedMsgs.concat({
+ role: "system",
+ content: Locale.Store.Prompt.Summarize,
+ date: "",
+ }),
+ {
+ filterBot: false,
+ onMessage(message, done) {
+ session.memoryPrompt = message;
+ if (done) {
+ console.log("[Memory] ", session.memoryPrompt);
+ session.lastSummarizeIndex = lastSummarizeIndex;
+ }
+ },
+ onError(error) {
+ console.error("[Summarize] ", error);
+ },
+ }
+ );
}
},
@@ -348,8 +444,8 @@ export const useChatStore = create()(
clearAllData() {
if (confirm(Locale.Store.ConfirmClearAll)) {
- localStorage.clear()
- location.reload()
+ localStorage.clear();
+ location.reload();
}
},
}),