NotesWhat is notes.io?

Notes brand slogan

Notes - notes.io

<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="description" content="Fast & Powerful LLM Webapp">
<meta name="theme-color" content="#edeff2">
<meta name="apple-mobile-web-app-status-bar-style" content="#edeff2">
<meta name="msapplication-TileColor" content="#edeff2">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="msapplication-TileImage" content="icon.png">
<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
<link rel="icon" type="image/png" href="icon.png">
<link rel="apple-touch-icon" href="icon.png" sizes="144x144">
<title>ChatGPT</title>
<style>
.requestBody,
.response .markdown-body {
max-width: calc(100% - 88px);
}

.bottom_wrapper {
max-width: 100%;
}

* {
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji" !important;
}

html {
width: 100%;
height: 100%;
}

:root {
--background: #edeff2;
--chat-back: #fff;
--main-back: #f6f6f6;
--active-btn: #e0e0e0;
--lighter-active: #eaeaea;
--sel-btn: #d0d0d0;
--btn-color: #404040;
--text-color: #909090;
--chat-text-color: #24292f;
--response-color: #f7f7f8;
--lighter-active-color: #e8e8e8;
--lighter-text-color: #555;
--svg-color: #808080;
--lighter-svg-color: #c0c0c0;
--code-color: #f0f0f0;
--black-color: #000;
}

[data-theme="dark"] {
--background: #1f1f1f;
--chat-back: #3c3c3c;
--main-back: #333;
--active-btn: #1f1f1f;
--lighter-active: #151515;
--sel-btn: #1e1e1e;
--btn-color: #bfbfbf;
--text-color: #8f8f8f;
--chat-text-color: #c9d1d9;
--response-color: #2f2f2f;
--lighter-active-color: #171717;
--lighter-text-color: #aaa;
--svg-color: #7f7f7f;
--lighter-svg-color: #3f3f3f;
--code-color: #101010;
--black-color: #fff;
}

body {
background-color: var(--background);
width: 100%;
height: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}

.chat_window {
position: absolute;
width: 100%;
max-width: 1188px;
height: 100%;
max-height: 888px;
border-radius: 8px;
background-color: var(--chat-back);
overflow: hidden;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
}

.overlay {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, .3);
z-index: 90;
cursor: pointer;
visibility: hidden;
opacity: 0;
-webkit-tap-highlight-color: transparent;
transition: all 250ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

@media screen and (max-width: 1188px) and (max-height: 888px) {
#toggleFull {
display: none;
}
}

@media screen and (min-width: 800px) {
.chat_window {
display: flex;
}

.mainContent {
width: calc(100% - 250px);
}

.nav {
position: relative;
margin-left: -250px;
transition: margin-left 250ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.show-nav .nav {
margin-left: 0;
}

#sysDialog {
max-width: 600px;
}

.sysContent {
display: flex;
}

.sysSwitch {
flex-shrink: 0;
width: 160px;
}

.sysSwitch>div {
padding-left: 6px;
}

.sysDetail {
margin-left: 12px;
margin-top: 5px;
}

.setNotNormalFlow {
width: calc(100% - 212px);
}
}

@media screen and (max-width: 800px) {
.chat_window {
display: block;
}

.mainContent {
width: 100%;
}

.nav {
position: absolute !important;
left: -250px;
transition: left 250ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

.show-nav .nav {
left: 0;
box-shadow: rgba(0, 0, 0, 0.2) 0px 8px 10px -5px, rgba(0, 0, 0, 0.14) 0px 16px 24px 2px, rgba(0, 0, 0, 0.12) 0px 6px 30px 5px;
}

.show-nav .overlay {
visibility: visible;
opacity: 1;
}

#sysDialog {
max-width: 400px;
}

.sysSwitch {
display: flex;
}

.sysSwitch>div {
width: 50%;
justify-content: center;
}

.sysDetail {
margin-top: 8px;
}

.setNotNormalFlow {
width: calc(100% - 40px);
}
}

.full_window {
max-width: none;
max-height: none;
}

.nav {
width: 250px;
height: 100%;
border-right: 1px solid var(--active-btn);
background-color: var(--main-back);
top: 0;
z-index: 99;
flex-shrink: 0;
display: flex;
flex-direction: column;
}

.mainContent {
height: 100%;
position: relative;
display: flex;
flex-direction: column;
flex: 1;
}

.top_menu {
background-color: var(--main-back);
width: 100%;
height: 50px;
padding: 5px 0;
}

.top_menu .toggler {
margin-left: 10px;
width: 40px;
height: 40px;
float: left;
padding: 5px 7px;
border-radius: 4px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}

.top_menu .toggler:hover {
background: var(--active-btn);
}

.top_menu .toggler .button {
width: 26px;
height: 4px;
border-radius: 4px;
position: absolute;
pointer-events: none;
}

.top_menu .toggler .button.close {
margin-top: 3px;
background-color: #99c959;
}

.top_menu .toggler .button.minimize {
margin-top: 13px;
background-color: #f8b26a;
}

.top_menu .toggler .button.maximize {
margin-top: 23px;
background-color: #e15b64;
}

.top_menu .title {
color: var(--text-color);
font-size: 20px;
height: 40px;
line-height: 40px;
position: relative;
pointer-events: none;
}

.title>span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

.messages {
position: relative;
flex: 1;
overflow-x: hidden;
overflow-y: auto;
font-size: 16px;
color: var(--chat-text-color);
text-align: center;
}

.messages::-webkit-scrollbar,
#chatlog .markdown-body>pre>code::-webkit-scrollbar,
#setDialog::-webkit-scrollbar,
.allList::-webkit-scrollbar,
.sysDetail::-webkit-scrollbar,
#apiSelect::-webkit-scrollbar {
width: 10px;
height: 10px;
}

.messages::-webkit-scrollbar-track,
#chatlog .markdown-body>pre>code::-webkit-scrollbar-track,
#setDialog::-webkit-scrollbar-track,
.allList::-webkit-scrollbar-track,
.sysDetail::-webkit-scrollbar-track,
#apiSelect::-webkit-scrollbar-track {
background-clip: padding-box;
background: transparent;
border: none;
}

.messages::-webkit-scrollbar-corner,
#chatlog .markdown-body>pre>code::-webkit-scrollbar-corner,
#setDialog::-webkit-scrollbar-corner,
.allList::-webkit-scrollbar-corner,
.sysDetail::-webkit-scrollbar-corner,
#apiSelect::-webkit-scrollbar-corner {
background-color: transparent;
}

.messages::-webkit-scrollbar-thumb,
#chatlog .markdown-body>pre>code::-webkit-scrollbar-thumb,
#setDialog::-webkit-scrollbar-thumb,
.allList::-webkit-scrollbar-thumb,
.sysDetail::-webkit-scrollbar-thumb,
#apiSelect::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.1);
background-clip: padding-box;
border: solid transparent;
border-radius: 10px;
}

.messages::-webkit-scrollbar-thumb:hover,
#chatlog .markdown-body>pre>code::-webkit-scrollbar-thumb:hover,
#setDialog::-webkit-scrollbar-thumb:hover,
.allList::-webkit-scrollbar-thumb:hover,
.sysDetail::-webkit-scrollbar-thumb:hover,
#apiSelect::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.4);
}

#chatlog {
word-wrap: break-word;
text-align: start;
}

.chatAvatar {
margin: 14px 14px 13px 13px;
width: 30px;
height: 30px;
flex-shrink: 0;
border-radius: 2px;
}

.response>.chatAvatar {
margin-top: 15px;
margin-bottom: 15px;
}

.response .gpt3Avatar {
background: #19c37d;
}

.response .gpt4Avatar {
background: #ab68ff;
}

.chatAvatar>img {
display: block;
width: 100%;
height: 100%;
border-radius: 2px;
}

.chatAvatar>svg {
display: block;
margin-top: 4px;
margin-left: 4px;
}

#chatlog .request {
position: relative;
display: flex;
justify-content: center;
}

.requestBody {
white-space: pre-wrap;
margin: 18px 0;
flex: 1;
}

#chatlog .response {
background: var(--response-color);
position: relative;
display: flex;
justify-content: center;
}

.response .markdown-body {
margin: 18px 0;
flex: 1;
background: var(--response-color) !important;
}

#chatlog .markdown-body>pre {
padding: 10px;
position: relative;
background: var(--code-color);
}

#chatlog .markdown-body>pre>code {
overflow-x: auto;
display: block;
}

.m-mdic-copy-wrapper {
position: absolute;
top: 5px;
right: 16px;
-webkit-user-select: none;
user-select: none;
}

.m-mdic-copy-wrapper span.u-mdic-copy-code_lang {
position: absolute;
top: 3px;
right: calc(100% + 4px);
font-family: system-ui;
font-size: 12px;
line-height: 18px;
color: #bbb;
}

.m-mdic-copy-wrapper div.u-mdic-copy-notify {
position: absolute;
top: 0;
right: 0;
padding: 3px 6px;
border: 0;
border-radius: 3px;
background: none;
font-family: system-ui;
font-size: 12px;
line-height: 18px;
color: var(--lighter-text-color);
outline: none;
right: 100%;
padding-right: 4px;
}

.m-mdic-copy-wrapper button.u-mdic-copy-btn {
position: relative;
top: 0;
right: 0;
padding: 3px 6px;
border: 0;
border-radius: 3px;
background: none;
font-family: system-ui;
font-size: 12px;
line-height: 18px;
color: #bbb;
outline: none;
cursor: pointer;
transition: color 250ms;
}

.m-mdic-copy-wrapper button.u-mdic-copy-btn:hover {
color: var(--lighter-text-color);
}

#stopChat {
display: none;
margin: 0 auto;
margin-top: 3px;
width: 80px;
height: 32px;
text-align: center;
line-height: 32px;
color: white;
background: #f8b26a;
cursor: pointer;
border-radius: 3px;
position: sticky;
bottom: 2px;
justify-content: center;
align-items: center;
}

#stopChat>svg {
margin-right: 8px;
}

#stopChat:hover {
background: #f0aa60;
}

.bottom_wrapper {
position: relative;
width: 100%;
padding: 10px 10px;
margin: 0 auto;
}

.bottom_wrapper .message_input_wrapper {
border: none;
width: calc(100% - 139px);
position: relative;
text-align: left;
}

.bottom_wrapper .message_input_wrapper .message_input_text {
border-radius: 4px;
border: none;
outline: none;
resize: none;
background-color: var(--main-back);
color: var(--chat-text-color);
height: 47px;
font-size: 16px;
max-height: 200px;
padding: 13px 0 13px 13px;
width: 100%;
display: block;
transition: background-color 250ms;
}

.bottom_wrapper .message_input_wrapper .message_input_text:focus {
background-color: var(--code-color);
}

.bottom_wrapper .message_input_wrapper .message_input_text::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}

#sendbutton {
width: 80px;
height: 47px;
font-size: 18px;
font-weight: bold;
border-radius: 3px;
background-color: #b8da8b;
border: none;
padding: 0;
color: #fff;
cursor: pointer;
transition: all 250ms;
text-align: center;
float: right;
position: absolute;
right: 63px;
bottom: 10px;
cursor: not-allowed;
}

.activeSendBtn {
background-color: #99c959 !important;
cursor: pointer !important;
}

.activeSendBtn:hover {
background-color: #90c050 !important;
}

.clearConv {
position: absolute;
right: 10px;
bottom: 10px;
width: 47px;
height: 47px;
border-radius: 3px;
background: var(--text-color);
border: none;
color: #fff;
cursor: pointer;
}

.clearConv>svg {
margin: 0 auto;
}

.clearConv:hover {
background: var(--svg-color);
}

.clearConv svg:first-child {
display: none;
}

.clearConv svg:nth-child(2) {
display: block;
}

.closeConv {
background: var(--active-btn);
}

.closeConv:hover {
background: var(--lighter-active-color);
}

.closeConv svg:first-child {
display: block;
}

.closeConv svg:nth-child(2) {
display: none;
}

.loaded>span {
display: inline-block;
}

.loaded>svg {
display: none;
}

.loading {
background: var(--active-btn) !important;
}

.loading>span {
display: none;
}

.loading>svg {
display: block;
}

.switch-slide {
display: inline-block;
vertical-align: middle;
}

.switch-slide-label {
display: block;
width: 38px;
height: 18px;
background: var(--text-color);
border-radius: 30px;
cursor: pointer;
position: relative;
-webkit-transition: all 250ms;
transition: all 250ms;
}

.switch-slide-label:after {
content: "";
display: block;
width: 16px;
height: 16px;
border-radius: 100%;
background: #fff;
box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
position: absolute;
left: 1px;
top: 1px;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-transition: all 250ms;
transition: all 250ms;
}

.switch-slide input:checked+label {
background: #99c959;
-webkit-transition: all 250ms;
transition: all 250ms;
}

.switch-slide input:checked+label:after {
left: 21px;
}

.settings {
margin-right: 10px;
display: flex;
position: absolute;
right: 0;
top: 5px;
}

.setBtn {
margin-left: 2px;
cursor: pointer;
padding: 5px;
border: none;
background-color: transparent;
border-radius: 4px;
}

.setBtn>svg {
display: block;
color: var(--text-color);
}

.setBtn:hover {
background: var(--active-btn);
}

#setting {
right: 15px;
}

#toggleFull {
right: 56px;
}

#toggleLight *,
#toggleFull * {
pointer-events: none;
}

.showSetting {
background: var(--lighter-svg-color) !important;
}

#setDialog {
color: var(--btn-color);
position: absolute;
z-index: 2;
background: var(--main-back);
width: 320px;
right: 6px;
top: 46px;
overflow-y: auto;
max-height: calc(100% - 55px);
-webkit-user-select: none;
user-select: none;
border-radius: 5px;
padding: 8px 12px 8px 12px;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.15);
}

#setDialog input {
width: 100%;
}

#setDialog .inlineTitle {
display: inline-block;
width: 88px;
line-height: 16px;
vertical-align: middle;
}

#convOption,
#speechOption,
#speechDetail,
#recOption {
margin-bottom: 6px;
}

#convOption>div,
#speechOption>div,
#speechDetail>div,
#recOption div {
margin-top: 8px;
}

#voiceRecSetting select,
#speechDetail select {
background: var(--chat-back);
color: var(--chat-text-color);
}

.inputTextClass {
outline: none;
border-radius: 2px;
margin-top: 3px;
height: 32px;
font-size: 15px;
padding-left: 6px;
background: var(--chat-back);
color: var(--chat-text-color);
border: none;
}

.areaTextClass {
width: 100%;
height: 80px;
display: block;
resize: none;
padding: 6px;
}

input[type="range"] {
-webkit-appearance: none;
appearance: none;
display: block;
margin: 4px 0 3px 0;
height: 8px;
background: var(--text-color);
border-radius: 5px;
background-image: linear-gradient(#99c959, #99c959);
background-size: 100% 100%;
background-repeat: no-repeat;
}

input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 15px;
width: 15px;
border-radius: 50%;
background: #99c959;
cursor: ew-resize;
}

input[type=range]::-webkit-slider-runnable-track {
-webkit-appearance: none;
box-shadow: none;
border: none;
background: transparent;
}

.justSetLine {
display: flex;
justify-content: space-between;
}

.justSetBtn {
height: 32px;
border-radius: 3px;
line-height: 32px;
background: var(--lighter-active);
text-align: center;
padding: 0px 8px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}

.justSetBtn:hover {
background-color: var(--sel-btn);
}

.justSetBtn>svg {
margin-right: 3px;
}

.readyTestVoice>div:not(:first-child) {
display: none;
}

.pauseTestVoice>div:nth-child(1),
.pauseTestVoice>div:nth-child(3) {
display: none;
}

.resumeTestVoice>div:nth-child(1),
.resumeTestVoice>div:nth-child(2) {
display: none;
}

.presetSelect>div {
display: inline-block;
}

.presetSelect select {
outline: none;
border-radius: 3px;
width: 128px;
border-color: rgba(0, 0, 0, .3);
background: var(--chat-back);
color: var(--chat-text-color);
}

.selectDef {
display: flex;
justify-content: space-between;
font-size: 13px;
color: var(--text-color);
}

#preSetSpeech {
width: 100%;
outline: none;
height: 30px;
font-size: 14px;
margin-top: 5px;
border-radius: 3px;
border-color: rgba(0, 0, 0, .3);
}

.mdOption {
flex-shrink: 0;
position: relative;
width: 30px;
pointer-events: none;
}

.mdOption>div {
pointer-events: auto;
cursor: pointer;
}

.mdOption svg * {
pointer-events: none;
}

.refreshReq svg:not(:first-child) {
display: none;
}

.halfRefReq svg:not(:nth-child(2)) {
display: none;
}

.optionItems {
position: absolute;
bottom: -12px;
display: flex;
justify-content: space-between;
visibility: hidden;
z-index: 1;
color: var(--svg-color);
}

.optionItems:hover {
visibility: visible;
}

.request:hover .optionItems,
.request:hover .voiceCls,
.response:hover .optionItems,
.response:hover .voiceCls {
visibility: visible;
}

.optionItem {
border-radius: 10px;
height: 24px;
width: 32px;
border: 1px solid var(--active-btn);
background-color: var(--response-color);
display: flex !important;
justify-content: center;
align-items: center;
}

.optionItem * {
pointer-events: none;
}

.optionItem:hover {
background: var(--active-btn);
}

.voiceCls {
position: relative;
height: 100%;
visibility: hidden;
display: flex;
align-items: center;
}

.voiceCls>svg {
color: var(--lighter-svg-color);
display: block;
margin-left: 5px;
position: relative;
}

.voiceCls:hover>svg {
color: var(--svg-color);
}

.showVoiceCls,
.showVoiceCls .markdown-body {
background: var(--active-btn) !important;
}

.showVoiceCls .voiceCls {
visibility: visible !important;
}

.showEditReq {
position: sticky !important;
top: 0;
bottom: 0;
z-index: 1;
}

.showEditReq,
.showEditReq .markdown-body {
background: var(--active-btn) !important;
}

.readyVoice svg:not(:first-child) {
display: none;
}

.pauseVoice svg:not(:nth-child(2)) {
display: none;
}

.resumeVoice svg:not(:nth-child(3)) {
display: none;
}

#voiceTypes>span {
border-radius: 3px;
margin-left: 4px;
cursor: pointer;
padding: 1px 5px;
}

#voiceTypes>span:hover {
background: var(--active-btn);
}

.selVoiceType {
background: var(--sel-btn) !important;
}

.navHeader {
width: 100%;
padding: 5px 10px;
display: flex;
justify-content: space-between;
}

#newChat {
text-align: center;
width: 80%;
height: 40px;
border-radius: 3px;
background: var(--lighter-active-color);
color: var(--btn-color);
display: flex;
align-items: center;
justify-content: center;
-webkit-user-select: none;
user-select: none;
cursor: pointer;
flex: 1;
}

.navHeader>div:hover {
background: var(--active-btn) !important;
}

#newChat>svg {
margin-right: 2px;
}

#newFolder {
height: 40px;
width: 40px;
margin-left: 10px;
border-radius: 3px;
color: var(--btn-color);
cursor: pointer;
position: relative;
background: var(--lighter-active-color);
-webkit-user-select: none;
user-select: none;
}

#newFolder>svg {
display: block;
margin: 8px auto;
}

.extraChat {
padding: 2px 10px 6px 10px;
position: relative;
}

#searchChat {
width: 100%;
height: 36px;
padding-left: 10px;
padding-right: 16px;
font-size: 16px;
outline: none;
border: none;
color: var(--chat-text-color);
background: var(--lighter-active-color);
border-radius: 3px;
}

#searchChat:focus {
background: var(--active-btn)
}

#searchChat:placeholder-shown+#clearSearch {
display: none;
}

#clearSearch {
position: absolute;
right: 12px;
top: 8px;
cursor: pointer;
color: var(--btn-color);
}

#clearSearch:hover {
color: var(--black-color);
}

#clearSearch>svg {
display: block;
}

.divider {
width: 100%;
border-top: 1px solid var(--active-btn);
margin: 4px 0;
}

.navFooter {
padding-bottom: 8px;
}

.navFunc {
padding-top: 5px;
display: flex;
justify-content: space-around;
}

.navFunc svg {
display: block;
}

.navFunc>div,
.navFunc>label {
border-radius: 20px;
text-align: center;
padding: 8px 8px;
color: var(--btn-color);
font-size: 14px;
cursor: pointer;
}

.navFunc>div:hover,
.navFunc>label:hover {
background: var(--active-btn);
}

.links {
text-align: center;
}

.links a {
color: var(--btn-color);
text-decoration: none;
}

.links a:hover {
color: var(--chat-text-color) !important;
}

.links a:visited {
color: var(--btn-color);
}

.allList {
width: 100%;
position: relative;
flex: 1;
overflow-y: auto;
}

#chatList {
min-height: 50px;
}

.dragingLi {
filter: brightness(90%);
}

.dragingChat {
background: var(--lighter-active-color);
}

.expandFolder>.headLi>svg {
transform: rotate(90deg);
}

.expandFolder>.chatsInFolder {
display: block;
}

.chatsInFolder {
display: none;
margin-left: 22px;
padding-left: 2px;
border-left: 1px solid var(--text-color);
}

.headLi,
.chatLi {
cursor: pointer;
width: 100%;
height: 50px;
color: var(--text-color);
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}

.headLi *,
.chatLi * {
pointer-events: none;
}

.headLi>svg,
.chatLi>svg {
margin-left: 10px;
color: var(--btn-color);
pointer-events: none;
}

.folderOption svg,
.chatOption svg {
pointer-events: auto;
}

.headLi svg *,
.chatLi svg * {
pointer-events: none;
}

.headLi .folderInfo {
position: absolute;
left: 40px;
height: 40px;
max-width: calc(100% - 115px);
}

.chatLi .chatInfo {
position: absolute;
left: 40px;
height: 40px;
max-width: calc(100% - 90px);
}

.folderInfo *,
.chatInfo * {
-webkit-user-select: none;
user-select: none;
}

.chatInfo span {
background: #f8b26a;
}

.headLi .folderName,
.chatLi .chatName {
color: var(--btn-color);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
line-height: 20px;
height: 20px;
}

.headLi .folderNum,
.chatLi .chatPre {
color: var(--btn-color);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-size: 12px;
line-height: 20px;
height: 20px;
}

.headLi .folderOption,
.chatLi .chatOption {
visibility: hidden;
display: flex;
color: #777;
margin-right: 2px;
}

.folderLi .chatLi>svg {
margin-left: 5px;
}

.folderLi .chatLi .chatInfo {
left: 35px;
max-width: calc(100% - 85px);
}

.folderLi .chatLi #activeChatEdit {
left: 32px;
width: calc(100% - 60px)
}

.folderLi:hover {
background: var(--lighter-active);
}

.chatLi:hover {
background: var(--active-btn);
}

.headLi:hover .folderOption,
.chatLi:hover .chatOption {
visibility: visible !important;
}

.activeFolder,
.activeChatLi {
background: var(--sel-btn) !important;
}

.activeChatLi .chatOption {
visibility: visible !important;
}

.folderOption>svg:hover,
.chatOption>svg:hover {
color: #444;
}

#activeChatEdit {
position: absolute;
left: 37px;
font-size: 16px;
border-radius: 2px;
color: var(--chat-text-color);
background: var(--chat-back);
outline: none;
border: none;
pointer-events: auto;
height: 24px;
line-height: 24px;
width: calc(100% - 65px);
padding: 20px 3px;
z-index: 1;
}

#loadMask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background-color: var(--background);
}

#loadMask>div {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
}

#loadMask svg {
width: 160px;
height: 80px;
}

#loadMask>div>div {
font-size: 40px;
color: var(--text-color);
}

#voiceRec {
position: absolute;
right: 0;
top: 0;
width: 47px;
height: 100%;
}

.message_if_voice {
padding-right: 47px !important;
}

#voiceRecIcon {
width: 100%;
height: 100%;
text-align: center;
cursor: pointer;
position: relative;
}

#voiceRecIcon:hover>svg {
color: var(--svg-color);
}

#voiceRecIcon>svg {
width: 25px;
height: 25px;
color: #b0b0b0;
position: absolute;
top: 50%;
left: 50%;
margin-top: -12px;
margin-left: -13px;
}

#voiceRecIcon>svg .animVoice {
display: none;
}

.voiceRecing>svg {
color: #99c959 !important;
}

.voiceRecing .animVoice {
display: inline !important;
transform-origin: 0 64%;
animation-duration: 1.5s;
animation-name: scaleVoice;
animation-timing-function: ease;
animation-iteration-count: infinite;
}

.voiceLong .animVoice {
display: inline !important;
transform-origin: 0 64%;
animation-duration: 0.3s;
animation-name: longVoice;
animation-timing-function: ease-in-out;
animation-iteration-count: 1;
}

@keyframes longVoice {
0% {
transform: scaleY(0);
}

100% {
transform: scaleY(1);
}
}

@keyframes scaleVoice {
0% {
transform: scaleY(0.28);
}

20% {
transform: scaleY(0.60);
}

28% {
transform: scaleY(0.28);
}

36% {
transform: scaleY(0.45);
}

44% {
transform: scaleY(0.28);
}

52% {
transform: scaleY(0.45);
}

62% {
transform: scaleY(0.80);
}

72% {
transform: scaleY(0.80);
}

90% {
transform: scaleY(0.28);
}

100% {
transform: scaleY(0.28);
}
}

#voiceRecSetting {
display: none;
position: absolute;
top: -70px;
left: -26px;
z-index: 1;
padding: 4px 4px;
-webkit-user-select: none;
user-select: none;
border-radius: 3px;
background-color: var(--main-back);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.15);
}

#voiceRecSetting select {
width: 102px;
outline: none;
height: 28px;
border-radius: 3px;
border-color: rgba(0, 0, 0, .3);
}

.presetModelCls label {
margin-right: 8px;
}

.presetModelCls select {
height: 30px;
margin-top: 2px;
font-size: 15px;
}

.setSwitch {
display: flex;
}

.setSwitch>div {
border-radius: 3px;
width: calc(100% / 3);
height: 32px;
line-height: 32px;
text-align: center;
cursor: pointer;
}

.setSwitch>div:hover {
background-color: var(--active-btn);
}

.activeSwitch {
background-color: var(--sel-btn) !important;
}

#checkVoiceLoad {
height: 32px;
border-radius: 3px;
line-height: 32px;
background: var(--sel-btn);
text-align: center;
display: flex;
justify-content: center;
cursor: pointer;
}

#checkVoiceLoad:hover {
background: var(--lighter-svg-color);
}

.voiceChecking {
background-color: var(--lighter-svg-color) !important;
}

.voiceChecking>svg {
display: inline !important;
}

#checkVoiceLoad>svg {
display: none;
margin-right: 8px;
height: 32px;
width: 64px;
}

#preSetSystem {
height: 20px;
line-height: 20px;
vertical-align: top;
}

#sysMask {
display: none;
position: fixed;
z-index: 200;
top: 0;
left: 0;
bottom: 0;
right: 0;
cursor: pointer;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, .4);
-webkit-tap-highlight-color: transparent;
}

#sysDialog {
position: relative;
background: var(--chat-back);
color: var(--btn-color);
cursor: auto;
max-height: 100%;
width: 88%;
display: flex;
flex-direction: column;
border-radius: 4px;
padding: 12px 20px 12px 20px;
}

.sysTitle {
font-size: 20px;
font-weight: bold;
margin-bottom: 8px;
}

.sysSwitch,
.sysSwitch>div * {
pointer-events: none;
}

.sysSwitch>div {
border-radius: 3px;
height: 32px;
line-height: 32px;
text-align: center;
cursor: pointer;
pointer-events: auto;
font-weight: bold;
display: flex;
align-items: center;
-webkit-user-select: none;
user-select: none;
}

.sysSwitch>div>svg {
margin-right: 4px;
}

.sysSwitch>div:hover {
background-color: var(--active-btn);
}

.sysDetail {
overflow-y: auto;
flex: 1;
}

#closeSet {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
padding: 10px 14px;
}

#closeSet:hover {
color: var(--black-color);
}

.setContent {
margin-bottom: 10px;
}

.setNotNormalFlow {
position: absolute;
}

.setTitle {
margin-bottom: 6px;
font-weight: bold;
}

.setDetail {
margin: 0 10px;
}

.dataDetail {
display: flex;
}

.dataDetail svg {
margin-right: 4px;
}

.dataDetail>div,
.dataDetail>label {
border-radius: 3px;
text-align: center;
padding: 6px 8px;
margin-right: 12px;
color: var(--btn-color);
font-size: 15px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
}

.dataDetail>div:hover,
.dataDetail>label:hover {
background: var(--lighter-active-color);
}

.hotKeyDetail>div {
position: relative;
height: 30px;
margin-bottom: 2px;
}

.hotKeyDetail label {
line-height: 30px;
}

.hotKeyDetail select {
position: absolute;
left: 110px;
outline: none;
border-radius: 3px;
width: 120px;
border-color: rgba(0, 0, 0, .3);
background: var(--chat-back);
color: var(--chat-text-color);
height: 30px;
font-size: 15px;
}

.avatarDetail {
display: flex;
margin-top: 2px;
}

.avatarDetail img {
border-radius: 2px;
width: 32px;
height: 32px;
flex-shrink: 0;
margin-right: 8px;
margin-top: 2px;
}

.inputDetail input {
outline: none;
border-radius: 3px;
padding-left: 8px;
font-size: 15px;
width: 100%;
height: 34px;
border: 1px solid rgba(0, 0, 0, .3);
background: var(--chat-back);
color: var(--chat-text-color);
}

.apiDetail {
display: flex;
}

.apiDetail input {
flex: 1;
}

.apiDetail>div {
margin-left: 6px;
background: var(--lighter-active-color);
border-radius: 3px;
width: 108px;
text-align: center;
cursor: pointer;
margin-top: 2px;
height: 34px;
line-height: 34px;
font-size: 15px;
}

.apiDetail>div:hover {
background: var(--active-btn);
}

.themeDetail {
display: flex;
width: 180px;
justify-content: space-between;
pointer-events: none;
}

.themeDetail svg {
display: block;
}

.themeDetail>div {
pointer-events: auto;
border-radius: 20px;
text-align: center;
padding: 8px 8px;
color: var(--btn-color);
font-size: 14px;
cursor: pointer;
}

.themeDetail>div * {
pointer-events: none;
}

.themeDetail>div:hover {
background: var(--lighter-active-color);
}

.darkTheme>div:first-child {
background: var(--sel-btn);
}

.lightTheme>div:nth-child(2) {
background: var(--sel-btn);
}

.autoTheme>div:nth-child(3) {
background: var(--sel-btn);
}

.langDetail {
width: 110px;
}

.enLang>div:first-child {
background: var(--sel-btn);
}

.zhLang>div:nth-child(2) {
background: var(--sel-btn);
}

#customAutoSet input {
width: 100px;
height: 30px;
line-height: 30px;
font-size: 15px;
outline: none;
border: 1px solid rgba(0, 0, 0, .3);
text-align: center;
border-radius: 3px;
background: var(--chat-back);
color: var(--chat-text-color);
}

#customAutoSet label {
margin-right: 8px;
}

.progressBar {
position: relative;
width: 100%;
height: 12px;
border-radius: 6px;
background: var(--active-btn);
overflow: hidden;
}

.expiredBar {
background: #e15b64;
}

.nowProgress {
position: absolute;
left: 0;
top: 0;
height: 12px;
min-width: 1px;
border-radius: 6px;
background: #99c959;
}

.progressDetail {
display: flex;
justify-content: space-between;
font-size: 15px;
}

.cursorCls {
background: var(--chat-text-color);
width: 4px;
animation: 1s cursor-blinker infinite step-start;
}

@keyframes cursor-blinker {
0% {
opacity: 0;
}

50% {
opacity: 1;
}

100% {
opacity: 0;
}
}

#apiSelect {
position: absolute;
top: 37px;
padding: 4px 0;
background: var(--chat-back);
width: 100%;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.15);
max-height: 180px;
overflow-y: auto;
-webkit-user-select: none;
user-select: none;
}

#apiSelect>div {
pointer-events: auto;
cursor: pointer;
font-size: 15px;
padding: 6px 0 6px 8px;
height: 36px;
display: flex;
justify-content: space-between;
align-items: center;
}

#apiSelect>div:hover {
background: var(--lighter-active-color);
}

#apiSelect>div>span {
height: 100%;
line-height: 23px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

#apiSelect>div * {
pointer-events: none;
}

.delApiOption:hover {
background: var(--lighter-svg-color);
}

.delApiOption {
width: 36px;
height: 36px;
flex-shrink: 0;
pointer-events: auto !important;
}

.delApiOption>svg {
margin: 6px;
display: block;
}
</style>
<script>
let themeMode; // 2: 自动, 1: 浅色,0: 深色
let autoThemeMode; // 1: 跟随系统,0:自定义时间
let customDarkTime; // 开始,结束时间
let isFull = false; // 是否全屏
const darkMedia = window.matchMedia("(prefers-color-scheme: dark)");
const justDarkTheme = (is) => {
if (is) document.documentElement.setAttribute("data-theme", "dark");
else document.documentElement.removeAttribute("data-theme");
document.head.children[4].content = document.head.children[5].content = document.head.children[6].content = getComputedStyle(document.documentElement).getPropertyValue("--background");
}
const checkDark = () => {
const checkCustomTheme = () => {
let date = new Date();
let nowTime = date.getTime();
let start = customDarkTime[0].split(":");
let startTime = new Date().setHours(start[0], start[1], 0, 0);
let end = customDarkTime[1].split(":");
let endTime = new Date().setHours(end[0], end[1], 0, 0);
let order = endTime > startTime;
let isDark = order ? (nowTime > startTime && endTime > nowTime) : !(nowTime > endTime && startTime > nowTime);
justDarkTheme(isDark);
}
const setDarkMode = () => {
if (themeMode === 2) {
if (autoThemeMode) {
justDarkTheme(darkMedia.matches);
} else {
checkCustomTheme();
}
} else if (themeMode === 1) {
justDarkTheme(false);
} else {
justDarkTheme(true);
}
localStorage.setItem("themeMode", themeMode);
}
let localTheme = localStorage.getItem("themeMode");
themeMode = parseInt(localTheme || "1");
let localAutoTheme = localStorage.getItem("autoThemeMode");
autoThemeMode = parseInt(localAutoTheme || "1");
let localCustomDark = localStorage.getItem("customDarkTime");
customDarkTime = JSON.parse(localCustomDark || '["21:00", "07:00"]');
setDarkMode();
}
checkDark();
</script>
</head>

<body>
<div style="display: none">
<svg>
<symbol viewBox="0 0 24 24" id="optionIcon">
<path fill="currentColor"
d="M12 3c-1.1 0-2 .9-2 2s.9 2 2 2s2-.9 2-2s-.9-2-2-2zm0 14c-1.1 0-2 .9-2 2s.9 2 2 2s2-.9 2-2s-.9-2-2-2zm0-7c-1.1 0-2 .9-2 2s.9 2 2 2s2-.9 2-2s-.9-2-2-2z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="refreshIcon">
<path fill="currentColor"
d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="halfRefIcon">
<path fill="currentColor"
d="M 4.009 12.163 C 4.012 12.206 2.02 12.329 2 12.098 C 2 6.575 6.477 2 12 2 C 17.523 2 22 6.477 22 12 C 22 14.136 21.33 16.116 20.19 17.74 L 17 12 L 20 12 C 19.999 5.842 13.333 1.993 7.999 5.073 C 3.211 8.343 4.374 12.389 4.009 12.163 Z" />
</symbol>
<symbol viewBox="-2 -2 20 20" id="copyIcon">
<path fill="currentColor"
d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z">
</path>
<path fill="currentColor"
d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="delIcon">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 7v0a3 3 0 0 1 3-3v0a3 3 0 0 1 3 3v0M9 7h6M9 7H6m9 0h3m2 0h-2M4 7h2m0 0v11a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="readyVoiceIcon">
<path fill="currentColor"
d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z">
</path>
</symbol>
<symbol viewBox="0 0 20 20" id="pauseVoiceIcon">
<path stroke="currentColor" stroke-width="2.4" d="M6 3v14M14 3v14"></path>
</symbol>
<symbol viewBox="0 0 16 16" id="resumeVoiceIcon">
<path fill="currentColor" d="M4 3L4 13L12 8Z"></path>
</symbol>
<symbol viewBox="0 0 24 24" id="stopResIcon">
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10zm0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16zM9 9h6v6H9V9z">
</path>
</symbol>
<symbol viewBox="0 0 128 128" id="downAudioIcon">
<path
d="M 64.662 1.549 C 56.549 4.524, 46.998 14.179, 45.523 20.895 C 45.041 23.089, 44.073 23.833, 40.433 24.807 C 34.752 26.326, 27.956 32.929, 25.527 39.289 C 24.273 42.574, 23.884 45.715, 24.196 50.034 C 24.620 55.897, 24.528 56.193, 21.836 57.585 C 17.142 60.012, 16 63.617, 16 76 C 16 88.463, 17.137 91.985, 21.967 94.483 C 28.244 97.729, 36.120 95.350, 38.579 89.466 C 39.387 87.532, 40 82.764, 40 78.415 C 40 70.971, 40.060 70.783, 42.250 71.370 C 43.487 71.701, 48.888 71.979, 54.250 71.986 L 64 72 64 76 L 64 80 57.122 80 C 49.420 80, 48.614 80.543, 47.547 86.453 C 46.552 91.964, 43.550 97.473, 40.273 99.803 C 33 104.974, 23.120 105.042, 16.118 99.971 C 11.407 96.558, 9.048 92.484, 8.145 86.205 C 6.963 77.979, 0.794 77.729, 0.191 85.883 C -0.196 91.111, 3.323 99.170, 8.062 103.908 C 11.290 107.136, 20.073 111.969, 22.750 111.990 C 23.540 111.996, 24 113.472, 24 116 C 24 119.740, 23.813 120, 21.122 120 C 17.674 120, 15.727 122.044, 16.173 125.195 C 16.492 127.441, 16.781 127.500, 27.391 127.500 C 36.676 127.500, 38.445 127.242, 39.386 125.750 C 40.993 123.203, 38.986 120.568, 35.149 120.187 C 32.206 119.894, 32 119.617, 32 115.956 C 32 112.509, 32.330 111.959, 34.750 111.377 C 42.181 109.591, 52.157 101.208, 53.575 95.559 C 53.928 94.152, 54.514 93, 54.878 93 C 55.242 93, 59.797 97.275, 65 102.500 C 70.762 108.286, 75.256 112, 76.495 112 C 77.769 112, 83.287 107.231, 91.264 99.236 C 101.113 89.366, 104 85.876, 104 83.843 C 104 80.580, 102.553 80, 94.418 80 L 88 80 88 76.105 L 88 72.211 99.750 71.815 C 113.117 71.364, 117.595 69.741, 122.762 63.473 C 128.159 56.925, 129.673 45.269, 126.134 37.500 C 123.787 32.346, 117.218 26.445, 112.132 24.921 C 108.617 23.868, 107.767 22.968, 105.028 17.405 C 99.364 5.901, 89.280 -0.062, 75.712 0.070 C 71.746 0.109, 66.773 0.774, 64.662 1.549 M 67.885 9.380 C 60.093 12.164, 55.057 17.704, 52.527 26.276 C 51.174 30.856, 50.220 31.617, 44.729 32.496 C 37.017 33.729, 30.917 42.446, 32.374 50.154 C 34.239 60.026, 40.582 63.944, 54.750 63.978 L 64 64 64 57.122 C 64 52.457, 64.449 49.872, 65.396 49.086 C 66.310 48.328, 70.370 48.027, 77.146 48.214 L 87.500 48.500 87.794 56.359 L 88.088 64.218 98.989 63.845 C 108.043 63.535, 110.356 63.125, 112.634 61.424 C 119.736 56.122, 121.911 47.667, 118.097 40.190 C 115.870 35.824, 110.154 32.014, 105.790 31.985 C 102.250 31.961, 101.126 30.787, 99.532 25.443 C 95.580 12.197, 80.880 4.736, 67.885 9.380 M 72 70.800 C 72 80.978, 71.625 85.975, 70.800 86.800 C 70.140 87.460, 67.781 88, 65.559 88 L 61.517 88 68.759 95.241 L 76 102.483 83.241 95.241 L 90.483 88 86.441 88 C 84.219 88, 81.860 87.460, 81.200 86.800 C 80.375 85.975, 80 80.978, 80 70.800 L 80 56 76 56 L 72 56 72 70.800 M 25.200 65.200 C 23.566 66.834, 23.566 85.166, 25.200 86.800 C 27.002 88.602, 29.798 88.246, 30.965 86.066 C 31.534 85.002, 32 80.472, 32 76 C 32 71.528, 31.534 66.998, 30.965 65.934 C 29.798 63.754, 27.002 63.398, 25.200 65.200"
stroke="none" fill="currentColor" fill-rule="evenodd" />
</symbol>
<symbol viewBox="0 0 24 24" id="chatIcon">
<path fill="currentColor"
d="m18 21l-1.4-1.4l1.575-1.6H14v-2h4.175L16.6 14.4L18 13l4 4l-4 4ZM3 21V6q0-.825.588-1.413T5 4h12q.825 0 1.413.588T19 6v5.075q-.25-.05-.5-.063T18 11q-.25 0-.5.013t-.5.062V6H5v10h7.075q-.05.25-.063.5T12 17q0 .25.013.5t.062.5H6l-3 3Zm4-11h8V8H7v2Zm0 4h5v-2H7v2Zm-2 2V6v10Z" />
</symbol>
<symbol viewBox="0 0 24 24" id="chatEditIcon">
<path fill="currentColor"
d="M5 19h1.4l8.625-8.625l-1.4-1.4L5 17.6V19ZM19.3 8.925l-4.25-4.2l1.4-1.4q.575-.575 1.413-.575t1.412.575l1.4 1.4q.575.575.6 1.388t-.55 1.387L19.3 8.925ZM17.85 10.4L7.25 21H3v-4.25l10.6-10.6l4.25 4.25Zm-3.525-.725l-.7-.7l1.4 1.4l-.7-.7Z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="deleteIcon">
<path fill="currentColor"
d="M8 20v-5h2v5h9v-7H5v7h3zm-4-9h16V8h-6V4h-4v4H4v3zM3 21v-8H2V7a1 1 0 0 1 1-1h5V3a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v3h5a1 1 0 0 1 1 1v6h-1v8a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="addIcon" stroke="currentColor" fill="none" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</symbol>
<symbol viewBox="0 0 200 100" preserveAspectRatio="xMidYMid" id="loadingIcon">
<g transform="translate(50 50)">
<circle cx="0" cy="0" r="15" fill="#e15b64">
<animateTransform attributeName="transform" type="scale" begin="-0.3333333333333333s"
calcMode="spline" keySplines="0.3 0 0.7 1;0.3 0 0.7 1" values="0;1;0" keyTimes="0;0.5;1"
dur="1s" repeatCount="indefinite"></animateTransform>
</circle>
</g>
<g transform="translate(100 50)">
<circle cx="0" cy="0" r="15" fill="#f8b26a">
<animateTransform attributeName="transform" type="scale" begin="-0.16666666666666666s"
calcMode="spline" keySplines="0.3 0 0.7 1;0.3 0 0.7 1" values="0;1;0" keyTimes="0;0.5;1"
dur="1s" repeatCount="indefinite"></animateTransform>
</circle>
</g>
<g transform="translate(150 50)">
<circle cx="0" cy="0" r="15" fill="#99c959">
<animateTransform attributeName="transform" type="scale" begin="0s" calcMode="spline"
keySplines="0.3 0 0.7 1;0.3 0 0.7 1" values="0;1;0" keyTimes="0;0.5;1" dur="1s"
repeatCount="indefinite"></animateTransform>
</circle>
</g>
</symbol>
<symbol viewBox="0 0 24 24" id="exportIcon">
<path fill="currentColor"
d="m17.86 18l1.04 1c-1.4 1.2-3.96 2-6.9 2c-4.41 0-8-1.79-8-4V7c0-2.21 3.58-4 8-4c2.95 0 5.5.8 6.9 2l-1.04 1l-.36.4C16.65 5.77 14.78 5 12 5C8.13 5 6 6.5 6 7s2.13 2 6 2c1.37 0 2.5-.19 3.42-.46l.96.96H13.5v1.42c-.5.05-1 .08-1.5.08c-2.39 0-4.53-.53-6-1.36v2.81C7.3 13.4 9.58 14 12 14c.5 0 1-.03 1.5-.08v.58h2.88l-1 1l.12.11c-1.09.25-2.26.39-3.5.39c-2.28 0-4.39-.45-6-1.23V17c0 .5 2.13 2 6 2c2.78 0 4.65-.77 5.5-1.39l.36.39m1.06-10.92L17.5 8.5L20 11h-5v2h5l-2.5 2.5l1.42 1.42L23.84 12l-4.92-4.92Z" />
</symbol>
<symbol viewBox="0 0 24 24" id="importIcon">
<path fill="currentColor"
d="m8.84 12l-4.92 4.92L2.5 15.5L5 13H0v-2h5L2.5 8.5l1.42-1.42L8.84 12M12 3C8.59 3 5.68 4.07 4.53 5.57L5 6l1.03 1.07C6 7.05 6 7 6 7c0-.5 2.13-2 6-2s6 1.5 6 2s-2.13 2-6 2c-2.62 0-4.42-.69-5.32-1.28l3.12 3.12c.7.1 1.44.16 2.2.16c2.39 0 4.53-.53 6-1.36v2.81c-1.3.95-3.58 1.55-6 1.55c-.96 0-1.9-.1-2.76-.27l-1.65 1.64c1.32.4 2.82.63 4.41.63c2.28 0 4.39-.45 6-1.23V17c0 .5-2.13 2-6 2s-6-1.5-6-2v-.04L5 18l-.46.43C5.69 19.93 8.6 21 12 21c4.41 0 8-1.79 8-4V7c0-2.21-3.58-4-8-4Z" />
</symbol>
<symbol viewBox="0 0 24 24" id="clearAllIcon">
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10zm0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16zm0-9.414l2.828-2.829l1.415 1.415L13.414 12l2.829 2.828l-1.415 1.415L12 13.414l-2.828 2.829l-1.415-1.415L10.586 12L7.757 9.172l1.415-1.415L12 10.586z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="collapseFullIcon">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="1.5"
d="m20 20l-5-5m0 0v4m0-4h4M4 20l5-5m0 0v4m0-4H5M20 4l-5 5m0 0V5m0 4h4M4 4l5 5m0 0V5m0 4H5" />
</symbol>
<symbol viewBox="0 0 24 24" id="expandFullIcon">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="1.5"
d="M9 9L4 4m0 0v4m0-4h4m7 5l5-5m0 0v4m0-4h-4M9 15l-5 5m0 0v-4m0 4h4m7-5l5 5m0 0v-4m0 4h-4" />
</symbol>
<symbol viewBox="0 0 24 24" id="darkThemeIcon">
<path fill="currentColor"
d="M20.742 13.045a8.088 8.088 0 0 1-2.077.271c-2.135 0-4.14-.83-5.646-2.336a8.025 8.025 0 0 1-2.064-7.723A1 1 0 0 0 9.73 2.034a10.014 10.014 0 0 0-4.489 2.582c-3.898 3.898-3.898 10.243 0 14.143a9.937 9.937 0 0 0 7.072 2.93 9.93 9.93 0 0 0 7.07-2.929 10.007 10.007 0 0 0 2.583-4.491 1.001 1.001 0 0 0-1.224-1.224zm-2.772 4.301a7.947 7.947 0 0 1-5.656 2.343 7.953 7.953 0 0 1-5.658-2.344c-3.118-3.119-3.118-8.195 0-11.314a7.923 7.923 0 0 1 2.06-1.483 10.027 10.027 0 0 0 2.89 7.848 9.972 9.972 0 0 0 7.848 2.891 8.036 8.036 0 0 1-1.484 2.059z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="lightThemeIcon">
<path fill="currentColor"
d="M6.993 12c0 2.761 2.246 5.007 5.007 5.007s5.007-2.246 5.007-5.007S14.761 6.993 12 6.993 6.993 9.239 6.993 12zM12 8.993c1.658 0 3.007 1.349 3.007 3.007S13.658 15.007 12 15.007 8.993 13.658 8.993 12 10.342 8.993 12 8.993zM10.998 19h2v3h-2zm0-17h2v3h-2zm-9 9h3v2h-3zm17 0h3v2h-3zM4.219 18.363l2.12-2.122 1.415 1.414-2.12 2.122zM16.24 6.344l2.122-2.122 1.414 1.414-2.122 2.122zM6.342 7.759 4.22 5.637l1.415-1.414 2.12 2.122zm13.434 10.605-1.414 1.414-2.122-2.122 1.414-1.414z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="autoThemeIcon">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M9.173 14.83a4 4 0 1 1 5.657-5.657" />
<path
d="m11.294 12.707l.174.247a7.5 7.5 0 0 0 8.845 2.492A9 9 0 0 1 5.642 18.36M3 12h1m8-9v1M5.6 5.6l.7.7M3 21L21 3" />
</g>
</symbol>
<symbol viewBox="0 0 24 24" id="newFolderIcon">
<path fill="currentColor"
d="M14 16h2v-2h2v-2h-2v-2h-2v2h-2v2h2v2ZM2 20V4h8l2 2h10v14H2Zm2-2h16V8h-8.825l-2-2H4v12Zm0 0V6v12Z" />
</symbol>
<symbol viewBox="0 0 20 20" id="expandFolderIcon">
<path fill="currentColor"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z">
</path>
</symbol>
<symbol viewBox="0 0 24 24" id="closeIcon">
<path fill="currentColor"
d="M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6L6.4 19Z" />
</symbol>
<symbol viewBox="0 0 24 24" id="settingIcon">
<path fill="currentColor"
d="M13.85 22.25h-3.7c-.74 0-1.36-.54-1.45-1.27l-.27-1.89c-.27-.14-.53-.29-.79-.46l-1.8.72c-.7.26-1.47-.03-1.81-.65L2.2 15.53c-.35-.66-.2-1.44.36-1.88l1.53-1.19c-.01-.15-.02-.3-.02-.46 0-.15.01-.31.02-.46l-1.52-1.19c-.59-.45-.74-1.26-.37-1.88l1.85-3.19c.34-.62 1.11-.9 1.79-.63l1.81.73c.26-.17.52-.32.78-.46l.27-1.91c.09-.7.71-1.25 1.44-1.25h3.7c.74 0 1.36.54 1.45 1.27l.27 1.89c.27.14.53.29.79.46l1.8-.72c.71-.26 1.48.03 1.82.65l1.84 3.18c.36.66.2 1.44-.36 1.88l-1.52 1.19c.01.15.02.3.02.46s-.01.31-.02.46l1.52 1.19c.56.45.72 1.23.37 1.86l-1.86 3.22c-.34.62-1.11.9-1.8.63l-1.8-.72c-.26.17-.52.32-.78.46l-.27 1.91c-.1.68-.72 1.22-1.46 1.22zm-3.23-2h2.76l.37-2.55.53-.22c.44-.18.88-.44 1.34-.78l.45-.34 2.38.96 1.38-2.4-2.03-1.58.07-.56c.03-.26.06-.51.06-.78s-.03-.53-.06-.78l-.07-.56 2.03-1.58-1.39-2.4-2.39.96-.45-.35c-.42-.32-.87-.58-1.33-.77l-.52-.22-.37-2.55h-2.76l-.37 2.55-.53.21c-.44.19-.88.44-1.34.79l-.45.33-2.38-.95-1.39 2.39 2.03 1.58-.07.56a7 7 0 0 0-.06.79c0 .26.02.53.06.78l.07.56-2.03 1.58 1.38 2.4 2.39-.96.45.35c.43.33.86.58 1.33.77l.53.22.38 2.55z">
</path>
<circle fill="currentColor" cx="12" cy="12" r="3.5"></circle>
</symbol>
<symbol viewBox="298 299 1808 1808" id="aiIcon">
<path fill="white"
d="M1107.3 299.1c-198 0-373.9 127.3-435.2 315.3C544.8 640.6 434.9 720.2 370.5 833c-99.3 171.4-76.6 386.9 56.4 533.8-41.1 123.1-27 257.7 38.6 369.2 98.7 172 297.3 260.2 491.6 219.2 86.1 97 209.8 152.3 339.6 151.8 198 0 373.9-127.3 435.3-315.3 127.5-26.3 237.2-105.9 301-218.5 99.9-171.4 77.2-386.9-55.8-533.9v-.6c41.1-123.1 27-257.8-38.6-369.8-98.7-171.4-297.3-259.6-491-218.6-86.6-96.8-210.5-151.8-340.3-151.2zm0 117.5-.6.6c79.7 0 156.3 27.5 217.6 78.4-2.5 1.2-7.4 4.3-11 6.1L952.8 709.3c-18.4 10.4-29.4 30-29.4 51.4V1248l-155.1-89.4V755.8c-.1-187.1 151.6-338.9 339-339.2zm434.2 141.9c121.6-.2 234 64.5 294.7 169.8 39.2 68.6 53.9 148.8 40.4 226.5-2.5-1.8-7.3-4.3-10.4-6.1l-360.4-208.2c-18.4-10.4-41-10.4-59.4 0L1024 984.2V805.4L1372.7 604c51.3-29.7 109.5-45.4 168.8-45.5zM650 743.5v427.9c0 21.4 11 40.4 29.4 51.4l421.7 243-155.7 90L597.2 1355c-162-93.8-217.4-300.9-123.8-462.8C513.1 823.6 575.5 771 650 743.5zm807.9 106 348.8 200.8c162.5 93.7 217.6 300.6 123.8 462.8l.6.6c-39.8 68.6-102.4 121.2-176.5 148.2v-428c0-21.4-11-41-29.4-51.4l-422.3-243.7 155-89.3zM1201.7 997l177.8 102.8v205.1l-177.8 102.8-177.8-102.8v-205.1L1201.7 997zm279.5 161.6 155.1 89.4v402.2c0 187.3-152 339.2-339 339.2v-.6c-79.1 0-156.3-27.6-217-78.4 2.5-1.2 8-4.3 11-6.1l360.4-207.5c18.4-10.4 30-30 29.4-51.4l.1-486.8zM1380 1421.9v178.8l-348.8 200.8c-162.5 93.1-369.6 38-463.4-123.7h.6c-39.8-68-54-148.8-40.5-226.5 2.5 1.8 7.4 4.3 10.4 6.1l360.4 208.2c18.4 10.4 41 10.4 59.4 0l421.9-243.7z" />
</symbol>
<symbol viewBox="0 0 24 24" id="importSetIcon">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m12 21l-8-4.5v-9L12 3l8 4.5V12m-8 0l8-4.5M12 12v9m0-9L4 7.5M22 18h-7m3-3l-3 3l3 3" />
</symbol>
<symbol viewBox="0 0 24 24" id="exportSetIcon">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="m12 21l-8-4.5v-9L12 3l8 4.5V12m-8 0l8-4.5M12 12v9m0-9L4 7.5M15 18h7m-3-3l3 3l-3 3" />
</symbol>
<symbol viewBox="0 0 24 24" id="databaseIcon">
<path fill="currentColor"
d="M12 3C7.58 3 4 4.79 4 7v10c0 2.21 3.59 4 8 4s8-1.79 8-4V7c0-2.21-3.58-4-8-4m6 14c0 .5-2.13 2-6 2s-6-1.5-6-2v-2.23c1.61.78 3.72 1.23 6 1.23s4.39-.45 6-1.23V17m0-4.55c-1.3.95-3.58 1.55-6 1.55s-4.7-.6-6-1.55V9.64c1.47.83 3.61 1.36 6 1.36s4.53-.53 6-1.36v2.81M12 9C8.13 9 6 7.5 6 7s2.13-2 6-2s6 1.5 6 2s-2.13 2-6 2Z" />
</symbol>
<symbol viewBox="0 0 24 24" id="stopIcon">
<path fill="currentColor" d="M6 5h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Z" />
</symbol>
<symbol viewBox="0 0 24 24" id="forceRefreshIcon">
<path fill="currentColor"
d="M13.82 14H9.66c-.1-.66-.16-1.32-.16-2s.06-1.35.16-2h4.68c.09.65.16 1.32.16 2c0 .5-.04 1-.1 1.46c.6-.5 1.32-.89 2.1-1.14V12c0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2v.18c.7.17 1.35.45 1.95.82c.05-.32.05-.66.05-1c0-5.5-4.5-10-10-10C6.47 2 2 6.5 2 12s4.5 10 10 10c.34 0 .68 0 1-.05c-.41-.66-.71-1.4-.87-2.2c-.04.07-.08.14-.13.21c-.83-1.2-1.5-2.53-1.91-3.96h2.41c.31-.75.76-1.42 1.32-2m5.1-6h-2.95a15.65 15.65 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2H4.26m.82 2H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 0 1 5.08 16M8 8H5.08A7.923 7.923 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m12.83 7.67L22 14.5v4h-4l1.77-1.77A2.5 2.5 0 1 0 20 20h1.71A3.991 3.991 0 0 1 18 22.5c-2.21 0-4-1.79-4-4s1.79-4 4-4c1.11 0 2.11.45 2.83 1.17Z" />
</symbol>
<symbol viewBox="0 0 24 24" id="hotkeyIcon">
<g fill="none">
<path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />
<path fill="currentColor"
d="M18 3a3 3 0 0 1 2.995 2.824L21 6v12a3 3 0 0 1-2.824 2.995L18 21H6a3 3 0 0 1-2.995-2.824L3 18V6a3 3 0 0 1 2.824-2.995L6 3h12Zm-2.707 13.708A2.99 2.99 0 0 1 14 17H5v1a1 1 0 0 0 1 1h11.586l-2.293-2.292ZM18 5h-1v9c0 .386-.073.755-.206 1.094l-.086.2L19 17.585V6a1 1 0 0 0-.883-.993L18 5Zm-3 0H6a1 1 0 0 0-.993.883L5 6v9h9a1 1 0 0 0 .993-.883L15 14V5ZM9 7a1 1 0 0 1 .993.883L10 8v.631l1.445-.963a1 1 0 0 1 1.203 1.594l-.093.07l-1.377.918l1.377.918a1 1 0 0 1-1.009 1.723l-.1-.059L10 11.868V12a1 1 0 0 1-1.993.117L8 12V8a1 1 0 0 1 1-1Z" />
</g>
</symbol>
<symbol viewBox="0 0 24 24" id="zhIcon">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2">
<path stroke-linejoin="round" d="M5 8h14v7H5z" />
<path d="M12 4v17" />
</g>
</symbol>
<symbol viewBox="0 0 24 24" id="enIcon">
<path fill="currentColor"
d="M14 10h2v.757a4.5 4.5 0 0 1 7 3.743V20h-2v-5.5c0-1.43-1.174-2.5-2.5-2.5S16 13.07 16 14.5V20h-2V10Zm-2-6v2H4v5h8v2H4v5h8v2H2V4h10Z" />
</symbol>
</svg>
</div>
<div id="loadMask">
<div>
<div>ChatGPT</div>
<svg>
<use xlink:href="#loadingIcon" />
</svg>
</div>
</div>
<div class="chat_window">
<div class="overlay"></div>
<nav class="nav">
<div class="navHeader">
<div id="newChat">
<svg width="24" height="24">
<use xlink:href="#addIcon" />
</svg>
<span data-i18n-key="newChat"></span>
</div>
<div id="newFolder" data-i18n-title="newFolder" title>
<svg width="24" height="24">
<use xlink:href="#newFolderIcon" />
</svg>
</div>
</div>
<div class="extraChat">
<input type="text" id="searchChat" autocomplete="off" data-i18n-place="search" placeholder />
<div id="clearSearch">
<svg width="24" height="24">
<use xlink:href="#closeIcon" />
</svg>
</div>
</div>
<div class="allList">
<div id="folderList"></div>
<div id="chatList"></div>
</div>
<div class="navFooter">
<div class="navFunc">
<div id="refreshPage" data-i18n-title="forceRe" title>
<svg width="24" height="24">
<use xlink:href="#forceRefreshIcon" />
</svg>
</div>
<div id="clearChat" data-i18n-title="clearAll" title>
<svg width="24" height="24">
<use xlink:href="#clearAllIcon" />
</svg>
</div>
<div id="toggleLight" data-i18n-theme title>
<svg width="24" height="24">
<use xlink:href="#lightThemeIcon" />
</svg>
</div>
<div id="sysSetting" data-i18n-title="setting" title>
<svg width="24" height="24">
<use xlink:href="#settingIcon" />
</svg>
</div>
</div>
<div class="divider"></div>
<div class="links">
<a href="https://t.me/reonjy" target="_blank"
rel="noopener noreferrer">Reonjy</a>
</div>
</div>
</nav>
<div class="mainContent">
<div class="top_menu">
<div class="toggler" data-i18n-title="nav" title>
<div class="button close"></div>
<div class="button minimize"></div>
<div class="button maximize"></div>
</div>
<div class="title"><span>ChatGPT</span></div>
<div class="settings">
<button class="setBtn" id="toggleFull" data-i18n-window title>
<svg width="30" height="30">
<use xlink:href="#expandFullIcon" />
</svg>
</button>
<button class="setBtn" id="setting">
<svg viewBox="0 0 100 100" width="30" height="30">
<title data-i18n-key="quickSet"></title>
<circle cx="50" cy="20" r="10" fill="#e15b64" />
<circle cx="50" cy="50" r="10" fill="#f8b26a" />
<circle cx="50" cy="80" r="10" fill="#99c959" />
</svg>
</button>
</div>
<div id="setDialog" style="display:none;">
<div class="setSwitch">
<div data-id="convOption" data-i18n-key="chat" class="activeSwitch"></div>
<div data-id="speechOption" data-i18n-key="tts"></div>
<div data-id="recOption" data-i18n-key="stt"></div>
</div>
<div id="convOption">
<div class="presetSelect presetModelCls">
<label for="preSetModel" data-i18n-key="gptModel"></label>
<select id="preSetModel">
<option value="gpt-3.5-turbo">GPT-3.5</option>
<option value="gpt-3.5-turbo-16k">GPT-3.5-16k</option>
<option value="gpt-4-0613">GPT-4</option>
</select>
</div>
<div>
<div data-i18n-key="avatar"></div>
<div class="avatarDetail">
<img id="setAvatarPre" src="" />
<input class="inputTextClass" autocomplete="off" type="text" id="setAvatar" />
</div>
</div>
<div>
<div class="justSetLine presetSelect">
<div data-i18n-key="systemRole"></div>
<div>
<label for="preSetSystem" data-i18n-key="presetRole"></label>
<select id="preSetSystem">
<option value="default" data-i18n-key="default"></option>
<option value="normal" data-i18n-key="assistant"></option>
<option value="cat" data-i18n-key="cat"></option>
<option value="emoji" data-i18n-key="emoji"></option>
<option value="image" data-i18n-key="withImg"></option>
</select>
</div>
</div>
<textarea class="inputTextClass areaTextClass" autocomplete="off"
data-i18n-place="assistantText" placeholder id="systemInput"></textarea>
</div>
<div>
<span data-i18n-key="nature"></span>
<input type="range" id="top_p" min="0" max="1" value="0.7" step="0.05" />
<div class="selectDef">
<span data-i18n-key="natureNeg"></span>
<span data-i18n-key="naturePos"></span>
</div>
</div>
<div>
<span data-i18n-key="quality"></span>
<input type="range" id="temp" min="0" max="2" value="1" step="0.05" />
<div class="selectDef">
<span data-i18n-key="qualityNeg"></span>
<span data-i18n-key="qualityPos"></span>
</div>
</div>
<div>
<span data-i18n-key="chatsWidth"></span>
<input type="range" id="convWidth" min="30" max="100" value="100" step="1" />
<div class="selectDef">
<span>30%</span>
<span>100%</span>
</div>
</div>
<div>
<span data-i18n-key="typeSpeed"></span>
<input type="range" id="textSpeed" min="0" max="100" value="88" step="1" />
<div class="selectDef">
<span data-i18n-key="slow"></span>
<span data-i18n-key="fast"></span>
</div>
</div>
<div>
<span class="inlineTitle" data-i18n-key="contChat"></span>
<label class="switch-slide">
<input type="checkbox" id="enableCont" checked="true" hidden />
<label for="enableCont" class="switch-slide-label"></label>
</label>
</div>
<div>
<span class="inlineTitle" data-i18n-key="longReply"></span>
<label class="switch-slide">
<input type="checkbox" id="enableLongReply" hidden />
<label for="enableLongReply" class="switch-slide-label"></label>
</label>
</div>
</div>
<div id="speechOption" style="display: none;">
<div class="presetSelect presetModelCls">
<label for="preSetService" data-i18n-key="ttsService"></label>
<select id="preSetService">
<option value="3" data-i18n-key="azureTTS"></option>
<option selected value="2" data-i18n-key="edgeTTS"></option>
<option value="1" data-i18n-key="systemTTS"></option>
</select>
</div>
<div class="presetSelect presetModelCls">
<label for="preSetAzureRegion" data-i18n-key="azureRegion"></label>
<select id="preSetAzureRegion">
</select>
</div>
<div>
<div>Azure Access Key</div>
<input class="inputTextClass" type="text" placeholder="Azure Key" id="azureKeyInput"
autocomplete="off" style="-webkit-text-security: disc;" />
</div>
<div id="checkVoiceLoad" style="display: none;">
<svg>
<use xlink:href="#loadingIcon" />
</svg>
<span data-i18n-key="loadVoice"></span>
</div>
<div id="speechDetail">
<div>
<div class="justSetLine">
<div data-i18n-key="voiceName"></div>
<div id="voiceTypes">
<span data-type="0" data-i18n-key="userVoice"></span>
<span data-type="1" class="selVoiceType" data-i18n-key="replyVoice"></span>
</div>
</div>
<select id="preSetSpeech">
</select>
</div>
<div>
<div class="justSetLine">
<input class="inputTextClass" id="testVoiceText" data-i18n-value="TTSTest" value />
</div>
<div class="justSetLine readyTestVoice" id="testVoiceBtn" style="margin-top: 6px;">
<div class="justSetBtn" onclick="startTestVoice()">
<svg width="18" height="18">
<use xlink:href="#readyVoiceIcon" />
</svg>
<span data-i18n-key="play"></span>
</div>
<div class="justSetBtn" onclick="pauseTestVoice()">
<svg width="18" height="18">
<use xlink:href="#pauseVoiceIcon" />
</svg>
<span data-i18n-key="pause"></span>
</div>
<div class="justSetBtn" onclick="resumeTestVoice()">
<svg width="18" height="18">
<use xlink:href="#resumeVoiceIcon" />
</svg>
<span data-i18n-key="resume"></span>
</div>
<div class="justSetBtn" style="margin-right: 130px" onclick="stopTestVoice()">
<svg width="18" height="18">
<use xlink:href="#stopIcon" />
</svg>
<span data-i18n-key="stop"></span>
</div>
</div>
</div>
<div class="justSetLine presetSelect" id="azureExtra" style="display:none;">
<div class="presetModelCls">
<label for="preSetVoiceStyle" data-i18n-key="style"></label>
<select id="preSetVoiceStyle">
</select>
</div>
<div class="presetModelCls">
<label for="preSetVoiceRole" data-i18n-key="role"></label>
<select id="preSetVoiceRole">
</select>
</div>
</div>
<div>
<span data-i18n-key="volume"></span>
<input type="range" id="voiceVolume" min="0" max="1" value="1" step="0.1" />
<div class="selectDef">
<span data-i18n-key="low"></span>
<span data-i18n-key="high"></span>
</div>
</div>
<div>
<span data-i18n-key="rate"></span>
<input type="range" id="voiceRate" min="0.1" max="2" value="1" step="0.1" />
<div class="selectDef">
<span data-i18n-key="slow"></span>
<span data-i18n-key="fast"></span>
</div>
</div>
<div>
<span data-i18n-key="pitch"></span>
<input type="range" id="voicePitch" min="0" max="2" value="1" step="0.1" />
<div class="selectDef">
<span data-i18n-key="neutral"></span>
<span data-i18n-key="intense"></span>
</div>
</div>
<div>
<span class="inlineTitle" data-i18n-key="contSpeech"></span>
<label class="switch-slide">
<input type="checkbox" id="enableContVoice" checked="true" hidden />
<label for="enableContVoice" class="switch-slide-label"></label>
</label>
</div>
<div>
<span class="inlineTitle" data-i18n-key="autoSpeech"></span>
<label class="switch-slide">
<input type="checkbox" id="enableAutoVoice" hidden />
<label for="enableAutoVoice" class="switch-slide-label"></label>
</label>
</div>
</div>
</div>
<div id="recOption" style="display: none;">
<div id="noRecTip" style="display: block;" data-i18n-key="unsupportRecTip"></div>
<div id="yesRec" style="display: none;">
<div class="presetSelect presetModelCls">
<label for="selectLangOption" data-i18n-key="lang"></label>
<select id="selectLangOption">
</select>
</div>
<div class="presetSelect presetModelCls">
<label for="selectDiaOption" data-i18n-key="dialect"></label>
<select id="selectDiaOption">
</select>
</div>
<div>
<div data-i18n-key="autoSendKey"></div>
<input class="inputTextClass" id="autoSendText" autocomplete="off"
data-i18n-place="send" placeholder />
</div>
<div>
<div data-i18n-key="autoStopKey"></div>
<input class="inputTextClass" id="autoStopText" autocomplete="off"
data-i18n-place="stop" placeholder />
</div>
<div>
<span data-i18n-key="autoSendDelay"></span>
<input type="range" id="autoSendTimeout" min="0" max="10" value="0" step="1" />
<div class="selectDef">
<span>0<span data-i18n-key="second"></span></span>
<span>10<span data-i18n-key="second"></span></span>
</div>
</div>
<div>
<span class="inlineTitle" data-i18n-key="keepListenMic"></span>
<label class="switch-slide">
<input type="checkbox" id="keepListenMic" checked="false" hidden />
<label for="keepListenMic" class="switch-slide-label"></label>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="messages">
<div id="chatlog"></div>
<div id="stopChat"><svg width="24" height="24">
<use xlink:href="#stopResIcon" />
</svg><span data-i18n-key="stop"></span></div>
</div>
<div class="bottom_wrapper clearfix">
<div class="message_input_wrapper">
<textarea class="message_input_text" autocomplete="off" spellcheck="false" data-i18n-place="askTip"
placeholder id="chatinput"></textarea>
<div id="voiceRec" style="display:none;">
<div id="voiceRecIcon">
<svg viewBox="0 0 48 48" id="voiceInputIcon">
<g fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="4">
<rect fill="none" width="14" height="27" x="17" y="4" rx="7" />
<rect class="animVoice" x="18" y="4" width="12" height="27" stroke="none"
fill="currentColor"></rect>
<path stroke-linecap="round"
d="M9 23c0 8.284 6.716 15 15 15c8.284 0 15-6.716 15-15M24 38v6" />
</g>
</svg>
</div>
<div id="voiceRecSetting">
<select id="select_language" style="margin-bottom: 4px;"></select>
<select id="select_dialect"></select>
</div>
</div>
</div>
<button class="loaded" id="sendbutton">
<span data-i18n-key="send"></span>
<svg style="margin:0 auto;height:40px;width:100%;">
<use xlink:href="#loadingIcon" />
</svg>
</button>
<button class="clearConv" data-i18n-title="clearChat" title>
<svg style="color: #e15b64;" width="29" height="29">
<use xlink:href="#closeIcon" />
</svg>
<svg width="21" height="21">
<use xlink:href="#deleteIcon" />
</svg>
</button>
</div>
</div>
</div>
<div id="sysMask">
<div id="sysDialog">
<div id="closeSet">
<svg width="24" height="24">
<use xlink:href="#closeIcon" />
</svg>
</div>
<div class="sysTitle" data-i18n-key="setting"></div>
<div class="sysContent">
<div class="sysSwitch">
<div data-id="generalOption" class="activeSwitch">
<svg width="24" height="24">
<use xlink:href="#settingIcon" />
</svg><span data-i18n-key="general"></span>
</div>
<div data-id="hotkeyOption">
<svg width="24" height="24">
<use xlink:href="#hotkeyIcon" />
</svg><span data-i18n-key="hotkey"></span>
</div>
<div data-id="dataOption">
<svg width="24" height="24">
<use xlink:href="#databaseIcon" />
</svg><span data-i18n-key="data"></span>
</div>
</div>
<div class="sysDetail">
<div id="generalOption">
<div class="setContent">
<div class="setTitle" data-i18n-key="theme"></div>
<div class="setDetail themeDetail lightTheme" id="setLight">
<div data-i18n-title="darkTheme" title>
<svg width="24" height="24">
<use xlink:href="#darkThemeIcon"></use>
</svg>
</div>
<div data-i18n-title="lightTheme" title>
<svg width="24" height="24">
<use xlink:href="#lightThemeIcon"></use>
</svg>
</div>
<div data-i18n-title="autoTheme" title>
<svg width="24" height="24">
<use xlink:href="#autoThemeIcon"></use>
</svg>
</div>
</div>
</div>
<div class="setContent" id="autoDetail" style="display: none;font-size: 15px;">
<div class="setDetail">
<div>
<input type="radio" id="autoTheme1" name="autoLight" value="1" checked />
<label for="autoTheme1" data-i18n-key="systemTheme"></label>
</div>
<div style="margin-top: 8px;">
<input type="radio" id="autoTheme0" name="autoLight" value="0" />
<label for="autoTheme0" data-i18n-key="customDarkTheme"></label>
</div>
<div id="customAutoSet" style="display: none; margin-top: 10px;">
<div>
<label for="customStart" data-i18n-key="startDark"></label>
<input type="time" id="customStart" required>
</div>
<div style="margin-top: 10px;">
<label for="customEnd" data-i18n-key="endDark"></label>
<input type="time" id="customEnd" required>
</div>
</div>
</div>
</div>
<div class="setContent">
<div class="setTitle" data-i18n-key="lang"></div>
<div class="setDetail themeDetail langDetail" id="setLang">
<div title="English">
<svg width="24" height="24">
<use xlink:href="#enIcon"></use>
</svg>
</div>
<div title="中文">
<svg width="24" height="24">
<use xlink:href="#zhIcon"></use>
</svg>
</div>
</div>
</div>
<div class="setContent setNotNormalFlow">
<div class="setTitle" data-i18n-key="aiEndpoint"></div>
<div class="setDetail inputDetail" style="position: relative;">
<input class="inputTextClass" placeholder="https://api.openai.com/" autocomplete="off"
id="apiHostInput" />
<div id="apiSelect" tabindex="-1" style="display: none;"></div>
</div>
</div>
<div class="setContent" style="margin-top: 83px;">
<div class="setTitle" data-i18n-key="aiKey"></div>
<div class="setDetail inputDetail apiDetail">
<input class="inputTextClass" type="text" placeholder="sk-xxxxxx" id="keyInput"
autocomplete="off" style="-webkit-text-security: disc;" />
<div id="checkBillBtn" class="loaded">
<span data-i18n-key="checkBill"></span>
<svg style="margin:0 auto;height:34px;width:100%;">
<use xlink:href="#loadingIcon"></use>
</svg>
</div>
</div>
</div>
<div class="setContent" id="quotaContent" style="display: none;">
<div class="setTitle" id="quotaTitle" data-i18n-quota></div>
<div class="setDetail">
<div class="progressBar">
<div class="nowProgress" id="usedQuotaBar"></div>
</div>
<div class="progressDetail">
<div><span data-i18n-key="used"></span><span id="usedQuota"></span></div>
<div><span data-i18n-key="available"></span><span id="availableQuota"></span></div>
</div>
</div>
</div>
</div>
<div id="hotkeyOption" style="display: none;">
<div class="setContent">
<div class="setTitle">UI</div>
<div class="setDetail hotKeyDetail">
<div>
<label for="hotKeyNav" data-i18n-key="navKey"></label>
<select id="hotKeyNav">
</select>
</div>
<div>
<label for="hotKeyWindow" data-i18n-key="fullKey"></label>
<select id="hotKeyWindow">
</select>
</div>
<div>
<label for="hotKeyTheme" data-i18n-key="themeKey"></label>
<select id="hotKeyTheme">
</select>
</div>
<div>
<label for="hotKeyLang" data-i18n-key="langKey"></label>
<select id="hotKeyLang">
</select>
</div>
</div>
<div class="setTitle" data-i18n-key="chat"></div>
<div class="setDetail hotKeyDetail">
<div>
<label for="hotKeySearch" data-i18n-key="search"></label>
<select id="hotKeySearch">
</select>
</div>
<div>
<label for="hotKeyInput" data-i18n-key="inputKey"></label>
<select id="hotKeyInput">
</select>
</div>
<div>
<label for="hotKeyNewChat" data-i18n-key="newChat"></label>
<select id="hotKeyNewChat">
</select>
</div>
<div>
<label for="hotKeyClearChat" data-i18n-key="clearChat"></label>
<select id="hotKeyClearChat">
</select>
</div>
</div>
<div class="setTitle" data-i18n-key="voiceKey"></div>
<div class="setDetail hotKeyDetail">
<div style="display: none;">
<label for="hotKeyVoiceRec" data-i18n-key="recKey"></label>
<select id="hotKeyVoiceRec">
</select>
</div>
<div>
<label for="hotKeyVoiceSpeak" data-i18n-key="speechKey"></label>
<select id="hotKeyVoiceSpeak">
</select>
</div>
</div>
</div>
</div>
<div id="dataOption" style="display: none;">
<div class="setContent">
<div class="setTitle" data-i18n-key="chat"></div>
<div class="setDetail dataDetail">
<div id="exportChat">
<svg width="24" height="24">
<use xlink:href="#exportIcon" />
</svg>
<span data-i18n-key="export"></span>
</div>
<label id="importChat" for="importChatInput">
<svg width="24" height="24">
<use xlink:href="#importIcon" />
</svg>
<span data-i18n-key="import"></span>
</label>
<input type="file" style="display: none;" id="importChatInput"
accept="application/json" />
<div id="clearChatSet">
<svg width="24" height="24">
<use xlink:href="#clearAllIcon" />
</svg>
<span data-i18n-key="clear"></span>
</div>
</div>
</div>
<div class="setContent">
<div class="setTitle" data-i18n-key="setting"></div>
<div class="setDetail dataDetail">
<div id="exportSet">
<svg width="24" height="24">
<use xlink:href="#exportSetIcon" />
</svg>
<span data-i18n-key="export"></span>
</div>
<label id="importSet" for="importSetInput">
<svg width="24" height="24">
<use xlink:href="#importSetIcon" />
</svg>
<span data-i18n-key="import"></span>
</label>
<input type="file" style="display: none;" id="importSetInput"
accept="application/json" />
<div id="resetSet">
<svg width="22" height="22" style="transform: scaleX(-1)">
<use xlink:href="#refreshIcon" />
</svg>
<span data-i18n-key="reset"></span>
</div>
</div>
</div>
<div class="setContent">
<div class="setTitle" data-i18n-key="localStore"></div>
<div class="setDetail">
<div class="progressBar">
<div class="nowProgress" id="usedStorageBar"></div>
</div>
<div class="progressDetail">
<div><span data-i18n-key="used"></span><span id="usedStorage"></span></div>
<div><span data-i18n-key="available"></span><span id="availableStorage"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<link crossorigin="anonymous" href="//cdn.staticfile.org/github-markdown-css/5.2.0/github-markdown-light.min.css"
rel="stylesheet">
<link crossorigin="anonymous" href="//cdn.staticfile.org/notyf/3.10.0/notyf.min.css" rel="stylesheet">
<script crossorigin="anonymous" src="//cdn.staticfile.org/notyf/3.10.0/notyf.min.js"></script>
<script>
const registerSW = () => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("sw.js" + location.search).then(reg => console.log("Service worker register succeeded"),
error => console.error(`Service worker register failed: ${error}`))
}
};
window.addEventListener("load", () => registerSW());
const isMobile = navigator.userAgent.match(/iPhone|iPad|iPod|Android|BlackBerry|webOS/);
if (isMobile) {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/gh/timruffles/[email protected]/release/index.min.js";
script.crossOrigin = "anonymous";
script.defer = true;
script.onload = () => {
MobileDragDrop.polyfill();
}
document.body.appendChild(script);
const link = document.createElement("link");
link.crossOrigin = "anonymous";
link.rel = "stylesheet";
link.href = "https://cdn.jsdelivr.net/gh/timruffles/[email protected]/release/default.css";
document.body.appendChild(link);
}
</script>
<script>
const localeList = ["en", "zh"];
let locale; // UI语言
const setLangEle = document.getElementById("setLang");
const setLang = () => {
let langClass = locale + "Lang";
localStorage.setItem("UILang", locale)
setLangEle.classList = "setDetail themeDetail langDetail " + langClass;
}
setLangEle.onclick = (ev) => {
let idx = Array.prototype.indexOf.call(setLangEle.children, ev.target);
if (locale !== localeList[idx]) {
locale = localeList[idx];
setLang();
changeLocale();
}
}
const initLang = () => {
let localLang = localStorage.getItem("UILang") || "en";
let isInit = locale === void 0;
if (locale !== localLang) {
locale = localLang;
if (!isInit) changeLocale();
};
setLang();
}
initLang();
const translations = {
"en": {
"description": "Simple and powerful ChatGPT app",
"newChat": "New chat",
"newChatName": "New chat",
"newFolder": "New folder",
"newFolderName": "New folder",
"search": "Search",
"forceRe": "Force refresh",
"clearAll": "Clear all chats",
"setting": "Setting",
"nav": "Navigate",
"winedWin": "Window",
"fullWin": "Full screen",
"quickSet": "Quick setting",
"chat": "Chat",
"tts": "TTS",
"stt": "STT",
"gptModel": "GPT Model",
"gptBrowsing": "GPT-4-browsing",
"avatar": "Avatar",
"systemRole": "System Role",
"presetRole": "Preset",
"default": "Default",
"assistant": "Assistant",
"cat": "Cat girl",
"emoji": "Emoji",
"withImg": "Image",
"defaultText": "You are ChatGPT, a large language model trained by OpenAI. Carefully heed the user's instructions. Respond using markdown.",
"assistantText": "You are a helpful assistant, answer as concisely as possible.",
"catText": "You are a cute cat girl, you must end every sentence with 'meow'",
"emojiText": "Your personality is very lively, there must be at least one emoji icon in every sentence",
"imageText": "When you need to send pictures, please generate them in markdown language, without backslashes or code boxes. When you need to use the unsplash API, follow the format, https://source.unsplash.com/960x640/?<English keywords>",
"nature": "Nature",
"natureNeg": "Accurate",
"naturePos": "Creativity",
"quality": "Quality",
"qualityNeg": "Repetitive",
"qualityPos": "Nonsense",
"chatsWidth": "Chats width",
"typeSpeed": "Typing speed",
"slow": "Slow",
"fast": "Fast",
"contChat": "Continuous",
"longReply": "Long reply",
"ttsService": "TTS API",
"azureTTS": "Azure",
"edgeTTS": "Edge",
"systemTTS": "System",
"azureRegion": "Azure region",
"loadVoice": "Load voice",
"voiceName": "Choose voice",
"userVoice": "User voice",
"replyVoice": "Reply voice",
"TTSTest": "Hello, nice to meet you.",
"play": "Play",
"pause": "Pause",
"resume": "Resume",
"stop": "Stop",
"style": "Style",
"role": "Role",
"volume": "Volume",
"low": "Low",
"high": "High",
"rate": "Rate",
"slow": "Slow",
"fast": "Fast",
"pitch": "Pitch",
"neutral": "Neutral",
"intense": "Intense",
"contSpeech": "Continuous speech",
"autoSpeech": "Auto speech",
"unsupportRecTip": "Voice recognition is not supported in the current environment. Please refer to the documentation.",
"lang": "Language",
"dialect": "Dialect",
"autoSendKey": "Auto send keyword",
"autoStopKey": "Auto stop keyword",
"autoSendDelay": "Auto send delay time",
"second": "s",
"keepListenMic": "Keep listen",
"send": "Send",
"askTip": "Type message here",
"clearChat": "Clear chat",
"general": "General",
"hotkey": "Hotkeys",
"data": "Data",
"theme": "Theme",
"darkTheme": "Dark",
"lightTheme": "Light",
"autoTheme": "Auto",
"systemTheme": "System",
"customDarkTheme": "Custom dark theme",
"startDark": "Start",
"endDark": "End",
"aiEndpoint": "OpenAI Endpoint",
"aiKey": "OpenAI API key",
"checkBill": "Check bill",
"used": "Used ",
"available": "Avail ",
"navKey": "Toggle nav",
"fullKey": "Window size",
"themeKey": "Toggle theme",
"themeKey": "Toggle lang",
"inputKey": "Message",
"voiceKey": "Voice",
"recKey": "Recognition",
"speechKey": "Start speech",
"export": "Export",
"import": "Import",
"clear": "Clear",
"reset": "Reset",
"localStore": "Local storage",
"forceReTip": "Force refresh page?",
"noSpeechTip": "No speech was detected. You may need to adjust your microphone settings.",
"noMicTip": "No microphone was found. Ensure that a microphone is installed and microphone settings are configured correctly.",
"noMicPerTip": "Permission to use microphone is blocked.",
"azureInvalidTip": "Access is denied due to invalid access key or API endpoint!",
"thisQuota": "This month's quota",
"freeTierTip": "Free tier - valid until: ",
"errorAiKeyTip": "Invalid or incorrect API key, please check API key!",
"copyCode": "Copy code",
"copySuccess": "Success",
"update": "Update",
"cancel": "Cancel",
"delMsgTip": "Delete this message?",
"edit": "Edit",
"refresh": "Refresh",
"continue": "Continue",
"copy": "Copy",
"del": "Delete",
"downAudio": "Download audio",
"speech": "Speech",
"chats": " chats",
"delFolderTip": "Delete this folder?",
"delChatTip": "Delete this chat?",
"exportSuccTip": "Export successful!",
"importSuccTip": "Import successful!",
"importFailTip": "Import failed, please check the file format!",
"clearChatSuccTip": "Clear chats data successful!",
"resetSetSuccTip": "Reset settings successful!",
"clearAllTip": "Delete all chats and folders?",
"resetSetTip": "Restore all settings to default?",
"hotkeyConflict": "Hotkey conflict, please choose another key!",
"customDarkTip": "Start time and end time cannot be the same!",
"timeoutTip": "Request timeout, please try again later!",
"largeReqTip": "Model error. There seems to be problem with the endpoint provider",
"noModelPerTip": "No permission to use this model, please choose another GPT model!",
"apiRateTip": "Trigger API call rate limit, please try again later!",
"exceedLimitTip": "API usage exceeded limit, please check your bill!",
"badGateTip": "Gateway error or timeout, please try again later!",
"badEndpointTip": "Failed to access the endpoint, please check the endpoint!",
"clearChatTip": "Clear this chat?",
"cantSpeechTip": "Current voice cannot synthesize this message, please choose another voice or message!",
},
"zh": {
"description": "简洁而强大的ChatGPT应用",
"newChat": "新建会话",
"newChatName": "新的会话",
"newFolder": "新建文件夹",
"newFolderName": "新文件夹",
"search": "搜索",
"forceRe": "强制刷新",
"clearAll": "清空全部",
"setting": "设置",
"nav": "导航",
"winedWin": "窗口",
"fullWin": "全屏",
"quickSet": "快速设置",
"chat": "会话",
"tts": "语音合成",
"stt": "语音识别",
"gptModel": "GPT模型",
"gptBrowsing": "GPT-4-联网",
"avatar": "用户头像",
"systemRole": "系统角色",
"presetRole": "预设角色",
"default": "默认",
"assistant": "助手",
"cat": "猫娘",
"emoji": "表情",
"withImg": "有图",
"defaultText": "",
"assistantText": "你是一个乐于助人的助手,尽量简明扼要地回答",
"catText": "你是一个可爱的猫娘,每句话结尾都要带个'喵'",
"emojiText": "你的性格很活泼,每句话中都要有至少一个emoji图标",
"imageText": "当你需要发送图片的时候,请用 markdown 语言生成,不要反斜线,不要代码框,需要使用 unsplash API时,遵循一下格式, https://source.unsplash.com/960x640/? <英文关键词>",
"nature": "角色性格",
"natureNeg": "准确严谨",
"naturePos": "灵活创新",
"quality": "回答质量",
"qualityNeg": "重复保守",
"qualityPos": "胡言乱语",
"chatsWidth": "会话宽度",
"typeSpeed": "打字机速度",
"slow": "慢",
"fast": "快",
"contChat": "连续会话",
"longReply": "长回复",
"ttsService": "语音合成服务",
"azureTTS": "Azure语音",
"edgeTTS": "Edge语音",
"systemTTS": "系统语音",
"azureRegion": "Azure区域",
"loadVoice": "加载语音",
"voiceName": "选择语音",
"userVoice": "用户语音",
"replyVoice": "回答语音",
"TTSTest": "你好,很高兴认识你。",
"play": "播放",
"pause": "暂停",
"resume": "恢复",
"stop": "停止",
"style": "风格",
"role": "角色",
"volume": "音量",
"low": "低",
"high": "高",
"rate": "语速",
"slow": "慢",
"fast": "快",
"pitch": "音调",
"neutral": "平淡",
"intense": "起伏",
"contSpeech": "连续朗读",
"autoSpeech": "自动朗读",
"unsupportRecTip": "当前环境不支持语音识别,请查阅文档。",
"lang": "语言",
"dialect": "方言",
"autoSendKey": "自动发送关键词",
"autoStopKey": "自动停止关键词",
"autoSendDelay": "自动发送延迟时间",
"second": "秒",
"keepListenMic": "保持监听",
"send": "发送",
"askTip": "来问点什么吧",
"clearChat": "清空会话",
"general": "通用",
"hotkey": "快捷键",
"data": "数据",
"theme": "主题",
"darkTheme": "深色",
"lightTheme": "浅色",
"autoTheme": "自动",
"systemTheme": "跟随系统",
"customDarkTheme": "自定义深色主题时间",
"startDark": "开始时间",
"endDark": "结束时间",
"aiEndpoint": "OpenAI接口",
"aiKey": "API密钥",
"checkBill": "检查API额度",
"used": "已用 ",
"available": "可用 ",
"navKey": "切换导航",
"fullKey": "全屏/窗口",
"themeKey": "切换主题",
"langKey": "切换语言",
"inputKey": "输入框",
"voiceKey": "语音",
"recKey": "语音输入",
"speechKey": "朗读会话",
"export": "导出",
"import": "导入",
"clear": "清空",
"reset": "重置",
"localStore": "本地存储",
"forceReTip": "是否强制刷新页面?",
"noSpeechTip": "未识别到语音,请调整麦克风后重试!",
"noMicTip": "未识别到麦克风,请确保已安装麦克风!",
"noMicPerTip": "未允许麦克风权限!",
"azureInvalidTip": "由于订阅密钥无效或 API 端点错误,访问被拒绝!",
"thisQuota": "本月额度",
"freeTierTip": "免费额度-有效期至: ",
"errorAiKeyTip": "API密钥错误或失效,请检查API密钥!",
"copyCode": "复制代码",
"copySuccess": "复制成功",
"update": "更新",
"cancel": "取消",
"delMsgTip": "是否删除此消息?",
"edit": "编辑",
"refresh": "刷新",
"continue": "继续",
"copy": "复制",
"del": "删除",
"downAudio": "下载语音",
"speech": "朗读",
"chats": "个会话",
"delFolderTip": "是否删除此文件夹?",
"delChatTip": "是否删除此会话?",
"exportSuccTip": "导出成功!",
"importSuccTip": "导入成功!",
"importFailTip": "导入失败,请检查文件格式!",
"clearChatSuccTip": "清空会话成功!",
"resetSetSuccTip": "重置设置成功!",
"clearAllTip": "是否删除所有会话和文件夹?",
"resetSetTip": "是否还原所有设置为默认值?",
"hotkeyConflict": "快捷键冲突,请选择其他键位!",
"customDarkTip": "开始时间和结束时间不能相同!",
"timeoutTip": "请求超时,请稍后重试!",
"largeReqTip": "请求内容过大,请删除部分对话或关闭连续对话!",
"noModelPerTip": "无权使用此模型,请选择其他GPT模型!",
"apiRateTip": "触发API调用频率限制,请稍后重试!",
"exceedLimitTip": "API使用超出限额,请检查您的账单!",
"badGateTip": "网关错误或超时,请稍后重试!",
"badEndpointTip": "访问接口失败,请检查接口!",
"clearChatTip": "是否清空此会话?",
"cantSpeechTip": "当前语音无法合成此消息,请选择其他语音或消息!",
},
};
const translateElement = (ele, type) => {
const key = ele.getAttribute("data-i18n-" + type);
const translation = translations[locale][key];
if (type === "title") {
ele.setAttribute("title", translation);
} else if (type === "place") {
ele.setAttribute("placeholder", translation);
} else if (type === "value") {
ele.setAttribute("value", translation);
} else {
ele.textContent = translation;
}
}
const initLocale = () => {
document.querySelectorAll("[data-i18n-title]").forEach(ele => {translateElement(ele, "title")});
document.querySelectorAll("[data-i18n-place]").forEach(ele => {translateElement(ele, "place")});
document.querySelectorAll("[data-i18n-value]").forEach(ele => {translateElement(ele, "value")});
document.querySelectorAll("[data-i18n-key]").forEach(ele => {translateElement(ele, "key")});
document.querySelectorAll("[data-i18n-theme]").forEach(ele => {
let key = themeMode === 2 ? "autoTheme" : themeMode === 1 ? "lightTheme" : "darkTheme";
ele.setAttribute("title", translations[locale][key])
})
document.querySelectorAll("[data-i18n-window]").forEach(ele => {
let key = isFull ? "winedWin" : "fullWin";
ele.setAttribute("title", translations[locale][key])
})
document.querySelectorAll("[data-i18n-quota]").forEach(ele => {
let splitIdx = ele.innerHTML.indexOf(":");
if (splitIdx === -1) {
ele.textContent = translations[locale]["thisQuota"];
} else {
let validHtml = ele.innerHTML.slice(splitIdx + 2);
ele.innerHTML = translations[locale]["freeTierTip"] + validHtml;
}
})
document.head.children[3].setAttribute("content", translations[locale]["description"])
};
initLocale();
const changeLocale = () => {
initLocale();
document.querySelectorAll("[data-type='chatEdit'],[data-type='folderEdit']").forEach(ele => {
ele.children[0].textContent = translations[locale]["edit"];
});
document.querySelectorAll("[data-type='chatDel'],[data-type='folderDel']").forEach(ele => {
ele.children[0].textContent = translations[locale]["del"];
});
document.querySelectorAll("[data-type='folderAddChat']").forEach(ele => {
ele.children[0].textContent = translations[locale]["newChat"];
});
document.querySelectorAll("[data-id]").forEach(ele => {
let key = ele.getAttribute("data-id");
if (key.endsWith("Md")) {
if (key === "speechMd" || key === "pauseMd" || key === "resumeMd") {
ele.children[0].textContent = translations[locale][key.slice(0, -2)];
} else if (key === "refreshMd") {
ele.setAttribute("title", translations[locale][ele.classList.contains("refreshReq") ? "refresh" : "continue"]);
} else {
ele.setAttribute("title", translations[locale][key.slice(0, -2)]);
}
}
});
document.querySelectorAll(".folderNum").forEach(ele => {
let num = ele.textContent.match(/d+/)[0];
ele.textContent = num + translations[locale]["chats"];
});
document.querySelectorAll(".u-mdic-copy-btn").forEach(ele => {
ele.textContent = translations[locale]["copyCode"];
})
document.querySelectorAll(".u-mdic-copy-notify").forEach(ele => {
ele.textContent = translations[locale]["copySuccess"];
})
if (editingIdx !== void 0) {
document.querySelector("[data-i18n-key='send']").textContent = translations[locale]["update"];
document.querySelector("[data-i18n-title='clearChat']").setAttribute("title", translations[locale]["cancel"]);
}
loadPrompt();
}
</script>
<script>
const notyf = new Notyf({
position: {x: "center", y: "top"},
types: [
{
type: "success",
background: "#99c959",
duration: 2000,
},
{
type: "warning",
background: "#f8b26a",
duration: 3000
},
{
type: "error",
background: "#e15b64",
duration: 3000,
}
]
});
const windowEle = document.getElementsByClassName("chat_window")[0];
const messagesEle = document.getElementsByClassName("messages")[0];
const chatlog = document.getElementById("chatlog");
const stopEle = document.getElementById("stopChat");
const sendBtnEle = document.getElementById("sendbutton");
const clearEle = document.getElementsByClassName("clearConv")[0];
const inputAreaEle = document.getElementById("chatinput");
const settingEle = document.getElementById("setting");
const dialogEle = document.getElementById("setDialog");
const lightEle = document.getElementById("toggleLight");
const setLightEle = document.getElementById("setLight");
const autoThemeEle = document.getElementById("autoDetail");
const systemEle = document.getElementById("systemInput");
const speechServiceEle = document.getElementById("preSetService");
const newChatEle = document.getElementById("newChat");
const folderListEle = document.getElementById("folderList");
const chatListEle = document.getElementById("chatList");
const searchChatEle = document.getElementById("searchChat");
const voiceRecEle = document.getElementById("voiceRecIcon");
const voiceRecSetEle = document.getElementById("voiceRecSetting");
const preEle = document.getElementById("preSetSystem");
let voiceType = 1; // 设置 0: 提问语音,1:回答语音
let voiceRole = []; // 语音
let voiceTestText; // 测试语音文本
let voiceVolume = []; //音量
let voiceRate = []; // 语速
let voicePitch = []; // 音调
let enableContVoice; // 连续朗读
let enableAutoVoice; // 自动朗读
let existVoice = 2; // 3:Azure语音 2:使用edge在线语音, 1:使用本地语音, 0:不支持语音
let azureToken;
let azureTokenTimer;
let azureRegion;
let azureKey;
let azureRole = [];
let azureStyle = [];
const supportSpe = !!(window.speechSynthesis && window.SpeechSynthesisUtterance);
const isSafeEnv = location.hostname.match(/127.|localhost/) || location.protocol.match(/https:|file:/); // https或本地安全环境
const supportRec = !!window.webkitSpeechRecognition && isSafeEnv; // 是否支持语音识别输入
let recing = false;
let autoSendWord; // 自动发送关键词
let autoStopWord; // 自动停止关键词
let autoSendTime; // 自动发送延迟时间
let keepListenMic; // 保持监听麦克风
let autoSendTimer;
let resetRecRes;
let toggleRecEv;
const isAndroid = /bAndroidb/i.test(navigator.userAgent);
const isApple = /(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent);
const dayMs = 8.64e7;
refreshPage.onclick = () => {
if (confirmAction(translations[locale]["forceReTip"])) {
location.href = location.origin + location.pathname + "?" + new Date().getTime()
}
};
const noLoading = () => {
return !loading && (!currentResEle || currentResEle.dataset.loading !== "true")
};
inputAreaEle.focus();
const textInputEvent = () => {
if (noLoading()) {
if (inputAreaEle.value.trim().length) {
sendBtnEle.classList.add("activeSendBtn");
} else {
sendBtnEle.classList.remove("activeSendBtn");
}
}
inputAreaEle.style.height = "47px";
inputAreaEle.style.height = inputAreaEle.scrollHeight + "px";
};
inputAreaEle.oninput = textInputEvent;
const toggleNavEv = () => {
document.body.classList.toggle("show-nav");
if (window.innerWidth > 800) {
localStorage.setItem("pinNav", document.body.classList.contains("show-nav"))
}
}
document.body.addEventListener("mousedown", event => {
if (event.target.className === "toggler") {
toggleNavEv();
} else if (event.target.className === "overlay") {
document.body.classList.remove("show-nav");
} else if (event.target === document.body) {
if (window.innerWidth <= 800) {
document.body.classList.remove("show-nav");
}
}
});
const endSetEvent = (ev) => {
if (!document.getElementById("sysDialog").contains(ev.target)) {
ev.preventDefault();
ev.stopPropagation();
endSet();
}
}
const endSet = () => {
document.getElementById("sysMask").style.display = "none";
document.body.removeEventListener("click", endSetEvent, true);
}
document.getElementById("closeSet").onclick = endSet;
document.getElementById("sysSetting").onclick = () => {
document.getElementById("sysMask").style.display = "flex";
checkStorage();
document.getElementById("sysMask").onmousedown = endSetEvent;
};
const clearAutoSendTimer = () => {
if (autoSendTimer !== void 0) {
clearTimeout(autoSendTimer);
autoSendTimer = void 0;
}
}
const initRecSetting = () => {
if (supportRec) {
noRecTip.style.display = "none";
yesRec.style.display = "block";
hotKeyVoiceRec.parentElement.style.display = "block";
document.getElementById("voiceRec").style.display = "block";
inputAreaEle.classList.add("message_if_voice");
let langs = [ // from https://www.google.com/intl/en/chrome/demos/speech.html
['中文', ['cmn-Hans-CN', '普通话 (大陆)'],
['cmn-Hans-HK', '普通话 (香港)'],
['cmn-Hant-TW', '中文 (台灣)'],
['yue-Hant-HK', '粵語 (香港)']],
['English', ['en-US', 'United States'],
['en-GB', 'United Kingdom'],
['en-AU', 'Australia'],
['en-CA', 'Canada'],
['en-IN', 'India'],
['en-KE', 'Kenya'],
['en-TZ', 'Tanzania'],
['en-GH', 'Ghana'],
['en-NZ', 'New Zealand'],
['en-NG', 'Nigeria'],
['en-ZA', 'South Africa'],
['en-PH', 'Philippines']]
];
if (locale !== "zh") langs = langs.reverse();
langs.forEach((lang, i) => {
select_language.options.add(new Option(lang[0], i));
selectLangOption.options.add(new Option(lang[0], i))
});
const updateCountry = function () {
selectLangOption.selectedIndex = select_language.selectedIndex = this.selectedIndex;
select_dialect.innerHTML = "";
selectDiaOption.innerHTML = "";
let list = langs[select_language.selectedIndex];
for (let i = 1; i < list.length; i++) {
select_dialect.options.add(new Option(list[i][1], list[i][0]));
selectDiaOption.options.add(new Option(list[i][1], list[i][0]));
}
select_dialect.style.visibility = list[1].length == 1 ? "hidden" : "visible";
selectDiaOption.parentElement.style.visibility = list[1].length == 1 ? "hidden" : "visible";
localStorage.setItem("voiceRecLang", select_dialect.value);
};
let localLangIdx = 0;
let localDiaIdx = 0;
let localRecLang = localStorage.getItem("voiceRecLang") || "en-US";
if (localRecLang) {
let localIndex = langs.findIndex(item => {
let diaIdx = item.findIndex(lang => {return lang instanceof Array && lang[0] === localRecLang});
if (diaIdx !== -1) {
localDiaIdx = diaIdx - 1;
return true;
}
return false;
});
if (localIndex !== -1) localLangIdx = localIndex;
}
selectLangOption.onchange = updateCountry;
select_language.onchange = updateCountry;
selectDiaOption.onchange = select_dialect.onchange = function () {
selectDiaOption.selectedIndex = select_dialect.selectedIndex = this.selectedIndex;
localStorage.setItem("voiceRecLang", select_dialect.value);
}
selectLangOption.selectedIndex = select_language.selectedIndex = localLangIdx;
select_language.dispatchEvent(new Event("change"));
selectDiaOption.selectedIndex = select_dialect.selectedIndex = localDiaIdx;
select_dialect.dispatchEvent(new Event("change"));
let localAutoSendWord = localStorage.getItem("autoVoiceSendWord");
autoSendWord = autoSendText.value = localAutoSendWord || autoSendText.getAttribute("value") || "";
autoSendText.onchange = () => {
autoSendWord = autoSendText.value;
localStorage.setItem("autoVoiceSendWord", autoSendWord);
}
autoSendText.dispatchEvent(new Event("change"));
let localAutoStopWord = localStorage.getItem("autoVoiceStopWord");
autoStopWord = autoStopText.value = localAutoStopWord || autoStopText.getAttribute("value") || "";
autoStopText.onchange = () => {
autoStopWord = autoStopText.value;
localStorage.setItem("autoVoiceStopWord", autoStopWord);
}
autoStopText.dispatchEvent(new Event("change"));
let outEle = document.getElementById("autoSendTimeout");
let localTimeout = localStorage.getItem("autoVoiceSendOut");
outEle.value = autoSendTime = parseInt(localTimeout || outEle.getAttribute("value"));
outEle.oninput = () => {
outEle.style.backgroundSize = (outEle.value - outEle.min) * 100 / (outEle.max - outEle.min) + "% 100%";
autoSendTime = parseInt(outEle.value);
localStorage.setItem("autoVoiceSendOut", outEle.value);
}
outEle.dispatchEvent(new Event("input"));
outEle.onchange = () => {
let hasAutoTimer = !!autoSendTimer;
clearAutoSendTimer();
if (hasAutoTimer) setAutoTimer();
}
const keepMicEle = document.getElementById("keepListenMic");
let localKeepMic = localStorage.getItem("keepListenMic");
keepMicEle.checked = keepListenMic = (localKeepMic || keepMicEle.getAttribute("checked")) === "true";
keepMicEle.onchange = () => {
keepListenMic = keepMicEle.checked;
localStorage.setItem("keepListenMic", keepListenMic);
}
keepMicEle.dispatchEvent(new Event("change"));
let recIns = new webkitSpeechRecognition();
// prevent some Android bug
recIns.continuous = !isAndroid;
recIns.interimResults = true;
recIns.maxAlternatives = 1;
let recRes = tempRes = "";
let preRes, affRes;
const setAutoTimer = () => {
if (autoSendTime) {
autoSendTimer = setTimeout(() => {
genFunc();
autoSendTimer = void 0;
}, autoSendTime * 1000);
}
}
const resEvent = (event) => {
if (typeof (event.results) === "undefined") {
toggleRecEvent();
return;
}
let isFinal;
let autoFlag;
for (let i = event.resultIndex; i < event.results.length; ++i) {
isFinal = event.results[i].isFinal;
if (isFinal) {
recRes += event.results[i][0].transcript
if (autoSendWord) {
let idx = recRes.indexOf(autoSendWord);
if (idx !== -1) {
recRes = recRes.slice(0, idx);
autoFlag = 1;
break;
}
}
if (autoStopWord) {
let idx = recRes.indexOf(autoStopWord);
if (idx !== -1) {
recRes = recRes.slice(0, idx);
autoFlag = 2;
break;
}
}
}
else {tempRes = recRes + event.results[i][0].transcript}
}
inputAreaEle.value = preRes + (isFinal ? recRes : tempRes) + affRes;
textInputEvent();
inputAreaEle.focus();
inputAreaEle.selectionEnd = inputAreaEle.value.length - affRes.length;
if (autoFlag) {
if (autoFlag === 1) genFunc();
else endEvent(false, false);
}
clearAutoSendTimer();
if (autoFlag !== 1) setAutoTimer();
};
resetRecRes = () => {
preRes = inputAreaEle.value.slice(0, inputAreaEle.selectionStart);
affRes = inputAreaEle.value.slice(inputAreaEle.selectionEnd);
recRes = tempRes = "";
}
const stopAction = () => {
clearAutoSendTimer();
recIns.onresult = null;
recIns.onerror = null;
recIns.onend = null;
voiceRecEle.classList.remove("voiceRecing");
recing = false;
}
const endEvent = (event, flag) => {
if (flag !== void 0) {
if (!flag) {
recIns.stop();
stopAction();
}
} else if (event) {
if (keepListenMic && event.type === "end") {
recIns.start();
resetRecRes();
} else {
if (event.type === "error") recIns.stop();
stopAction();
}
}
};
const errorEvent = (ev) => {
if (event.error === "no-speech") {
notyf.open({
type: "warning",
message: translations[locale]["noSpeechTip"]
});
}
if (event.error === "audio-capture") {
notyf.error(translations[locale]["noMicTip"])
endEvent(ev);
}
if (event.error === "not-allowed") {
notyf.error(translations[locale]["noMicPerTip"])
endEvent(ev);
}
}
const closeEvent = (ev) => {
if (voiceRecSetEle.contains(ev.target)) return;
if (!voiceRecSetEle.contains(ev.target)) {
voiceRecSetEle.style.display = "none";
document.removeEventListener("mousedown", closeEvent, true);
voiceRecEle.classList.remove("voiceLong");
}
}
const longEvent = () => {
voiceRecSetEle.style.display = "block";
document.addEventListener("mousedown", closeEvent, true);
}
const toggleRecEvent = () => {
voiceRecEle.classList.toggle("voiceRecing");
if (voiceRecEle.classList.contains("voiceRecing")) {
try {
resetRecRes();
recIns.lang = select_dialect.value;
recIns.start();
recIns.onresult = resEvent;
recIns.onerror = errorEvent;
recIns.onend = endEvent;
recing = true;
} catch (e) {
endEvent(false, false);
}
} else {
endEvent(false, false);
}
};
toggleRecEv = toggleRecEvent;
let timer;
const voiceDownEvent = (ev) => {
ev.preventDefault();
let i = 0;
voiceRecEle.classList.add("voiceLong");
timer = setInterval(() => {
i += 1;
if (i >= 3) {
clearInterval(timer);
timer = void 0;
longEvent();
}
}, 100)
};
const voiceUpEvent = (ev) => {
ev.preventDefault();
if (timer !== void 0) {
toggleRecEvent();
clearInterval(timer);
timer = void 0;
voiceRecEle.classList.remove("voiceLong");
}
}
voiceRecEle.onmousedown = voiceDownEvent;
voiceRecEle.ontouchstart = voiceDownEvent;
voiceRecEle.onmouseup = voiceUpEvent;
voiceRecEle.ontouchend = voiceUpEvent;
};
};
initRecSetting();
document.querySelector(".sysSwitch").onclick = document.querySelector(".setSwitch").onclick = function (ev) {
let activeEle = this.getElementsByClassName("activeSwitch")[0];
if (ev.target !== activeEle) {
activeEle.classList.remove("activeSwitch");
ev.target.classList.add("activeSwitch");
document.getElementById(ev.target.dataset.id).style.display = "block";
document.getElementById(activeEle.dataset.id).style.display = "none";
}
};
if (!supportSpe) {
speechServiceEle.remove(2);
}
const initVoiceVal = () => {
let localVoiceType = localStorage.getItem("existVoice");
speechServiceEle.value = existVoice = parseInt(localVoiceType || "2");
}
initVoiceVal();
const clearAzureVoice = () => {
azureKey = void 0;
azureRegion = void 0;
azureRole = [];
azureStyle = [];
document.getElementById("azureExtra").style.display = "none";
azureKeyInput.parentElement.style.display = "none";
preSetAzureRegion.parentElement.style.display = "none";
if (azureTokenTimer) {
clearInterval(azureTokenTimer);
azureTokenTimer = void 0;
}
}
speechServiceEle.onchange = () => {
existVoice = parseInt(speechServiceEle.value);
localStorage.setItem("existVoice", existVoice);
toggleVoiceCheck(true);
if (checkAzureAbort && !checkAzureAbort.signal.aborted) {
checkAzureAbort.abort();
checkAzureAbort = void 0;
}
if (checkEdgeAbort && !checkEdgeAbort.signal.aborted) {
checkEdgeAbort.abort();
checkEdgeAbort = void 0;
}
if (existVoice === 3) {
azureKeyInput.parentElement.style.display = "block";
preSetAzureRegion.parentElement.style.display = "block";
loadAzureVoice();
} else if (existVoice === 2) {
clearAzureVoice();
loadEdgeVoice();
} else if (existVoice === 1) {
toggleVoiceCheck(false);
clearAzureVoice();
loadLocalVoice();
}
}
let azureVoiceData, edgeVoiceData, systemVoiceData, checkAzureAbort, checkEdgeAbort;
const toggleVoiceCheck = (bool) => {
checkVoiceLoad.style.display = bool ? "flex" : "none";
speechDetail.style.display = bool ? "none" : "block";
}
const loadAzureVoice = () => {
let checking = false;
const checkAzureFunc = () => {
if (checking) return;
if (azureKey) {
checking = true;
checkVoiceLoad.classList.add("voiceChecking");
if (azureTokenTimer) {
clearInterval(azureTokenTimer);
}
checkAzureAbort = new AbortController();
setTimeout(() => {
if (checkAzureAbort && !checkAzureAbort.signal.aborted) {
checkAzureAbort.abort();
checkAzureAbort = void 0;
}
}, 15000);
Promise.all([getAzureToken(checkAzureAbort.signal), getVoiceList(checkAzureAbort.signal)]).then(() => {
azureTokenTimer = setInterval(() => {
getAzureToken();
}, 540000);
toggleVoiceCheck(false);
}).catch(e => {
}).finally(() => {
checkVoiceLoad.classList.remove("voiceChecking");
checking = false;
})
}
};
checkVoiceLoad.onclick = checkAzureFunc;
const getAzureToken = (signal) => {
return new Promise((res, rej) => {
fetch("https://" + azureRegion + ".api.cognitive.microsoft.com/sts/v1.0/issueToken", {
signal,
method: "POST",
headers: {
"Ocp-Apim-Subscription-Key": azureKey
}
}).then(response => {
response.text().then(text => {
try {
let json = JSON.parse(text);
notyf.error(translations[locale]["azureInvalidTip"]);
rej();
} catch (e) {
azureToken = text;
res();
}
});
}).catch(e => {
rej();
})
})
};
const getVoiceList = (signal) => {
return new Promise((res, rej) => {
if (azureVoiceData) {
initVoiceSetting(azureVoiceData);
res();
} else {
let localAzureVoiceData = localStorage.getItem(azureRegion + "voiceData");
if (localAzureVoiceData) {
azureVoiceData = JSON.parse(localAzureVoiceData);
initVoiceSetting(azureVoiceData);
res();
} else {
fetch("https://" + azureRegion + ".tts.speech.microsoft.com/cognitiveservices/voices/list", {
signal,
headers: {
"Ocp-Apim-Subscription-Key": azureKey
}
}).then(response => {
response.json().then(json => {
azureVoiceData = json;
localStorage.setItem(azureRegion + "voiceData", JSON.stringify(json));
initVoiceSetting(json);
res();
}).catch(e => {
notyf.error(translations[locale]["azureInvalidTip"]);
rej();
})
}).catch(e => {
rej();
})
}
}
})
};
let azureRegionEle = document.getElementById("preSetAzureRegion");
if (!azureRegionEle.options.length) {
const azureRegions = ['southafricanorth', 'eastasia', 'southeastasia', 'australiaeast', 'centralindia', 'japaneast', 'japanwest', 'koreacentral', 'canadacentral', 'northeurope', 'westeurope', 'francecentral', 'germanywestcentral', 'norwayeast', 'switzerlandnorth', 'switzerlandwest', 'uksouth', 'uaenorth', 'brazilsouth', 'centralus', 'eastus', 'eastus2', 'northcentralus', 'southcentralus', 'westcentralus', 'westus', 'westus2', 'westus3'];
azureRegions.forEach((region, i) => {
let option = document.createElement("option");
option.value = region;
option.text = region;
azureRegionEle.options.add(option);
});
}
let localAzureRegion = localStorage.getItem("azureRegion");
if (localAzureRegion) {
azureRegion = localAzureRegion;
azureRegionEle.value = localAzureRegion;
}
azureRegionEle.onchange = () => {
azureRegion = azureRegionEle.value;
localStorage.setItem("azureRegion", azureRegion);
toggleVoiceCheck(true);
}
azureRegionEle.dispatchEvent(new Event("change"));
let azureKeyEle = document.getElementById("azureKeyInput");
let localAzureKey = localStorage.getItem("azureKey");
if (localAzureKey) {
azureKey = localAzureKey;
azureKeyEle.value = localAzureKey;
}
azureKeyEle.onchange = () => {
azureKey = azureKeyEle.value;
localStorage.setItem("azureKey", azureKey);
toggleVoiceCheck(true);
}
azureKeyEle.dispatchEvent(new Event("change"));
if (azureKey) {
checkAzureFunc();
}
}
const loadEdgeVoice = () => {
let checking = false;
const endCheck = () => {
checkVoiceLoad.classList.remove("voiceChecking");
checking = false;
};
const checkEdgeFunc = () => {
if (checking) return;
checking = true;
checkVoiceLoad.classList.add("voiceChecking");
if (edgeVoiceData) {
initVoiceSetting(edgeVoiceData);
toggleVoiceCheck(false);
endCheck();
} else {
checkEdgeAbort = new AbortController();
setTimeout(() => {
if (checkEdgeAbort && !checkEdgeAbort.signal.aborted) {
checkEdgeAbort.abort();
checkEdgeAbort = void 0;
}
}, 10000);
fetch("https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4", {signal: checkEdgeAbort.signal}).then(response => {
response.json().then(json => {
edgeVoiceData = json;
toggleVoiceCheck(false);
initVoiceSetting(json);
endCheck();
});
}).catch(err => {
endCheck();
})
}
};
checkEdgeFunc();
checkVoiceLoad.onclick = checkEdgeFunc;
};
const loadLocalVoice = () => {
if (systemVoiceData) {
initVoiceSetting(systemVoiceData);
} else {
let initedVoice = false;
const getLocalVoice = () => {
let voices = speechSynthesis.getVoices();
if (voices.length) {
if (!initedVoice) {
initedVoice = true;
systemVoiceData = voices;
initVoiceSetting(voices);
}
return true;
} else {
return false;
}
}
let syncExist = getLocalVoice();
if (!syncExist) {
if ("onvoiceschanged" in speechSynthesis) {
speechSynthesis.onvoiceschanged = () => {
getLocalVoice();
}
} else if (speechSynthesis.addEventListener) {
speechSynthesis.addEventListener("voiceschanged", () => {
getLocalVoice();
})
}
let timeout = 0;
let timer = setInterval(() => {
if (getLocalVoice() || timeout > 1000) {
if (timeout > 1000) {
existVoice = 0;
}
clearInterval(timer);
timer = null;
}
timeout += 300;
}, 300)
}
}
};
const initVoiceSetting = (voices) => {
let isOnline = existVoice >= 2;
let voicesEle = document.getElementById("preSetSpeech");
// 支持中文和英文
voices = isOnline ? voices.filter(item => item.Locale.match(/^(zh-|en-)/)) : voices.filter(item => item.lang.match(/^(zh-|en-)/));
if (isOnline) {
voices.map(item => {
item.name = item.FriendlyName || (`${item.DisplayName} Online (${item.VoiceType}) - ${item.LocaleName}`);
item.lang = item.Locale;
})
}
voices.sort((a, b) => {
if (a.lang.slice(0, 2) === b.lang.slice(0, 2)) {
if (a.lang.slice(0, 2) === "zh") {
return (a.lang === b.lang) ? 0 : (a.lang > b.lang) ? 1 : -1; // zh-CN 在前
} else {
return 0
}
}
return (locale === "zh" ? (a.lang < b.lang) : (a.lang > b.lang)) ? 1 : -1; // 中文UI,则中文"z"在前
});
voices.map(item => {
if (item.name.match(/^(Google |Microsoft )/)) {
item.displayName = item.name.replace(/^.*? /, "");
} else {
item.displayName = item.name;
}
});
voicesEle.innerHTML = "";
voices.forEach((voice, i) => {
let option = document.createElement("option");
option.value = i;
option.text = voice.displayName;
voicesEle.options.add(option);
});
voicesEle.onchange = () => {
voiceRole[voiceType] = voices[voicesEle.value];
localStorage.setItem("voice" + voiceType, voiceRole[voiceType].name);
if (voiceRole[voiceType].StyleList || voiceRole[voiceType].RolePlayList) {
document.getElementById("azureExtra").style.display = "block";
let voiceStyles = voiceRole[voiceType].StyleList;
let voiceRoles = voiceRole[voiceType].RolePlayList;
if (voiceRoles) {
preSetVoiceRole.innerHTML = "";
voiceRoles.forEach((role, i) => {
let option = document.createElement("option");
option.value = role;
option.text = role;
preSetVoiceRole.options.add(option);
});
let localRole = localStorage.getItem("azureRole" + voiceType);
if (localRole && voiceRoles.indexOf(localRole) !== -1) {
preSetVoiceRole.value = localRole;
azureRole[voiceType] = localRole;
} else {
preSetVoiceRole.selectedIndex = 0;
azureRole[voiceType] = voiceRole[0];
}
preSetVoiceRole.onchange = () => {
azureRole[voiceType] = preSetVoiceRole.value;
localStorage.setItem("azureRole" + voiceType, preSetVoiceRole.value);
}
preSetVoiceRole.dispatchEvent(new Event("change"));
} else {
azureRole[voiceType] = void 0;
localStorage.removeItem("azureRole" + voiceType);
}
preSetVoiceRole.style.display = voiceRoles ? "block" : "none";
preSetVoiceRole.previousElementSibling.style.display = voiceRoles ? "block" : "none";
if (voiceStyles) {
preSetVoiceStyle.innerHTML = "";
voiceStyles.forEach((style, i) => {
let option = document.createElement("option");
option.value = style;
option.text = style;
preSetVoiceStyle.options.add(option);
});
let localStyle = localStorage.getItem("azureStyle" + voiceType);
if (localStyle && voiceStyles.indexOf(localStyle) !== -1) {
preSetVoiceStyle.value = localStyle;
azureStyle[voiceType] = localStyle;
} else {
preSetVoiceStyle.selectedIndex = 0;
azureStyle[voiceType] = voiceStyles[0];
}
preSetVoiceStyle.onchange = () => {
azureStyle[voiceType] = preSetVoiceStyle.value;
localStorage.setItem("azureStyle" + voiceType, preSetVoiceStyle.value)
}
preSetVoiceStyle.dispatchEvent(new Event("change"));
} else {
azureStyle[voiceType] = void 0;
localStorage.removeItem("azureStyle" + voiceType);
}
preSetVoiceStyle.style.display = voiceStyles ? "block" : "none";
preSetVoiceStyle.previousElementSibling.style.display = voiceStyles ? "block" : "none";
} else {
document.getElementById("azureExtra").style.display = "none";
azureRole[voiceType] = void 0;
localStorage.removeItem("azureRole" + voiceType);
azureStyle[voiceType] = void 0;
localStorage.removeItem("azureStyle" + voiceType);
}
};
const loadAnother = (type) => {
type = type ^ 1;
let localVoice = localStorage.getItem("voice" + type);
if (localVoice) {
let localIndex = voices.findIndex(item => {return item.name === localVoice});
if (localIndex === -1) localIndex = 0;
voiceRole[type] = voices[localIndex];
} else {
voiceRole[type] = voices[0];
}
if (existVoice === 3) {
let localStyle = localStorage.getItem("azureStyle" + type);
azureStyle[type] = localStyle ? localStyle : void 0;
let localRole = localStorage.getItem("azureRole" + type);
azureRole[type] = localRole ? localRole : void 0;
}
}
const voiceChange = () => {
let localVoice = localStorage.getItem("voice" + voiceType);
if (localVoice) {
let localIndex = voices.findIndex(item => {return item.name === localVoice});
if (localIndex === -1) localIndex = 0;
voiceRole[voiceType] = voices[localIndex];
voicesEle.value = localIndex;
} else {
voiceRole[voiceType] = voices[0];
}
voicesEle.dispatchEvent(new Event("change"));
}
voiceChange();
loadAnother(voiceType);
let volumeEle = document.getElementById("voiceVolume");
let localVolume = localStorage.getItem("voiceVolume0");
voiceVolume[0] = parseFloat(localVolume || volumeEle.getAttribute("value"));
const voiceVolumeChange = () => {
let localVolume = localStorage.getItem("voiceVolume" + voiceType);
volumeEle.value = voiceVolume[voiceType] = parseFloat(localVolume || volumeEle.getAttribute("value"));
volumeEle.style.backgroundSize = (volumeEle.value - volumeEle.min) * 100 / (volumeEle.max - volumeEle.min) + "% 100%";
}
volumeEle.oninput = () => {
volumeEle.style.backgroundSize = (volumeEle.value - volumeEle.min) * 100 / (volumeEle.max - volumeEle.min) + "% 100%";
voiceVolume[voiceType] = parseFloat(volumeEle.value);
localStorage.setItem("voiceVolume" + voiceType, volumeEle.value);
}
voiceVolumeChange();
let rateEle = document.getElementById("voiceRate");
let localRate = localStorage.getItem("voiceRate0");
voiceRate[0] = parseFloat(localRate || rateEle.getAttribute("value"));
const voiceRateChange = () => {
let localRate = localStorage.getItem("voiceRate" + voiceType);
rateEle.value = voiceRate[voiceType] = parseFloat(localRate || rateEle.getAttribute("value"));
rateEle.style.backgroundSize = (rateEle.value - rateEle.min) * 100 / (rateEle.max - rateEle.min) + "% 100%";
}
rateEle.oninput = () => {
rateEle.style.backgroundSize = (rateEle.value - rateEle.min) * 100 / (rateEle.max - rateEle.min) + "% 100%";
voiceRate[voiceType] = parseFloat(rateEle.value);
localStorage.setItem("voiceRate" + voiceType, rateEle.value);
}
voiceRateChange();
let pitchEle = document.getElementById("voicePitch");
let localPitch = localStorage.getItem("voicePitch0");
voicePitch[0] = parseFloat(localPitch || pitchEle.getAttribute("value"));
const voicePitchChange = () => {
let localPitch = localStorage.getItem("voicePitch" + voiceType);
pitchEle.value = voicePitch[voiceType] = parseFloat(localPitch || pitchEle.getAttribute("value"));
pitchEle.style.backgroundSize = (pitchEle.value - pitchEle.min) * 100 / (pitchEle.max - pitchEle.min) + "% 100%";
}
pitchEle.oninput = () => {
pitchEle.style.backgroundSize = (pitchEle.value - pitchEle.min) * 100 / (pitchEle.max - pitchEle.min) + "% 100%";
voicePitch[voiceType] = parseFloat(pitchEle.value);
localStorage.setItem("voicePitch" + voiceType, pitchEle.value);
}
voicePitchChange();
document.getElementById("voiceTypes").onclick = (ev) => {
let type = ev.target.dataset.type;
if (type !== void 0) {
type = parseInt(type);
if (type != voiceType) {
voiceType = type;
ev.target.classList.add("selVoiceType");
ev.target.parentElement.children[type ^ 1].classList.remove("selVoiceType");
voiceChange();
voiceVolumeChange();
voiceRateChange();
voicePitchChange();
}
};
};
const voiceTestEle = document.getElementById("testVoiceText");
let localTestVoice = localStorage.getItem("voiceTestText");
voiceTestText = voiceTestEle.value = localTestVoice || voiceTestEle.getAttribute("value");
voiceTestEle.oninput = () => {
voiceTestText = voiceTestEle.value;
localStorage.setItem("voiceTestText", voiceTestText);
}
const contVoiceEle = document.getElementById("enableContVoice");
let localCont = localStorage.getItem("enableContVoice");
contVoiceEle.checked = enableContVoice = (localCont || contVoiceEle.getAttribute("checked")) === "true";
contVoiceEle.onchange = () => {
enableContVoice = contVoiceEle.checked;
localStorage.setItem("enableContVoice", enableContVoice);
}
contVoiceEle.dispatchEvent(new Event("change"));
const autoVoiceEle = document.getElementById("enableAutoVoice");
let localAuto = localStorage.getItem("enableAutoVoice");
autoVoiceEle.checked = enableAutoVoice = (localAuto || autoVoiceEle.getAttribute("checked")) === "true";
autoVoiceEle.onchange = () => {
enableAutoVoice = autoVoiceEle.checked;
localStorage.setItem("enableAutoVoice", enableAutoVoice);
}
autoVoiceEle.dispatchEvent(new Event("change"));
};
speechServiceEle.dispatchEvent(new Event("change"));
</script>
<script crossorigin="anonymous" src="//cdn.staticfile.org/markdown-it/13.0.1/markdown-it.min.js"></script>
<script crossorigin="anonymous" src="//cdn.staticfile.org/highlight.js/11.8.0/highlight.min.js"></script>
<script crossorigin="anonymous" src="//cdn.staticfile.org/KaTeX/0.16.7/katex.min.js"></script>
<script crossorigin="anonymous" src="//npm.elemecdn.com/[email protected]/texmath.js"></script>
<script crossorigin="anonymous"
src="//npm.elemecdn.com/[email protected]/dist/markdown-it-link-attributes.min.js"></script>
<script>
const API_URL = "v1/chat/completions";
let loading = false;
let presetRoleData = {
"default": translations[locale]["defaultText"],
"normal": translations[locale]["assistantText"],
"cat": translations[locale]["catText"],
"emoji": translations[locale]["emojiText"],
"image": translations[locale]["imageText"]
};
let modelVersion; // 模型版本
let apiHost; // api反代地址
let apiSelects = []; // api地址列表
let customAPIKey; // 自定义apiKey
let systemRole; // 自定义系统角色
let roleNature; // 角色性格
let roleTemp; // 回答质量
let convWidth = []; // 会话宽度,0:窗口宽度,1:全屏宽度
let textSpeed; // 打字机速度,越小越快
let enableCont; // 是否开启连续对话,默认开启,对话包含上下文信息。
let enableLongReply; // 是否开启长回复,默认关闭,开启可能导致api费用增加。
let longReplyFlag;
let voiceIns; // Audio or SpeechSynthesisUtterance
const supportMSE = !!window.MediaSource; // 是否支持MSE(除了ios应该都支持)
let voiceMIME = "audio/mpeg";
let userAvatar; // 用户头像
let customDarkOut;
const isContentBottom = () => messagesEle.scrollHeight - messagesEle.scrollTop - messagesEle.clientHeight < 128;
const scrollToBottom = () => {
if (isContentBottom()) {
messagesEle.scrollTo(0, messagesEle.scrollHeight)
}
}
const scrollToBottomLoad = (ele) => {
if (messagesEle.scrollHeight - messagesEle.scrollTop - messagesEle.clientHeight < ele.clientHeight + 128) {
messagesEle.scrollTo(0, messagesEle.scrollHeight)
}
}
const forceRepaint = (ele) => {
ele.style.display = "none";
ele.offsetHeight;
ele.style.display = null;
}
const escapeTextarea = document.createElement("textarea");
const getEscape = str => {
escapeTextarea.textContent = str;
return escapeTextarea.innerHTML;
}
const getUnescape = html => {
escapeTextarea.innerHTML = html;
return escapeTextarea.textContent;
}
const formatDate = date => {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
}
const checkBill = async () => {
let headers = {"Content-Type": "application/json"};
if (customAPIKey) headers["Authorization"] = "Bearer " + customAPIKey;
let subCtrl = new AbortController();
setTimeout(() => {
subCtrl.abort();
}, 20000);
let subRes;
try {
subRes = await fetch(apiHost + "v1/dashboard/billing/subscription", {
headers,
signal: subCtrl.signal
});
if (subRes.status !== 200) return;
} catch (e) {
return;
}
const subData = await subRes.json();
const totalQuota = subData.hard_limit_usd;
const isSubsrcibed = subData.has_payment_method;
const urlUsage = apiHost + "v1/dashboard/billing/usage?";
let useCtrl = new AbortController();
const nowDate = new Date(new Date().getTime() + dayMs);
let usedData;
let expired;
setTimeout(() => {
useCtrl.abort();
}, 20000);
if (isSubsrcibed) {
const subDate = new Date();
subDate.setDate(1);
try {
const useRes = await fetch(urlUsage + `start_date=${formatDate(subDate)}&end_date=${formatDate(nowDate)}`, {
headers,
signal: useCtrl.signal
});
const useJson = await useRes.json();
usedData = useJson.total_usage / 100;
} catch (e) {
return;
}
} else {
let urls = [];
const expireTime = subData.access_until * 1000;
expired = new Date(expireTime);
const startDate = new Date(expireTime - 128 * dayMs);
const midDate = new Date(expireTime - 64 * dayMs);
const endDate = new Date(expireTime + dayMs);
if (nowDate < midDate) {
urls.push(`start_date=${formatDate(startDate)}&end_date=${formatDate(nowDate)}`);
} else if (nowDate < endDate) {
urls.push(`start_date=${formatDate(startDate)}&end_date=${formatDate(midDate)}`);
if (formatDate(nowDate) !== formatDate(midDate)) {
urls.push(`start_date=${formatDate(midDate)}&end_date=${formatDate(nowDate)}`);
}
} else {
urls.push(`start_date=${formatDate(startDate)}&end_date=${formatDate(midDate)}`);
urls.push(`start_date=${formatDate(midDate)}&end_date=${formatDate(endDate)}`);
}
try {
const uses = await Promise.all(urls.map(async url => {
return new Promise((res, rej) => {
fetch(urlUsage + url, {
headers,
signal: useCtrl.signal
}).then(response => {
response.json().then(json => res(json.total_usage));
}).catch(() => {
rej();
})
})
}));
usedData = uses.reduce((prev, curr) => prev + curr, 0) / 100;
} catch (e) {
return;
}
}
if (usedData > totalQuota) usedData = totalQuota;
return [totalQuota, usedData, isSubsrcibed, expired];
}
let checkingBill = false;
checkBillBtn.onclick = async () => {
if (checkingBill) return;
checkingBill = true;
checkBillBtn.className = "loading";
quotaContent.style.display = "none";
try {
let res = await checkBill();
if (!res) throw new Error("API error");
let [total, used, isSub, exp] = res;
usedQuotaBar.style.width = (used / total * 100).toFixed(2) + "%";
usedQuotaBar.parentElement.classList.remove("expiredBar");
usedQuota.textContent = "$" + used.toFixed(2);
let remain = total - used;
availableQuota.textContent = "$" + Math.floor(remain * 100) / 100;
if (isSub) {
quotaTitle.textContent = translations[locale]["thisQuota"];
} else {
let isExpired = new Date() > exp;
if (isExpired) usedQuotaBar.parentElement.classList.add("expiredBar");
let accessDate = formatDate(exp);
quotaTitle.innerHTML = translations[locale]["freeTierTip"] + `<span style="color: ${isExpired ? "#e15b64" : "#99c959"} ">${accessDate}</span>`;
}
quotaContent.style.display = "block";
} catch (e) {
notyf.error(translations[locale]["errorAiKeyTip"]);
}
checkingBill = false;
checkBillBtn.className = "loaded";
}
const checkStorage = () => {
let used = 0;
for (let key in localStorage) {
localStorage.hasOwnProperty(key) && (used += localStorage[key].length)
}
let remain = 5242880 - used;
usedStorageBar.style.width = (used / 5242880 * 100).toFixed(2) + "%";
let usedMBs = used / 1048576;
usedStorage.textContent = (usedMBs < 1 ? usedMBs.toPrecision(2) : usedMBs.toFixed(2)) + "MB";
availableStorage.textContent = Math.floor(remain / 1048576 * 100) / 100 + "MB";
};
const md = markdownit({
linkify: true, // 识别链接
highlight: function (str, lang) { // markdown高亮
try {
return hljs.highlightAuto(str).value;
} catch (e) { }
return ""; // use external default escaping
}
});
md.use(texmath, {engine: katex, delimiters: "dollars", katexOptions: {macros: {"\RR": "\mathbb{R}"}}})
.use(markdownitLinkAttributes, {attrs: {target: "_blank", rel: "noopener"}});
const codeUtils = {
getCodeLang(str = "") {
const res = str.match(/ class="language-(.*?)"/);
return (res && res[1]) || "";
},
getFragment(str = "") {
return str ? `<span class="u-mdic-copy-code_lang">${str}</span>` : "";
},
};
const getCodeLangFragment = (oriStr = "") => {
return codeUtils.getFragment(codeUtils.getCodeLang(oriStr));
};
const copyClickCode = (ele) => {
const input = document.createElement("textarea");
input.value = ele.parentElement.previousElementSibling.textContent;
const nDom = ele.previousElementSibling;
const nDelay = ele.dataset.mdicNotifyDelay;
const cDom = nDom.previousElementSibling;
document.body.appendChild(input);
input.select();
input.setSelectionRange(0, input.value.length);
document.execCommand("copy");
document.body.removeChild(input);
if (nDom.style.display === "none") {
nDom.style.display = "block";
cDom && (cDom.style.display = "none");
setTimeout(() => {
nDom.style.display = "none";
cDom && (cDom.style.display = "block");
}, nDelay);
}
};
const copyClickMd = (idx) => {
const input = document.createElement("textarea");
input.value = data[idx].content;
document.body.appendChild(input);
input.select();
input.setSelectionRange(0, input.value.length);
document.execCommand("copy");
document.body.removeChild(input);
}
const enhanceCode = (render, options = {}) => (...args) => {
/* args = [tokens, idx, options, env, slf] */
const {
btnText = translations[locale]["copyCode"], // button text
successText = translations[locale]["copySuccess"], // copy-success text
successTextDelay = 2000, // successText show time [ms]
showCodeLanguage = true, // false | show code language
} = options;
const [tokens = {}, idx = 0] = args;
const originResult = render.apply(this, args);
const langFrag = showCodeLanguage ? getCodeLangFragment(originResult) : "";
const tpls = [
'<div class="m-mdic-copy-wrapper">',
`${langFrag}`,
`<div class="u-mdic-copy-notify" style="display:none;">${successText}</div>`,
'<button ',
'class="u-mdic-copy-btn j-mdic-copy-btn" ',
`data-mdic-notify-delay="${successTextDelay}" `,
`onclick="copyClickCode(this)">${btnText}</button>`,
'</div>',
];
return originResult.replace("</pre>", `${tpls.join("")}</pre>`);
};
md.renderer.rules.code_block = enhanceCode(md.renderer.rules.code_block);
md.renderer.rules.fence = enhanceCode(md.renderer.rules.fence);
md.renderer.rules.image = function (tokens, idx, options, env, slf) {
let token = tokens[idx];
token.attrs[token.attrIndex("alt")][1] = slf.renderInlineAsText(token.children, options, env);
token.attrSet("onload", "scrollToBottomLoad(this);this.removeAttribute('onload');this.removeAttribute('onerror')");
token.attrSet("onerror", "scrollToBottomLoad(this);this.removeAttribute('onload');this.removeAttribute('onerror')");
return slf.renderToken(tokens, idx, options)
}
let currentVoiceIdx;
let editingIdx;
let originText;
const resumeSend = () => {
if (editingIdx !== void 0) {
chatlog.children[systemRole ? editingIdx - 1 : editingIdx].classList.remove("showEditReq");
}
sendBtnEle.children[0].textContent = translations[locale]["send"];
inputAreaEle.value = originText;
clearEle.title = translations[locale]["clearChat"];
clearEle.classList.remove("closeConv");
originText = void 0;
editingIdx = void 0;
}
const mdOptionEvent = function (ev) {
let id = ev.target.dataset.id;
if (id) {
let parent = ev.target.parentElement;
let idxEle = parent.parentElement;
let idx = Array.prototype.indexOf.call(chatlog.children, this.parentElement);
if (id === "voiceBtn" || id === "speechMd" || id === "pauseMd" || id === "resumeMd") {
let classList = parent.dataset.id === "voiceBtn" ? parent.classList : ev.target.classList;
if (classList.contains("readyVoice")) {
if (chatlog.children[idx].dataset.loading !== "true") {
idx = systemRole ? idx + 1 : idx;
speechEvent(idx);
}
} else if (classList.contains("pauseVoice")) {
if (voiceIns) {
if (voiceIns instanceof Audio) voiceIns.pause();
else {
if (supportSpe) speechSynthesis.pause();
classList.remove("readyVoice");
classList.remove("pauseVoice");
classList.add("resumeVoice");
}
}
} else {
if (voiceIns) {
if (voiceIns instanceof Audio) voiceIns.play();
else {
if (supportSpe) speechSynthesis.resume();
classList.remove("readyVoice");
classList.remove("resumeVoice");
classList.add("pauseVoice");
}
}
}
} else if (id === "editMd") {
let reqEle = chatlog.children[idx];
idx = systemRole ? idx + 1 : idx;
if (editingIdx === idx) return;
if (editingIdx !== void 0) {
chatListEle.children[systemRole ? editingIdx - 1 : editingIdx].classList.remove("showEditReq");
}
reqEle.classList.add("showEditReq");
editingIdx = idx;
originText = inputAreaEle.value;
inputAreaEle.value = data[idx].content;
inputAreaEle.dispatchEvent(new Event("input"));
inputAreaEle.focus();
sendBtnEle.children[0].textContent = translations[locale]["update"];
clearEle.title = translations[locale]["cancel"];
clearEle.classList.add("closeConv");
} else if (id === "refreshMd") {
if (noLoading()) {
if (ev.target.classList.contains("refreshReq")) {
chatlog.children[idx].children[1].innerHTML = "<p class='cursorCls'><br /></p>";
chatlog.children[idx].dataset.loading = true;
idx = systemRole ? idx + 1 : idx;
data[idx].content = "";
if (idx === currentVoiceIdx) {endSpeak()};
loadAction(true);
refreshIdx = idx;
streamGen();
} else {
chatlog.children[idx].dataset.loading = true;
idx = systemRole ? idx + 1 : idx;
progressData = data[idx].content;
loadAction(true);
refreshIdx = idx;
streamGen(true);
}
}
} else if (id === "copyMd") {
idx = systemRole ? idx + 1 : idx;
copyClickMd(idx);
notyf.success(translations[locale]["copySuccess"]);
} else if (id === "delMd") {
if (noLoading()) {
if (confirmAction(translations[locale]["delMsgTip"])) {
chatlog.removeChild(chatlog.children[idx]);
idx = systemRole ? idx + 1 : idx;
let firstIdx = data.findIndex(item => {return item.role === "assistant"});
if (currentVoiceIdx !== void 0) {
if (currentVoiceIdx === idx) {endSpeak()}
else if (currentVoiceIdx > idx) {currentVoiceIdx--}
}
if (editingIdx !== void 0) {
if (editingIdx === idx) {resumeSend()}
else if (editingIdx > idx) {editingIdx--}
}
data.splice(idx, 1);
if (firstIdx === idx) updateChatPre();
updateChats();
}
}
} else if (id === "downAudioMd") {
if (chatlog.children[idx].dataset.loading !== "true") {
idx = systemRole ? idx + 1 : idx;
downloadAudio(idx);
}
}
}
}
const formatMdEle = (ele, model) => {
let avatar = document.createElement("div");
avatar.className = "chatAvatar";
if (ele.className === "response") avatar.classList.add((model && model.startsWith("gpt-4")) ? "gpt4Avatar" : "gpt3Avatar");
avatar.innerHTML = ele.className === "request" ? `<img src="${userAvatar}" />` : `<svg width="22" height="22"><use xlink:href="#aiIcon"></use></svg>`;
ele.appendChild(avatar);
let realMd = document.createElement("div");
realMd.className = ele.className === "request" ? "requestBody" : "markdown-body";
ele.appendChild(realMd);
let mdOption = document.createElement("div");
mdOption.className = "mdOption";
ele.appendChild(mdOption);
let optionWidth = existVoice >= 2 ? 140 : 105;
mdOption.innerHTML += `<div class="optionItems" style="width:${optionWidth}px;left:-${optionWidth - 10}px">`
+ (ele.className === "request" ? `<div data-id="editMd" class="optionItem" title="${translations[locale]["edit"]}">
<svg width="18" height="18"><use xlink:href="#chatEditIcon" /></svg>
</div>` : `<div data-id="refreshMd" class="refreshReq optionItem" title="${translations[locale]["refresh"]}">
<svg width="16" height="16" ><use xlink:href="#refreshIcon" /></svg>
<svg width="16" height="16" ><use xlink:href="#halfRefIcon" /></svg>
</div>`) +
`<div data-id="copyMd" class="optionItem" title="${translations[locale]["copy"]}">
<svg width="20" height="20"><use xlink:href="#copyIcon" /></svg>
</div>
<div data-id="delMd" class="optionItem" title="${translations[locale]["del"]}">
<svg width="20" height="20"><use xlink:href="#delIcon" /></svg>
</div>` + (existVoice >= 2 ? `<div data-id="downAudioMd" class="optionItem" title="${translations[locale]["downAudio"]}">
<svg width="20" height="20"><use xlink:href="#downAudioIcon" /></svg>
</div>` : "") + `</div>`;
if (existVoice) {
mdOption.innerHTML += `<div class="voiceCls readyVoice" data-id="voiceBtn">
<svg width="20" height="20" role="img" data-id="speechMd"><title>${translations[locale]["speech"]}</title><use xlink:href="#readyVoiceIcon" /></svg>
<svg width="20" height="20" role="img" data-id="pauseMd"><title>${translations[locale]["pause"]}</title><use xlink:href="#pauseVoiceIcon" /></svg>
<svg width="20" height="20" role="img" data-id="resumeMd"><title>${translations[locale]["resume"]}</title><use xlink:href="#resumeVoiceIcon" /></svg>
</div>`
}
mdOption.onclick = mdOptionEvent;
}
let allListEle = chatListEle.parentElement;
let folderData = [];
let chatsData = [];
let chatIdxs = [];
let searchIdxs = [];
let activeChatIdx = 0;
let activeChatEle;
let operateChatIdx, operateFolderIdx;
let dragLi, dragType, dragIdx;
let mobileDragOut;
const mobileDragStartEV = function (ev) {
if (mobileDragOut !== void 0) {
clearTimeout(mobileDragOut);
mobileDragOut = void 0;
}
mobileDragOut = setTimeout(() => {
this.setAttribute("draggable", "true");
this.dispatchEvent(ev);
}, 200);
};
if (isMobile) {
let stopDragOut = () => {
if (mobileDragOut !== void 0) {
clearTimeout(mobileDragOut);
mobileDragOut = void 0;
}
};
let stopDrag = () => {
stopDragOut();
document.querySelectorAll("[draggable=true]").forEach(ele => {
ele.setAttribute("draggable", "false");
})
};
document.body.addEventListener("touchmove", stopDragOut);
document.body.addEventListener("touchend", stopDrag);
document.body.addEventListener("touchcancel", stopDrag);
};
const delDragIdx = () => {
let chatIdx = chatIdxs.indexOf(dragIdx);
if (chatIdx !== -1) {
chatIdxs.splice(chatIdx, 1);
} else {
folderData.forEach((item, i) => {
let inIdx = item.idxs.indexOf(dragIdx);
if (inIdx !== -1) {
item.idxs.splice(inIdx, 1);
updateFolder(i);
}
})
}
}
const updateFolder = (idx) => {
let folderEle = folderListEle.children[idx];
let childLen = folderData[idx].idxs.length;
folderEle.children[0].children[1].children[1].textContent = childLen + translations[locale]["chats"];
if (childLen) {folderEle.classList.add("expandFolder")}
else {folderEle.classList.remove("expandFolder")}
}
folderListEle.ondragenter = chatListEle.ondragenter = function (ev) {
ev.preventDefault();
if (ev.target === dragLi) return;
allListEle.querySelectorAll(".dragingChat").forEach(ele => {
ele.classList.remove("dragingChat");
})
if (dragType === "chat") {
if (this === chatListEle) {
this.classList.add("dragingChat");
let dragindex = Array.prototype.indexOf.call(chatListEle.children, dragLi);
let targetindex = Array.prototype.indexOf.call(chatListEle.children, ev.target);
delDragIdx();
if (targetindex !== -1) {
chatIdxs.splice(targetindex, 0, dragIdx);
if (dragindex === -1 || dragindex >= targetindex) {
chatListEle.insertBefore(dragLi, ev.target);
} else {
chatListEle.insertBefore(dragLi, ev.target.nextElementSibling);
}
} else {
chatIdxs.push(dragIdx);
chatListEle.appendChild(dragLi);
}
} else if (this === folderListEle) {
let folderIdx;
if (ev.target.classList.contains("headLi")) {
ev.target.parentElement.classList.add("dragingChat");
ev.target.nextElementSibling.appendChild(dragLi);
delDragIdx();
folderIdx = Array.prototype.indexOf.call(folderListEle.children, ev.target.parentElement);
folderData[folderIdx].idxs.push(dragIdx);
updateFolder(folderIdx);
} else if (ev.target.classList.contains("chatLi")) {
ev.target.parentElement.parentElement.classList.add("dragingChat");
let parent = ev.target.parentElement;
delDragIdx();
folderIdx = Array.prototype.indexOf.call(folderListEle.children, parent.parentElement);
let dragindex = Array.prototype.indexOf.call(parent.children, dragLi);
let targetindex = Array.prototype.indexOf.call(parent.children, ev.target);
if (dragindex !== -1) {
folderData[folderIdx].idxs.splice(targetindex, 0, dragIdx);
if (dragindex < targetindex) {
parent.insertBefore(dragLi, ev.target.nextElementSibling);
} else {
parent.insertBefore(dragLi, ev.target);
}
} else {
folderData[folderIdx].idxs.push(dragIdx);
parent.appendChild(dragLi);
}
updateFolder(folderIdx);
}
}
updateChatIdxs();
} else if (dragType === "folder") {
if (this === folderListEle) {
let dragindex = Array.prototype.indexOf.call(folderListEle.children, dragLi);
let folderIdx = Array.prototype.findIndex.call(folderListEle.children, (item) => {
return item.contains(ev.target);
})
folderListEle.children[folderIdx].classList.remove("expandFolder");
let folderEle = folderListEle.children[folderIdx];
let data = folderData.splice(dragindex, 1)[0];
folderData.splice(folderIdx, 0, data);
if (dragindex === -1 || dragindex >= folderIdx) {
folderListEle.insertBefore(dragLi, folderEle);
} else {
folderListEle.insertBefore(dragLi, folderEle.nextElementSibling);
}
updateChatIdxs();
}
}
}
folderListEle.ondragover = chatListEle.ondragover = (ev) => {
ev.preventDefault();
}
folderListEle.ondragend = chatListEle.ondragend = (ev) => {
document.getElementsByClassName("dragingLi")[0].classList.remove("dragingLi");
allListEle.querySelectorAll(".dragingChat").forEach(ele => {
ele.classList.remove("dragingChat");
})
dragType = dragIdx = dragLi = void 0;
}
const chatDragStartEv = function (ev) {
ev.stopPropagation();
dragLi = this;
dragLi.classList.add("dragingLi");
dragType = "chat";
if (chatListEle.contains(this)) {
let idx = Array.prototype.indexOf.call(chatListEle.children, this);
dragIdx = chatIdxs[idx];
} else if (folderListEle.contains(this)) {
let folderIdx = Array.prototype.indexOf.call(folderListEle.children, this.parentElement.parentElement);
let inFolderIdx = Array.prototype.indexOf.call(this.parentElement.children, this);
dragIdx = folderData[folderIdx].idxs[inFolderIdx];
}
}
const extraFolderActive = (folderIdx) => {
let folderNewIdx = -1;
for (let i = folderIdx - 1; i >= 0; i--) {
if (folderData[i].idxs.length) {
folderNewIdx = i;
}
}
if (folderNewIdx === -1) {
for (let i = folderIdx + 1; i < folderData.length; i++) {
if (folderData[i].idxs.length) folderNewIdx = i;
}
}
if (folderNewIdx !== -1) {
activeChatIdx = folderData[folderNewIdx].idxs[0];
} else if (chatIdxs.length) {
activeChatIdx = chatIdxs[0];
} else {
activeChatIdx = -1;
}
}
const delFolder = (folderIdx, ele) => {
if (confirmAction(translations[locale]["delFolderTip"])) {
let delData = folderData[folderIdx];
let idxs = delData.idxs.sort();
ele.parentElement.remove();
if (idxs.indexOf(activeChatIdx) !== -1) {
endAll();
extraFolderActive(folderIdx);
}
folderData.splice(folderIdx, 1);
for (let i = idxs.length - 1; i >= 0; i--) {
chatsData.splice(idxs[i], 1);
}
folderData.forEach(item => {
if (item.idxs.length) {
item.idxs.forEach((i, ix) => {
let len = idxs.filter(j => {return i > j}).length;
if (len) {
item.idxs[ix] = i - len;
}
})
}
})
chatIdxs.forEach((item, ix) => {
let len = idxs.filter(j => {return item > j}).length;
if (len) chatIdxs[ix] = item - len;
})
let len = idxs.filter(j => {return activeChatIdx > j}).length;
if (len) activeChatIdx -= len;
if (activeChatIdx === -1) {
addNewChat();
activeChatIdx = 0;
chatEleAdd(activeChatIdx);
}
updateChats();
activeChat();
}
}
const folderAddChat = (folderIdx, headEle) => {
endAll();
let chat = {name: translations[locale]["newChatName"], data: []};
chatsData.push(chat);
activeChatIdx = chatsData.length - 1;
folderData[folderIdx].idxs.push(activeChatIdx);
let ele = chatEleAdd(activeChatIdx, false)
headEle.nextElementSibling.appendChild(ele);
updateFolder(folderIdx);
updateChats();
activeChat(ele);
}
const folderEleEvent = function (ev) {
ev.preventDefault();
ev.stopPropagation();
let parent = this.parentElement;
let idx = Array.prototype.indexOf.call(folderListEle.children, parent);
if (ev.target.className === "headLi") {
parent.classList.toggle("expandFolder");
if (folderData[idx].idxs.indexOf(activeChatIdx) !== -1) {
if (parent.classList.contains("expandFolder")) {
parent.classList.remove("activeFolder");
} else {
parent.classList.add("activeFolder");
}
}
} else if (ev.target.dataset.type === "folderAddChat") {
folderAddChat(idx, this);
} else if (ev.target.dataset.type === "folderEdit") {
toEditName(idx, this, 0);
} else if (ev.target.dataset.type === "folderDel") {
delFolder(idx, this);
}
}
const folderDragStartEv = function (ev) {
dragLi = this;
dragLi.classList.add("dragingLi");
dragType = "folder";
dragIdx = Array.prototype.indexOf.call(folderListEle.children, this);
}
const folderEleAdd = (idx, push = true) => {
let folder = folderData[idx];
let folderEle = document.createElement("div");
folderEle.className = "folderLi";
if (!isMobile) folderEle.setAttribute("draggable", "true");
else folderEle.ontouchstart = mobileDragStartEV;
let headEle = document.createElement("div");
headEle.className = "headLi";
headEle.innerHTML = `<svg width="24" height="24"><use xlink:href="#expandFolderIcon" /></svg>
<div class="folderInfo">
<div class="folderName"></div>
<div class="folderNum"></div>
</div>
<div class="folderOption">
<svg data-type="folderAddChat" width="24" height="24" role="img"><title>${translations[locale]["newChat"]}</title><use xlink:href="#addIcon" /></svg>
<svg data-type="folderEdit" width="24" height="24" role="img"><title>${translations[locale]["edit"]}</title><use xlink:href="#chatEditIcon" /></svg>
<svg data-type="folderDel" width="24" height="24" role="img"><title>${translations[locale]["del"]}</title><use xlink:href="#delIcon" /></svg>
</div>`
headEle.children[1].children[0].textContent = folder.name;
headEle.children[1].children[1].textContent = folder.idxs.length + translations[locale]["chats"];
folderEle.appendChild(headEle);
folderEle.ondragstart = folderDragStartEv;
headEle.onclick = folderEleEvent;
let chatsEle = document.createElement("div");
chatsEle.className = "chatsInFolder";
for (let i = 0; i < folder.idxs.length; i++) {
chatsEle.appendChild(chatEleAdd(folder.idxs[i], false));
}
folderEle.appendChild(chatsEle);
if (push) {folderListEle.appendChild(folderEle)}
else {folderListEle.insertBefore(folderEle, folderListEle.firstChild)}
}
document.getElementById("newFolder").onclick = function () {
folderData.unshift({name: translations[locale]["newFolderName"], idxs: []});
folderEleAdd(0, false);
updateChatIdxs();
folderListEle.parentElement.scrollTop = 0;
};
const initChatEle = (index, chatEle) => {
chatEle.children[1].children[0].textContent = chatsData[index].name;
let chatPreview = "";
if (chatsData[index].data && chatsData[index].data.length) {
let first = chatsData[index].data.find(item => {return item.role === "assistant"});
if (first) {chatPreview = first.content.slice(0, 30)}
}
chatEle.children[1].children[1].textContent = chatPreview;
};
const chatEleAdd = (idx, appendChat = true) => {
let chat = chatsData[idx];
let chatEle = document.createElement("div");
chatEle.className = "chatLi";
if (!isMobile) chatEle.setAttribute("draggable", "true");
else chatEle.ontouchstart = mobileDragStartEV;
chatEle.ondragstart = chatDragStartEv;
chatEle.innerHTML = `<svg width="24" height="24"><use xlink:href="#chatIcon" /></svg>
<div class="chatInfo">
<div class="chatName"></div>
<div class="chatPre"></div>
</div>
<div class="chatOption"><svg data-type="chatEdit" width="24" height="24" role="img"><title>${translations[locale]["edit"]}</title><use xlink:href="#chatEditIcon" /></svg>
<svg data-type="chatDel" width="24" height="24" role="img"><title>${translations[locale]["del"]}</title><use xlink:href="#delIcon" /></svg></div>`
if (appendChat) chatListEle.appendChild(chatEle);
initChatEle(idx, chatEle);
chatEle.onclick = chatEleEvent;
return chatEle;
};
const addNewChat = () => {
let chat = {name: translations[locale]["newChatName"], data: []};
if (presetRoleData.default) chat.data.unshift({role: "system", content: presetRoleData.default});
preEle.selectedIndex = 0;
chatsData.push(chat);
chatIdxs.push(chatsData.length - 1);
updateChats();
};
const delChat = (idx, ele, folderIdx, inFolderIdx) => {
if (confirmAction(translations[locale]["delChatTip"])) {
if (idx === activeChatIdx) endAll();
if (folderIdx !== void 0) {
let folder = folderData[folderIdx];
folder.idxs.splice(inFolderIdx, 1);
updateFolder(folderIdx);
if (idx === activeChatIdx) {
if (inFolderIdx - 1 >= 0) {
activeChatIdx = folder.idxs[inFolderIdx - 1];
} else if (folder.idxs.length) {
activeChatIdx = folder.idxs[0];
} else {
extraFolderActive(folderIdx);
}
}
} else {
let chatIdx = chatIdxs.indexOf(idx);
chatIdxs.splice(chatIdx, 1);
if (idx === activeChatIdx) {
if (chatIdx - 1 >= 0) {
activeChatIdx = chatIdxs[chatIdx - 1];
} else if (chatIdxs.length) {
activeChatIdx = chatIdxs[0];
} else {
let folderNewIdx = -1;
for (let i = folderData.length - 1; i >= 0; i--) {
if (folderData[i].idxs.length) folderNewIdx = i;
}
if (folderNewIdx !== -1) {
activeChatIdx = folderData[folderNewIdx].idxs[0];
} else {
activeChatIdx = -1;
}
}
}
}
if (activeChatIdx > idx) activeChatIdx--;
chatsData.splice(idx, 1);
ele.remove();
folderData.forEach(item => {
if (item.idxs.length) {
item.idxs.forEach((i, ix) => {
if (i > idx) item.idxs[ix] = i - 1;
})
}
})
chatIdxs.forEach((item, ix) => {
if (item > idx) chatIdxs[ix] = item - 1;
})
if (activeChatIdx === -1) {
addNewChat();
activeChatIdx = 0;
chatEleAdd(activeChatIdx);
}
updateChats();
activeChat();
}
};
const endEditEvent = (ev) => {
if (!document.getElementById("activeChatEdit").contains(ev.target)) {
endEditChat();
}
};
const preventDrag = (ev) => {
ev.preventDefault();
ev.stopPropagation();
}
const endEditChat = () => {
if (operateChatIdx !== void 0) {
let ele = getChatEle(operateChatIdx);
chatsData[operateChatIdx].name = ele.children[1].children[0].textContent = document.getElementById("activeChatEdit").value;
ele.lastElementChild.remove();
} else if (operateFolderIdx !== void 0) {
let ele = folderListEle.children[operateFolderIdx].children[0];
folderData[operateFolderIdx].name = ele.children[1].children[0].textContent = document.getElementById("activeChatEdit").value;
ele.lastElementChild.remove();
}
updateChats();
operateChatIdx = operateFolderIdx = void 0;
document.body.removeEventListener("mousedown", endEditEvent, true);
}
const toEditName = (idx, ele, type) => {
let inputEle = document.createElement("input");
inputEle.id = "activeChatEdit";
inputEle.setAttribute("draggable", "true");
inputEle.ondragstart = preventDrag;
ele.appendChild(inputEle);
if (type) {
inputEle.value = chatsData[idx].name;
operateChatIdx = idx;
} else {
inputEle.value = folderData[idx].name;
operateFolderIdx = idx;
}
inputEle.setSelectionRange(0, 0);
inputEle.focus();
inputEle.onkeydown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
endEditChat();
}
};
document.body.addEventListener("mousedown", endEditEvent, true);
return inputEle;
};
const chatEleEvent = function (ev) {
ev.preventDefault();
ev.stopPropagation();
let idx, folderIdx, inFolderIdx;
if (chatListEle.contains(this)) {
idx = Array.prototype.indexOf.call(chatListEle.children, this);
idx = chatIdxs[idx];
} else if (folderListEle.contains(this)) {
folderIdx = Array.prototype.indexOf.call(folderListEle.children, this.parentElement.parentElement);
inFolderIdx = Array.prototype.indexOf.call(this.parentElement.children, this);
idx = folderData[folderIdx].idxs[inFolderIdx];
}
if (ev.target.classList.contains("chatLi")) {
if (searchChatEle.value || activeChatIdx !== idx) {
endAll();
activeChatIdx = idx;
activeChat(this);
}
if (window.innerWidth <= 800) {
document.body.classList.remove("show-nav");
}
} else if (ev.target.dataset.type === "chatEdit") {
toEditName(idx, this, 1);
} else if (ev.target.dataset.type === "chatDel") {
delChat(idx, this, folderIdx, inFolderIdx);
}
};
const updateChats = () => {
localStorage.setItem("chats", JSON.stringify(chatsData));
updateChatIdxs();
};
const updateChatIdxs = () => {
localStorage.setItem("chatIdxs", JSON.stringify(chatIdxs));
localStorage.setItem("folders", JSON.stringify(folderData));
}
const createConvEle = (className, append = true, model) => {
let div = document.createElement("div");
div.className = className;
formatMdEle(div, model);
if (append) chatlog.appendChild(div);
return div;
}
const getChatEle = (idx) => {
let chatIdx = chatIdxs.indexOf(idx);
if (chatIdx !== -1) {
return chatListEle.children[chatIdx];
} else {
let inFolderIdx;
let folderIdx = folderData.findIndex(item => {
inFolderIdx = item.idxs.indexOf(idx);
return inFolderIdx !== -1;
})
if (folderIdx !== -1) {
return folderListEle.children[folderIdx].children[1].children[inFolderIdx];
}
}
}
const activeChat = (ele) => {
data = chatsData[activeChatIdx]["data"];
allListEle.querySelectorAll(".activeChatLi").forEach(ele => {
ele.classList.remove("activeChatLi");
})
allListEle.querySelectorAll(".activeFolder").forEach(ele => {
ele.classList.remove("activeFolder")
})
if (!ele) ele = getChatEle(activeChatIdx);
ele.classList.add("activeChatLi");
activeChatEle = ele;
if (chatIdxs.indexOf(activeChatIdx) === -1) {
if (!ele.parentElement.parentElement.classList.contains("expandFolder")) {
ele.parentElement.parentElement.classList.add("activeFolder");
}
}
if (data[0] && data[0].role === "system") {
systemRole = data[0].content;
systemEle.value = systemRole;
} else {
systemRole = void 0;
systemEle.value = "";
}
chatlog.innerHTML = "";
if (systemRole ? data.length - 1 : data.length) {
let firstIdx = systemRole ? 1 : 0;
for (let i = firstIdx; i < data.length; i++) {
if (data[i].role === "user") {
createConvEle("request").children[1].textContent = data[i].content;
} else {
createConvEle("response", true, data[i].model).children[1].innerHTML = md.render(data[i].content) || "<br />";
}
}
}
let top = ele.offsetTop + ele.offsetHeight - allListEle.clientHeight;
if (allListEle.scrollTop < top) allListEle.scrollTop = top;
localStorage.setItem("activeChatIdx", activeChatIdx);
if (searchIdxs[activeChatIdx] !== void 0) {
let dataIdx = searchIdxs[activeChatIdx];
if (dataIdx !== -1) {
let childs = chatlog.children[systemRole ? dataIdx - 1 : dataIdx].children[1].getElementsByTagName("*");
for (let i = childs.length - 1; i >= 0; i--) {
if (childs[i].textContent && childs[i].textContent.indexOf(searchChatEle.value) !== -1) {
let offTop = findOffsetTop(childs[i], messagesEle);
messagesEle.scrollTop = offTop + childs[i].offsetHeight - messagesEle.clientHeight * 0.15;
break;
}
}
} else messagesEle.scrollTop = 0;
}
};
const findOffsetTop = (ele, target) => { // target is positioned ancestor element
if (ele.offsetParent !== target) return ele.offsetTop + findOffsetTop(ele.offsetParent, target)
else return ele.offsetTop
}
newChatEle.onclick = () => {
endAll();
addNewChat();
activeChatIdx = chatsData.length - 1;
chatEleAdd(activeChatIdx);
activeChat(chatListEle.lastElementChild);
};
const initChats = () => {
let localChats = localStorage.getItem("chats");
let localFolders = localStorage.getItem("folders");
let localChatIdxs = localStorage.getItem("chatIdxs")
let localChatIdx = localStorage.getItem("activeChatIdx");
activeChatIdx = (localChatIdx && parseInt(localChatIdx)) || 0;
if (localChats) {
chatsData = JSON.parse(localChats);
let folderIdxs = [];
if (localFolders) {
folderData = JSON.parse(localFolders);
for (let i = 0; i < folderData.length; i++) {
folderEleAdd(i);
folderIdxs.push(...folderData[i].idxs);
}
}
if (localChatIdxs) {
chatIdxs = JSON.parse(localChatIdxs);
for (let i = 0; i < chatIdxs.length; i++) {
chatEleAdd(chatIdxs[i]);
}
} else {
for (let i = 0; i < chatsData.length; i++) {
if (folderIdxs.indexOf(i) === -1) {
chatIdxs.push(i);
chatEleAdd(i);
}
}
updateChatIdxs();
}
} else {
addNewChat();
chatEleAdd(activeChatIdx);
}
};
const initExpanded = () => {
let folderIdx = folderData.findIndex(item => {
return item.idxs.indexOf(activeChatIdx) !== -1;
})
if (folderIdx !== -1) {
folderListEle.children[folderIdx].classList.add("expandFolder");
}
}
initChats();
initExpanded();
activeChat();
document.getElementById("clearSearch").onclick = () => {
searchChatEle.value = "";
searchChatEle.dispatchEvent(new Event("input"));
searchChatEle.focus();
}
let compositionFlag;
let lastCompositon;
searchChatEle.addEventListener("compositionstart", () => {
compositionFlag = true;
});
searchChatEle.addEventListener("compositionend", (ev) => {
compositionFlag = false;
if (ev.data.length && ev.data === lastCompositon) {
searchChatEle.dispatchEvent(new Event("input"));
}
lastCompositon = void 0;
});
searchChatEle.oninput = (ev) => {
if (ev.isComposing) lastCompositon = ev.data;
if (compositionFlag) return;
let value = searchChatEle.value;
if (value.length) {
searchIdxs.length = 0;
for (let i = 0; i < chatsData.length; i++) {
let chatEle = getChatEle(i);
chatEle.style.display = null;
let nameIdx = chatsData[i].name.indexOf(value);
let dataIdx = chatsData[i].data.findIndex(item => {
return item.role !== "system" && item.content.indexOf(value) !== -1
})
if (nameIdx !== -1 || dataIdx !== -1) {
let ele = chatEle.children[1];
if (dataIdx !== -1) {
let data = chatsData[i].data[dataIdx];
let idx = data.content.indexOf(value);
ele.children[1].textContent = (idx > 8 ? "..." : "") + data.content.slice(idx > 8 ? idx - 5 : 0, idx);
ele.children[1].appendChild(document.createElement("span"));
ele.children[1].lastChild.textContent = value;
ele.children[1].appendChild(document.createTextNode(data.content.slice(idx + value.length)))
} else {
initChatEle(i, chatEle);
}
if (nameIdx !== -1) {
ele.children[0].textContent = (nameIdx > 5 ? "..." : "") + chatsData[i].name.slice(nameIdx > 5 ? nameIdx - 3 : 0, nameIdx);
ele.children[0].appendChild(document.createElement("span"));
ele.children[0].lastChild.textContent = value;
ele.children[0].appendChild(document.createTextNode(chatsData[i].name.slice(nameIdx + value.length)))
} else {
ele.children[0].textContent = chatsData[i].name;
}
searchIdxs[i] = dataIdx;
} else {
chatEle.style.display = "none";
initChatEle(i, chatEle);
}
}
for (let i = 0; i < folderListEle.children.length; i++) {
let folderChatEle = folderListEle.children[i].children[1];
if (!folderChatEle.children.length || Array.prototype.filter.call(folderChatEle.children, (ele) => {
return ele.style.display !== "none"
}).length === 0) {
folderListEle.children[i].style.display = "none";
}
}
} else {
searchIdxs.length = 0;
for (let i = 0; i < chatsData.length; i++) {
let chatEle = getChatEle(i);
chatEle.style.display = null;
initChatEle(i, chatEle);
}
for (let i = 0; i < folderListEle.children.length; i++) {
folderListEle.children[i].style.display = null;
}
}
};
const blobToText = (blob) => {
return new Promise((res, rej) => {
let reader = new FileReader();
reader.readAsText(blob);
reader.onload = () => {
res(reader.result);
}
reader.onerror = (error) => {
rej(error);
}
})
};
document.getElementById("exportChat").onclick = () => {
if (loading) stopLoading();
let data = {
chatsData: chatsData,
folderData: folderData,
chatIdxs: chatIdxs
}
let blob = new Blob([JSON.stringify(data, null, 2)], {type: "application/json"});
let date = new Date();
let fileName = "chats-" + date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + ".json";
downBlob(blob, fileName);
notyf.success(translations[locale]["exportSuccTip"]);
};
document.getElementById("importChatInput").onchange = function () {
let file = this.files[0];
blobToText(file).then(text => {
try {
let json = JSON.parse(text);
let checked = json.chatsData && json.folderData && json.chatIdxs && json.chatsData.every(item => {
return item.name !== void 0 && item.data !== void 0;
});
if (checked) {
let preFolder = folderData.length;
let preLen = chatsData.length;
if (json.chatsData) {
chatsData = chatsData.concat(json.chatsData);
}
if (json.folderData) {
for (let i = 0; i < json.folderData.length; i++) {
json.folderData[i].idxs = json.folderData[i].idxs.map(item => {
return item + preLen;
})
folderData.push(json.folderData[i]);
folderEleAdd(i + preFolder);
}
}
if (json.chatIdxs) {
for (let i = 0; i < json.chatIdxs.length; i++) {
let newIdx = json.chatIdxs[i] + preLen;
chatIdxs.push(newIdx)
chatEleAdd(newIdx);
}
}
updateChats();
checkStorage();
notyf.success(translations[locale]["importSuccTip"]);
} else {
throw new Error("fmt error");
}
} catch (e) {
notyf.error(translations[locale]["importFailTip"]);
}
this.value = "";
})
};
clearChatSet.onclick = clearChat.onclick = () => {
if (confirmAction(translations[locale]["clearAllTip"])) {
chatsData.length = 0;
chatIdxs.length = 0;
folderData.length = 0;
folderListEle.innerHTML = "";
chatListEle.innerHTML = "";
endAll();
addNewChat();
activeChatIdx = 0;
chatEleAdd(activeChatIdx);
updateChats();
checkStorage();
activeChat(chatListEle.firstElementChild);
notyf.success(translations[locale]["clearChatSuccTip"]);
}
};
let localSetKeys = ['modelVersion', 'APISelect', 'APIHost', 'APIKey', 'hotKeys', 'userAvatar', 'system', 'temp', 'top_p', 'convWidth0', 'convWidth1', 'textSpeed', 'enableCont', 'enableLongReply', 'existVoice', 'voiceTestText', 'azureRegion', 'azureKey', 'enableContVoice', 'enableAutoVoice', 'voiceRecLang', 'autoVoiceSendWord', 'autoVoiceStopWord', 'autoVoiceSendOut', 'keepListenMic', 'fullWindow', 'themeMode', 'autoThemeMode', 'customDarkTime', 'UILang', 'pinNav', 'voice0', 'voicePitch0', 'voiceVolume0', 'voiceRate0', 'azureRole0', 'azureStyle0', 'voice1', 'voicePitch1', 'voiceVolume1', 'voiceRate1', 'azureRole1', 'azureStyle1'];
document.getElementById("exportSet").onclick = () => {
let data = {}
for (let i = 0; i < localSetKeys.length; i++) {
let key = localSetKeys[i];
let val = localStorage.getItem(key);
if (val != void 0) data[key] = val;
}
let blob = new Blob([JSON.stringify(data, null, 2)], {type: "application/json"});
let date = new Date();
let fileName = "settings-" + date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + ".json";
downBlob(blob, fileName);
notyf.success(translations[locale]["exportSuccTip"]);
};
document.getElementById("importSetInput").onchange = function () {
let file = this.files[0];
blobToText(file).then(text => {
try {
let json = JSON.parse(text);
let keys = Object.keys(json);
for (let i = 0; i < localSetKeys.length; i++) {
let key = localSetKeys[i];
let val = json[key];
if (val !== void 0) localStorage.setItem(key, val);
else localStorage.removeItem(key);
}
initSetting();
initVoiceVal();
speechServiceEle.dispatchEvent(new Event("change"));
initRecSetting();
initHotKey();
initLang();
checkStorage();
notyf.success(translations[locale]["importSuccTip"]);
} catch (e) {
notyf.error(translations[locale]["importFailTip"]);
}
this.value = "";
})
};
document.getElementById("resetSet").onclick = () => {
if (confirmAction(translations[locale]["resetSetTip"])) {
endAll();
if (existVoice === 3) localStorage.removeItem(azureRegion + "voiceData");
let data = {};
for (let i = 0; i < localSetKeys.length; i++) {
let key = localSetKeys[i];
let val = localStorage.removeItem(key);
}
initSetting();
initVoiceVal();
speechServiceEle.dispatchEvent(new Event("change"));
initRecSetting();
initHotKey();
initLang();
checkStorage();
notyf.success(translations[locale]["resetSetSuccTip"]);
}
}
const endAll = () => {
endSpeak();
if (editingIdx !== void 0) resumeSend();
if (loading) stopLoading();
};
const processIdx = (plus) => {
if (currentVoiceIdx !== void 0) currentVoiceIdx += plus;
if (editingIdx !== void 0) editingIdx += plus;
}
const hotKeyVals = {};
const ctrlHotKeyEv = (ev) => {
if (ev.ctrlKey || ev.metaKey) {
switch (ev.key.toLowerCase()) {
case hotKeyVals["Nav"]:
ev.preventDefault();
toggleNavEv();
return false;
case hotKeyVals["Search"]:
ev.preventDefault();
searchChatEle.focus();
return false;
case hotKeyVals["Input"]:
ev.preventDefault();
inputAreaEle.focus();
return false;
case hotKeyVals["NewChat"]:
ev.preventDefault();
newChatEle.dispatchEvent(new MouseEvent("click"));
return false;
case hotKeyVals["ClearChat"]:
ev.preventDefault();
clearEle.dispatchEvent(new MouseEvent("click"));
return false;
case hotKeyVals["VoiceRec"]:
if (supportRec) {
ev.preventDefault();
toggleRecEv();
}
return false;
case hotKeyVals["VoiceSpeak"]:
ev.preventDefault();
speechEvent(systemRole ? 1 : 0);
return false;
}
}
}
const ctrlAltHotKeyEv = (ev) => {
if ((ev.ctrlKey || ev.metaKey) && ev.altKey) {
switch (ev.key.toLowerCase()) {
case hotKeyVals["Window"]:
ev.preventDefault();
toggleFull.dispatchEvent(new Event("click"));
return false;
case hotKeyVals["Theme"]:
ev.preventDefault();
lightEle.dispatchEvent(new Event("click"));
return false;
case hotKeyVals["Lang"]:
ev.preventDefault();
let idx = localeList.indexOf(locale) + 1;
if (idx === localeList.length) idx = 0;
locale = localeList[idx];
setLang();
changeLocale();
return false;
}
}
}
const listKey = ['Nav', 'Search', 'Input', 'NewChat', 'ClearChat', 'VoiceRec', 'VoiceSpeak', 'Window', 'Theme', 'Lang'];
const ctrlKeyIdx = 7;
const defKeyVal = ['b', 'k', 'i', 'e', 'r', 'q', 's', 'u', 't', 'l'];
const initHotKey = () => {
let localKeysObj = {};
let localKeys = localStorage.getItem("hotKeys");
if (localKeys) {
try {
localKeysObj = JSON.parse(localKeys);
} catch (e) { }
}
let pre1 = isApple ? "⌘ + " : "Ctrl + ";
let pre2 = isApple ? "⌘ + ⌥ + " : "Ctrl + Alt + ";
for (let i = 0; i < listKey.length; i++) {
let key = listKey[i];
if (key === "VoiceRec" && !supportRec) continue;
let ele = window["hotKey" + key];
for (let j = 0; j < 26; j++) {
// top-level hotkey, can't overwrite
if (i < ctrlKeyIdx && (j === 13 || j === 19 || j === 22)) continue;
let val = String.fromCharCode(j + 97);
ele.options.add(new Option((i < ctrlKeyIdx ? pre1 : pre2) + val.toUpperCase(), val));
}
hotKeyVals[key] = ele.value = localKeysObj[key] || defKeyVal[i];
ele.onchange = () => {
if (hotKeyVals[key] === ele.value) return;
let exist = listKey.find((item, idx) => {
return (i < ctrlKeyIdx ? idx < ctrlKeyIdx : idx >= ctrlKeyIdx) && hotKeyVals[item] === ele.value;
})
if (exist) {
ele.value = hotKeyVals[key];
notyf.error(translations[locale]["hotkeyConflict"])
return;
}
hotKeyVals[key] = ele.value;
localStorage.setItem("hotKeys", JSON.stringify(hotKeyVals));
}
}
};
initHotKey();
document.addEventListener("keydown", ctrlHotKeyEv);
document.addEventListener("keydown", ctrlAltHotKeyEv);
const initSetting = () => {
const modelEle = document.getElementById("preSetModel");
let localModel = localStorage.getItem("modelVersion");
modelVersion = modelEle.value = localModel || "gpt-3.5-turbo";
modelEle.onchange = () => {
modelVersion = modelEle.value;
localStorage.setItem("modelVersion", modelVersion);
}
modelEle.dispatchEvent(new Event("change"));
const apiHostEle = document.getElementById("apiHostInput");
const apiSelectEle = document.getElementById("apiSelect");
let localApiSelect = localStorage.getItem("APISelect");
if (localApiSelect) {
try {
apiSelects = JSON.parse(localApiSelect);
} catch (e) {
apiSelects.length = 0;
}
} else {
apiSelects.length = 0;
}
const delApiOption = function (ev) {
ev.stopPropagation();
let index = Array.prototype.indexOf.call(apiSelectEle.children, this.parentElement);
apiSelects.splice(index, 1);
this.parentElement.remove();
localStorage.setItem("APISelect", JSON.stringify(apiSelects));
if (!apiSelects.length) apiSelectEle.style.display = "none";
}
const appendApiOption = () => {
apiSelects.push(apiHost);
initApiOption(apiHost);
localStorage.setItem("APISelect", JSON.stringify(apiSelects));
}
const selApiOption = function (ev) {
ev.preventDefault();
ev.stopPropagation();
apiSelectEle.style.display = "none";
let index = Array.prototype.indexOf.call(apiSelectEle.children, this);
apiHostEle.value = apiSelects[index];
apiHostEle.dispatchEvent(new Event("change"));
}
const initApiOption = (api) => {
let optionEle = document.createElement("div");
optionEle.onclick = selApiOption;
let textEle = document.createElement("span");
textEle.textContent = api;
optionEle.appendChild(textEle);
let delEle = document.createElement("div");
delEle.className = "delApiOption";
delEle.onclick = delApiOption;
delEle.innerHTML = `<svg width="24" height="24"><use xlink:href="#closeIcon" /></svg>`;
optionEle.appendChild(delEle);
apiSelectEle.appendChild(optionEle);
}
const initApiSelectEle = () => {
apiSelectEle.innerHTML = "";
for (let i = 0; i < apiSelects.length; i++) {
initApiOption(apiSelects[i]);
}
}
initApiSelectEle();
apiHostEle.onfocus = () => {
if (apiSelects.length) apiSelectEle.style.display = "block";
}
apiHostEle.onblur = (ev) => {
if (!(ev.relatedTarget && apiSelectEle.contains(ev.relatedTarget))) apiSelectEle.style.display = "none";
}
let localApiHost = localStorage.getItem("APIHost");
apiHost = apiHostEle.value = localApiHost || apiHostEle.getAttribute("value") || "";
apiHostEle.onchange = () => {
apiHost = apiHostEle.value;
if (apiHost.length && !apiHost.endsWith("/")) {
apiHost += "/";
apiHostEle.value = apiHost;
}
if (apiHost && apiSelects.indexOf(apiHost) === -1) appendApiOption();
localStorage.setItem("APIHost", apiHost);
}
apiHostEle.dispatchEvent(new Event("change"));
const keyEle = document.getElementById("keyInput");
let localKey = localStorage.getItem("APIKey");
customAPIKey = keyEle.value = localKey || keyEle.getAttribute("value") || "";
keyEle.onchange = () => {
quotaContent.style.display = "none";
customAPIKey = keyEle.value;
localStorage.setItem("APIKey", customAPIKey);
}
keyEle.dispatchEvent(new Event("change"));
const updateAvatar = () => {
setAvatarPre.src = userAvatar;
chatlog.querySelectorAll(".request>.chatAvatar").forEach(ele => {
ele.children[0].src = userAvatar;
})
}
let localAvatar = localStorage.getItem("userAvatar");
userAvatar = setAvatarPre.src = setAvatar.value = localAvatar || setAvatar.getAttribute("value") || "https://cdn.jsdelivr.net/gh/xqdoo00o/chatgpt-web@master/avatar.jpg";
setAvatar.onchange = () => {
userAvatar = setAvatar.value;
localStorage.setItem("userAvatar", userAvatar);
updateAvatar();
}
setAvatar.dispatchEvent(new Event("change"));
let localSystem = localStorage.getItem("system");
systemEle.onchange = () => {
systemRole = systemEle.value;
localStorage.setItem("system", systemRole);
if (systemRole) {
if (data[0] && data[0].role === "system") {
data[0].content = systemRole;
} else {
data.unshift({role: "system", content: systemRole});
processIdx(1);
}
} else if (data[0] && data[0].role === "system") {
data.shift();
processIdx(-1);
}
updateChats();
}
if (systemRole === void 0) {
systemRole = systemEle.value = localSystem || presetRoleData.default || "";
if (systemRole) {
data.unshift({role: "system", content: systemRole});
processIdx(1);
updateChats();
}
}
preEle.onchange = () => {
let val = preEle.value;
if (val && presetRoleData[val]) {
systemEle.value = presetRoleData[val];
} else {
systemEle.value = "";
}
systemEle.dispatchEvent(new Event("change"));
systemEle.focus();
}
const topEle = document.getElementById("top_p");
let localTop = localStorage.getItem("top_p");
topEle.value = roleNature = parseFloat(localTop || topEle.getAttribute("value"));
topEle.oninput = () => {
topEle.style.backgroundSize = (topEle.value - topEle.min) * 100 / (topEle.max - topEle.min) + "% 100%";
roleNature = parseFloat(topEle.value);
localStorage.setItem("top_p", topEle.value);
}
topEle.dispatchEvent(new Event("input"));
const tempEle = document.getElementById("temp");
let localTemp = localStorage.getItem("temp");
tempEle.value = roleTemp = parseFloat(localTemp || tempEle.getAttribute("value"));
tempEle.oninput = () => {
tempEle.style.backgroundSize = (tempEle.value - tempEle.min) * 100 / (tempEle.max - tempEle.min) + "% 100%";
roleTemp = parseFloat(tempEle.value);
localStorage.setItem("temp", tempEle.value);
}
tempEle.dispatchEvent(new Event("input"));
const convWEle = document.getElementById("convWidth");
const styleSheet = document.styleSheets[0];
convWEle.oninput = () => {
let type = isFull ? 1 : 0;
convWEle.style.backgroundSize = (convWEle.value - convWEle.min) * 100 / (convWEle.max - convWEle.min) + "% 100%";
convWidth[type] = parseInt(convWEle.value);
localStorage.setItem("convWidth" + type, convWEle.value);
styleSheet.deleteRule(0);
styleSheet.deleteRule(0);
styleSheet.insertRule(`.bottom_wrapper{max-width:${convWidth[type]}%;}`, 0);
styleSheet.insertRule(`.requestBody,.response .markdown-body{max-width:calc(${convWidth[type]}% - 88px);}`, 0);
}
const setConvValue = () => {
let type = isFull ? 1 : 0;
let localConv = localStorage.getItem("convWidth" + type);
convWEle.value = parseInt(localConv || (type ? "60" : "100"));
convWEle.dispatchEvent(new Event("input"));
}
const fullFunc = () => {
isFull = windowEle.classList.contains("full_window");
localStorage.setItem("fullWindow", isFull);
setConvValue();
toggleFull.title = isFull ? translations[locale]["winedWin"] : translations[locale]["fullWin"];
toggleFull.children[0].children[0].setAttributeNS("http://www.w3.org/1999/xlink", "href", isFull ? "#collapseFullIcon" : "#expandFullIcon");
}
toggleFull.onclick = () => {
windowEle.classList.toggle("full_window");
fullFunc();
}
let localFull = localStorage.getItem("fullWindow");
if (localFull && localFull === "true") {
if (!windowEle.classList.contains("full_window")) {
windowEle.classList.add("full_window");
fullFunc();
}
} else if (windowEle.classList.contains("full_window")) {
windowEle.classList.remove("full_window");
fullFunc();
} else {
setConvValue();
}
const speedEle = document.getElementById("textSpeed");
let localSpeed = localStorage.getItem("textSpeed");
speedEle.value = localSpeed || speedEle.getAttribute("value");
textSpeed = parseFloat(speedEle.min) + (speedEle.max - speedEle.value);
speedEle.oninput = () => {
speedEle.style.backgroundSize = (speedEle.value - speedEle.min) * 100 / (speedEle.max - speedEle.min) + "% 100%";
textSpeed = parseFloat(speedEle.min) + (speedEle.max - speedEle.value);
localStorage.setItem("textSpeed", speedEle.value);
}
speedEle.dispatchEvent(new Event("input"));
const contEle = document.getElementById("enableCont");
let localCont = localStorage.getItem("enableCont");
contEle.checked = enableCont = (localCont || contEle.getAttribute("checked")) === "true";
contEle.onchange = () => {
enableCont = contEle.checked;
localStorage.setItem("enableCont", enableCont);
}
contEle.dispatchEvent(new Event("change"));
const longEle = document.getElementById("enableLongReply");
let localLong = localStorage.getItem("enableLongReply");
longEle.checked = enableLongReply = (localLong || longEle.getAttribute("checked")) === "true";
longEle.onchange = () => {
enableLongReply = longEle.checked;
localStorage.setItem("enableLongReply", enableLongReply);
}
longEle.dispatchEvent(new Event("change"));
let localPin = localStorage.getItem("pinNav");
if (window.innerWidth > 800 && !(localPin && localPin === "false")) {
document.body.classList.add("show-nav");
};
const setDarkTheme = (is) => {
let cssEle = document.body.getElementsByTagName("link")[0];
cssEle.href = cssEle.href.replace(is ? "light" : "dark", is ? "dark" : "light");
justDarkTheme(is);
}
const handleAutoMode = (ele) => {
if (ele.checked) {
autoThemeMode = parseInt(ele.value);
localStorage.setItem("autoThemeMode", autoThemeMode);
initAutoTime();
if (autoThemeMode) {
if (customDarkOut !== void 0) {
clearTimeout(customDarkOut);
customDarkOut = void 0;
}
setDarkTheme(darkMedia.matches);
} else {
checkCustomTheme();
}
}
}
autoTheme0.onchange = autoTheme1.onchange = function () {handleAutoMode(this)};
const handleAutoTime = (ele, idx) => {
let otherIdx = 1 - idx;
if (ele.value !== customDarkTime[otherIdx]) {
customDarkTime[idx] = ele.value;
localStorage.setItem("customDarkTime", JSON.stringify(customDarkTime));
checkCustomTheme();
} else {
ele.value = customDarkTime[idx];
notyf.error(translations[locale]["customDarkTip"]);
}
}
customStart.onchange = function () {handleAutoTime(this, 0)};
customEnd.onchange = function () {handleAutoTime(this, 1)};
const initAutoTime = () => {
customAutoSet.style.display = autoThemeMode === 0 ? "block" : "none";
if (autoThemeMode === 0) {
customStart.value = customDarkTime[0];
customEnd.value = customDarkTime[1];
}
}
const initAutoThemeEle = () => {
autoThemeEle.querySelector("#autoTheme" + autoThemeMode).checked = true;
initAutoTime();
}
const checkCustomTheme = () => {
if (customDarkOut !== void 0) clearTimeout(customDarkOut);
let date = new Date();
let nowTime = date.getTime();
let start = customDarkTime[0].split(":");
let startTime = new Date().setHours(start[0], start[1], 0, 0);
let end = customDarkTime[1].split(":");
let endTime = new Date().setHours(end[0], end[1], 0, 0);
let order = endTime > startTime;
let isDark = order ? (nowTime > startTime && endTime > nowTime) : !(nowTime > endTime && startTime > nowTime);
let nextChange = isDark ? endTime - nowTime : startTime - nowTime;
if (nextChange < 0) nextChange += dayMs;
setDarkTheme(isDark);
customDarkOut = setTimeout(() => {
checkCustomTheme();
}, nextChange);
}
const setDarkMode = () => {
if (customDarkOut !== void 0) {
clearTimeout(customDarkOut);
customDarkOut = void 0;
}
autoThemeEle.style.display = "none";
let themeClass, title;
if (themeMode === 2) {
autoThemeEle.style.display = "block";
if (autoThemeMode) {
setDarkTheme(darkMedia.matches);
} else {
checkCustomTheme();
initAutoThemeEle();
}
themeClass = "autoTheme";
title = translations[locale]["autoTheme"];
} else if (themeMode === 1) {
setDarkTheme(false);
themeClass = "lightTheme";
title = translations[locale]["lightTheme"];
} else {
setDarkTheme(true);
themeClass = "darkTheme";
title = translations[locale]["darkTheme"];
}
localStorage.setItem("themeMode", themeMode);
setLightEle.className = "setDetail themeDetail " + themeClass;
lightEle.children[0].children[0].setAttributeNS("http://www.w3.org/1999/xlink", "href", "#" + themeClass + "Icon");
lightEle.title = title;
}
lightEle.onclick = () => {
themeMode = themeMode - 1;
if (themeMode === -1) themeMode = 2;
setDarkMode();
}
setLightEle.onclick = (ev) => {
let idx = Array.prototype.indexOf.call(setLightEle.children, ev.target);
if (themeMode !== idx) {
themeMode = idx;
setDarkMode();
}
}
let localTheme = localStorage.getItem("themeMode");
themeMode = parseInt(localTheme || "1");
let localAutoTheme = localStorage.getItem("autoThemeMode");
autoThemeMode = parseInt(localAutoTheme || "1");
let localCustomDark = localStorage.getItem("customDarkTime");
customDarkTime = JSON.parse(localCustomDark || '["21:00", "07:00"]');
setDarkMode();
darkMedia.onchange = e => {
if (themeMode === 2 && autoThemeMode) setDarkTheme(e.matches);
};
};
initSetting();
document.getElementById("loadMask").style.display = "none";
const closeEvent = (ev) => {
if (settingEle.contains(ev.target)) return;
if (!dialogEle.contains(ev.target)) {
dialogEle.style.display = "none";
document.removeEventListener("mousedown", closeEvent, true);
settingEle.classList.remove("showSetting");
stopTestVoice();
}
}
settingEle.onmousedown = () => {
dialogEle.style.display = dialogEle.style.display === "block" ? "none" : "block";
if (dialogEle.style.display === "block") {
document.addEventListener("mousedown", closeEvent, true);
settingEle.classList.add("showSetting");
} else {
document.removeEventListener("mousedown", closeEvent, true);
settingEle.classList.remove("showSetting");
}
}
let delayId;
const delay = () => {
return new Promise((resolve) => delayId = setTimeout(resolve, textSpeed)); //打字机时间间隔
}
const uuidv4 = () => {
let uuid = ([1e7] + 1e3 + 4e3 + 8e3 + 1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
return existVoice === 3 ? uuid.toUpperCase() : uuid;
}
const getTime = () => {
return existVoice === 3 ? new Date().toISOString() : new Date().toString();
}
const getWSPre = (date, requestId) => {
let osPlatform = (typeof window !== "undefined") ? "Browser" : "Node";
osPlatform += "/" + navigator.platform;
let osName = navigator.userAgent;
let osVersion = navigator.appVersion;
return `Path: speech.configrnX-RequestId: ${requestId}rnX-Timestamp: ${date}rnContent-Type: application/jsonrnrn{"context":{"system":{"name":"SpeechSDK","version":"1.26.0","build":"JavaScript","lang":"JavaScript","os":{"platform":"${osPlatform}","name":"${osName}","version":"${osVersion}"}}}}`
}
const getWSAudio = (date, requestId) => {
return existVoice === 3 ? `Path: synthesis.contextrnX-RequestId: ${requestId}rnX-Timestamp: ${date}rnContent-Type: application/jsonrnrn{"synthesis":{"audio":{"metadataOptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":false},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}`
: `X-Timestamp:${date}rnContent-Type:application/json; charset=utf-8rnPath:speech.configrnrn{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":"false","wordBoundaryEnabled":"true"},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`
}
const getWSText = (date, requestId, lang, voice, volume, rate, pitch, style, role, msg) => {
let fmtVolume = volume === 1 ? "+0%" : volume * 100 - 100 + "%";
let fmtRate = (rate >= 1 ? "+" : "") + (rate * 100 - 100) + "%";
let fmtPitch = (pitch >= 1 ? "+" : "") + (pitch - 1) + "Hz";
msg = getEscape(msg);
if (existVoice === 3) {
let fmtStyle = style ? ` style="${style}"` : "";
let fmtRole = role ? ` role="${role}"` : "";
let fmtExpress = fmtStyle + fmtRole;
return `Path: ssmlrnX-RequestId: ${requestId}rnX-Timestamp: ${date}rnContent-Type: application/ssml+xmlrnrn<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='https://www.w3.org/2001/mstts' xml:lang='${lang}'><voice name='${voice}'><mstts:express-as${fmtExpress}><prosody pitch='${fmtPitch}' rate='${fmtRate}' volume='${fmtVolume}'>${msg}</prosody></mstts:express-as></voice></speak>`;
} else {
return `X-RequestId:${requestId}rnContent-Type:application/ssml+xmlrnX-Timestamp:${date}ZrnPath:ssmlrnrn<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='https://www.w3.org/2001/mstts' xml:lang='${lang}'><voice name='${voice}'><prosody pitch='${fmtPitch}' rate='${fmtRate}' volume='${fmtVolume}'>${msg}</prosody></voice></speak>`;
}
}
const getAzureWSURL = () => {
return `wss://${azureRegion}.tts.speech.microsoft.com/cognitiveservices/websocket/v1?Authorization=bearer%20${azureToken}`
}
const edgeTTSURL = "wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4";
const resetSpeakIcon = () => {
if (currentVoiceIdx !== void 0) {
chatlog.children[systemRole ? currentVoiceIdx - 1 : currentVoiceIdx].classList.remove("showVoiceCls");
chatlog.children[systemRole ? currentVoiceIdx - 1 : currentVoiceIdx].lastChild.lastChild.className = "voiceCls readyVoice";
}
}
const endSpeak = () => {
resetSpeakIcon();
currentVoiceIdx = void 0;
if (voiceIns && voiceIns instanceof Audio) {
voiceIns.pause();
voiceIns.currentTime = 0;
URL.revokeObjectURL(voiceIns.src);
voiceIns.removeAttribute("src");
voiceIns.onended = voiceIns.onerror = null;
sourceBuffer = void 0;
speechPushing = false;
if (voiceSocket && voiceSocket["pending"]) {
voiceSocket.close()
}
if (autoVoiceSocket && autoVoiceSocket["pending"]) {
autoVoiceSocket.close()
}
speechQuene.length = 0;
autoMediaSource = void 0;
voiceContentQuene = [];
voiceEndFlagQuene = [];
voiceBlobURLQuene = [];
autoOnlineVoiceFlag = false;
} else if (supportSpe) {
speechSynthesis.cancel();
}
}
const speakEvent = (ins, force = true, end = false) => {
return new Promise((res, rej) => {
ins.onerror = () => {
if (end) {
endSpeak();
} else if (force) {
resetSpeakIcon();
}
res();
}
if (ins instanceof Audio) {
ins.onended = ins.onerror;
ins.play();
} else {
ins.onend = ins.onerror;
speechSynthesis.speak(voiceIns);
}
})
};
let voiceData = [];
let voiceSocket;
let speechPushing = false;
let speechQuene = [];
let sourceBuffer;
speechQuene.push = function (buffer) {
if (!speechPushing && (sourceBuffer && !sourceBuffer.updating)) {
speechPushing = true;
sourceBuffer.appendBuffer(buffer);
} else {
Array.prototype.push.call(this, buffer)
}
}
const initSocket = () => {
return new Promise((res, rej) => {
if (!voiceSocket || voiceSocket.readyState > 1) {
voiceSocket = new WebSocket(existVoice === 3 ? getAzureWSURL() : edgeTTSURL);
voiceSocket.binaryType = "arraybuffer";
voiceSocket.onopen = () => {
res();
};
voiceSocket.onerror = () => {
rej();
}
} else {
return res();
}
})
}
const initStreamVoice = (mediaSource) => {
return new Promise((r, j) => {
Promise.all([initSocket(), new Promise(res => {
mediaSource.onsourceopen = () => {
res();
};
})]).then(() => {
r();
})
})
}
let downQuene = {};
let downSocket;
const downBlob = (blob, name) => {
let a = document.createElement("a");
a.download = name;
a.href = URL.createObjectURL(blob);
a.click();
}
const initDownSocket = () => {
return new Promise((res, rej) => {
if (!downSocket || downSocket.readyState > 1) {
downSocket = new WebSocket(existVoice === 3 ? getAzureWSURL() : edgeTTSURL);
downSocket.binaryType = "arraybuffer";
downSocket.onopen = () => {
res();
};
downSocket.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
let text = new TextDecoder().decode(e.data.slice(0, 130));
let reqIdx = text.indexOf(":");
let uuid = text.slice(reqIdx + 1, reqIdx + 33);
downQuene[uuid]["blob"].push(e.data.slice(130));
} else if (e.data.indexOf("Path:turn.end") !== -1) {
let reqIdx = e.data.indexOf(":");
let uuid = e.data.slice(reqIdx + 1, reqIdx + 33);
let blob = new Blob(downQuene[uuid]["blob"], {type: voiceMIME});
let key = downQuene[uuid]["key"];
let name = downQuene[uuid]["name"];
if (blob.size === 0) {
notyf.open({
type: "warning",
message: translations[locale]["cantSpeechTip"]
});
return;
}
voiceData[key] = blob;
if (downQuene[uuid]["isTest"]) {
testVoiceBlob = blob;
playTestAudio();
} else {
downBlob(blob, name.slice(0, 16) + ".mp3");
}
}
}
downSocket.onerror = () => {
rej();
}
} else {
return res();
}
})
}
let testVoiceBlob;
let testVoiceIns;
const playTestAudio = () => {
if (existVoice >= 2) {
if (!testVoiceIns || testVoiceIns instanceof Audio === false) {
testVoiceIns = new Audio();
testVoiceIns.onended = testVoiceIns.onerror = () => {
stopTestVoice();
}
}
testVoiceIns.src = URL.createObjectURL(testVoiceBlob);
testVoiceIns.play();
} else if (supportSpe) {
speechSynthesis.speak(testVoiceIns);
}
}
const pauseTestVoice = () => {
if (testVoiceIns) {
if (testVoiceIns && testVoiceIns instanceof Audio) {
testVoiceIns.pause();
} else if (supportSpe) {
speechSynthesis.pause();
}
}
testVoiceBtn.className = "justSetLine resumeTestVoice";
}
const resumeTestVoice = () => {
if (testVoiceIns) {
if (testVoiceIns && testVoiceIns instanceof Audio) {
testVoiceIns.play();
} else if (supportSpe) {
speechSynthesis.resume();
}
}
testVoiceBtn.className = "justSetLine pauseTestVoice";
}
const stopTestVoice = () => {
if (testVoiceIns) {
if (testVoiceIns instanceof Audio) {
testVoiceIns.pause();
testVoiceIns.currentTime = 0;
URL.revokeObjectURL(testVoiceIns.src);
testVoiceIns.removeAttribute("src");
} else if (supportSpe) {
speechSynthesis.cancel();
}
}
testVoiceBtn.className = "justSetLine readyTestVoice";
}
const startTestVoice = async () => {
testVoiceBtn.className = "justSetLine pauseTestVoice";
let volume = voiceVolume[voiceType];
let rate = voiceRate[voiceType];
let pitch = voicePitch[voiceType];
let content = voiceTestText;
if (existVoice >= 2) {
let voice = existVoice === 3 ? voiceRole[voiceType].ShortName : voiceRole[voiceType].Name;
let style = azureStyle[voiceType];
let role = azureRole[voiceType];
let key = content + voice + volume + rate + pitch + (style ? style : "") + (role ? role : "");
let blob = voiceData[key];
if (blob) {
testVoiceBlob = blob;
playTestAudio();
} else {
await initDownSocket();
let currDate = getTime();
let lang = voiceRole[voiceType].lang;
let uuid = uuidv4();
if (existVoice === 3) {
downSocket.send(getWSPre(currDate, uuid));
}
downSocket.send(getWSAudio(currDate, uuid));
downSocket.send(getWSText(currDate, uuid, lang, voice, volume, rate, pitch, style, role, content));
downSocket["pending"] = true;
downQuene[uuid] = {};
downQuene[uuid]["name"] = content;
downQuene[uuid]["key"] = key;
downQuene[uuid]["isTest"] = true;
downQuene[uuid]["blob"] = [];
}
} else {
testVoiceIns = new SpeechSynthesisUtterance();
testVoiceIns.onend = testVoiceIns.onerror = () => {
stopTestVoice();
}
testVoiceIns.voice = voiceRole[voiceType];
testVoiceIns.volume = volume;
testVoiceIns.rate = rate;
testVoiceIns.pitch = pitch;
testVoiceIns.text = content;
playTestAudio();
}
}
const downloadAudio = async (idx) => {
if (existVoice < 2) {
return;
}
let type = data[idx].role === "user" ? 0 : 1;
let voice = existVoice === 3 ? voiceRole[type].ShortName : voiceRole[type].Name;
let volume = voiceVolume[type];
let rate = voiceRate[type];
let pitch = voicePitch[type];
let style = azureStyle[type];
let role = azureRole[type];
let content = data[idx].content;
let key = content + voice + volume + rate + pitch + (style ? style : "") + (role ? role : "");
let blob = voiceData[key];
if (blob) {
downBlob(blob, content.slice(0, 16) + ".mp3");
} else {
await initDownSocket();
let currDate = getTime();
let lang = voiceRole[type].lang;
let uuid = uuidv4();
if (existVoice === 3) {
downSocket.send(getWSPre(currDate, uuid));
}
downSocket.send(getWSAudio(currDate, uuid));
downSocket.send(getWSText(currDate, uuid, lang, voice, volume, rate, pitch, style, role, content));
downSocket["pending"] = true;
downQuene[uuid] = {};
downQuene[uuid]["name"] = content;
downQuene[uuid]["key"] = key;
downQuene[uuid]["blob"] = [];
}
}
const NoMSEPending = (key) => {
return new Promise((res, rej) => {
let bufArray = [];
voiceSocket.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
bufArray.push(e.data.slice(130));
} else if (e.data.indexOf("Path:turn.end") !== -1) {
voiceSocket["pending"] = false;
if (!(bufArray.length === 1 && bufArray[0].byteLength === 0)) {
voiceData[key] = new Blob(bufArray, {type: voiceMIME});
res(voiceData[key]);
} else {
res(new Blob());
}
}
}
})
}
const pauseEv = () => {
if (voiceIns.src) {
let ele = chatlog.children[systemRole ? currentVoiceIdx - 1 : currentVoiceIdx].lastChild.lastChild;
ele.classList.remove("readyVoice");
ele.classList.remove("pauseVoice");
ele.classList.add("resumeVoice");
}
}
const resumeEv = () => {
if (voiceIns.src) {
let ele = chatlog.children[systemRole ? currentVoiceIdx - 1 : currentVoiceIdx].lastChild.lastChild;
ele.classList.remove("readyVoice");
ele.classList.remove("resumeVoice");
ele.classList.add("pauseVoice");
}
}
const speechEvent = async (idx) => {
if (!data[idx]) return;
endSpeak();
currentVoiceIdx = idx;
if (!data[idx].content && enableContVoice) {
if (currentVoiceIdx !== data.length - 1) {return speechEvent(currentVoiceIdx + 1)}
else {return endSpeak()}
};
let type = data[idx].role === "user" ? 0 : 1;
chatlog.children[systemRole ? idx - 1 : idx].classList.add("showVoiceCls");
let voiceIconEle = chatlog.children[systemRole ? idx - 1 : idx].lastChild.lastChild;
voiceIconEle.className = "voiceCls pauseVoice";
let content = data[idx].content;
let volume = voiceVolume[type];
let rate = voiceRate[type];
let pitch = voicePitch[type];
let style = azureStyle[type];
let role = azureRole[type];
if (existVoice >= 2) {
if (!voiceIns || voiceIns instanceof Audio === false) {
voiceIns = new Audio();
voiceIns.onpause = pauseEv;
voiceIns.onplay = resumeEv;
}
let voice = existVoice === 3 ? voiceRole[type].ShortName : voiceRole[type].Name;
let key = content + voice + volume + rate + pitch + (style ? style : "") + (role ? role : "");
let currData = voiceData[key];
if (currData) {
voiceIns.src = URL.createObjectURL(currData);
} else {
let mediaSource;
if (supportMSE) {
mediaSource = new MediaSource;
voiceIns.src = URL.createObjectURL(mediaSource);
await initStreamVoice(mediaSource);
if (!sourceBuffer) {
sourceBuffer = mediaSource.addSourceBuffer(voiceMIME);
}
sourceBuffer.onupdateend = function () {
speechPushing = false;
if (speechQuene.length) {
let buf = speechQuene.shift();
if (buf["end"]) {
if (!sourceBuffer.buffered.length) notyf.open({type: "warning", message: translations[locale]["cantSpeechTip"]});
mediaSource.endOfStream();
} else {
speechPushing = true;
sourceBuffer.appendBuffer(buf);
}
}
};
let bufArray = [];
voiceSocket.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
let buf = e.data.slice(130);
bufArray.push(buf);
speechQuene.push(buf);
} else if (e.data.indexOf("Path:turn.end") !== -1) {
voiceSocket["pending"] = false;
if (!(bufArray.length === 1 && bufArray[0].byteLength === 0)) voiceData[key] = new Blob(bufArray, {type: voiceMIME});
if (!speechQuene.length && !speechPushing) {
mediaSource.endOfStream();
} else {
let buf = new ArrayBuffer();
buf["end"] = true;
speechQuene.push(buf);
}
}
}
} else {
await initSocket();
}
let currDate = getTime();
let lang = voiceRole[type].lang;
let uuid = uuidv4();
if (existVoice === 3) {
voiceSocket.send(getWSPre(currDate, uuid));
}
voiceSocket.send(getWSAudio(currDate, uuid));
voiceSocket.send(getWSText(currDate, uuid, lang, voice, volume, rate, pitch, style, role, content));
voiceSocket["pending"] = true;
if (!supportMSE) {
let blob = await NoMSEPending(key);
if (blob.size === 0) notyf.open({type: "warning", message: translations[locale]["cantSpeechTip"]});
voiceIns.src = URL.createObjectURL(blob);
}
}
} else {
voiceIns = new SpeechSynthesisUtterance();
voiceIns.voice = voiceRole[type];
voiceIns.volume = volume;
voiceIns.rate = rate;
voiceIns.pitch = pitch;
voiceIns.text = content;
}
await speakEvent(voiceIns);
if (enableContVoice) {
if (currentVoiceIdx !== data.length - 1) {return speechEvent(currentVoiceIdx + 1)}
else {endSpeak()}
}
};
let autoVoiceSocket;
let autoMediaSource;
let voiceContentQuene = [];
let voiceEndFlagQuene = [];
let voiceBlobURLQuene = [];
let autoOnlineVoiceFlag = false;
const autoAddQuene = () => {
if (voiceContentQuene.length) {
let content = voiceContentQuene.shift();
let currDate = getTime();
let uuid = uuidv4();
let voice = voiceRole[1].Name;
if (existVoice === 3) {
autoVoiceSocket.send(getWSPre(currDate, uuid));
voice = voiceRole[1].ShortName;
}
autoVoiceSocket.send(getWSAudio(currDate, uuid));
autoVoiceSocket.send(getWSText(currDate, uuid, voiceRole[1].lang, voice, voiceVolume[1], voiceRate[1], voicePitch[1], azureStyle[1], azureRole[1], content));
autoVoiceSocket["pending"] = true;
autoOnlineVoiceFlag = true;
}
}
const autoSpeechEvent = (content, ele, force = false, end = false) => {
if (ele.lastChild.lastChild.classList.contains("readyVoice")) {
ele.classList.add("showVoiceCls");
ele.lastChild.lastChild.className = "voiceCls pauseVoice";
}
if (existVoice >= 2) {
voiceContentQuene.push(content);
voiceEndFlagQuene.push(end);
if (!voiceIns || voiceIns instanceof Audio === false) {
voiceIns = new Audio();
voiceIns.onpause = pauseEv;
voiceIns.onplay = resumeEv;
}
if (!autoVoiceSocket || autoVoiceSocket.readyState > 1) {
autoVoiceSocket = new WebSocket(existVoice === 3 ? getAzureWSURL() : edgeTTSURL);
autoVoiceSocket.binaryType = "arraybuffer";
autoVoiceSocket.onopen = () => {
autoAddQuene();
};
autoVoiceSocket.onerror = () => {
autoOnlineVoiceFlag = false;
};
};
let bufArray = [];
autoVoiceSocket.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
(supportMSE ? speechQuene : bufArray).push(e.data.slice(130));
} else {
if (e.data.indexOf("Path:turn.end") !== -1) {
autoVoiceSocket["pending"] = false;
autoOnlineVoiceFlag = false;
if (!supportMSE) {
let blob = new Blob(bufArray, {type: voiceMIME});
bufArray = [];
if (blob.size) {
let blobURL = URL.createObjectURL(blob);
if (!voiceIns.src) {
voiceIns.src = blobURL;
voiceIns.play();
} else {
voiceBlobURLQuene.push(blobURL);
}
} else {
notyf.open({type: "warning", message: translations[locale]["cantSpeechTip"]});
}
autoAddQuene();
}
if (voiceEndFlagQuene.shift()) {
if (supportMSE) {
if (!speechQuene.length && !speechPushing) {
autoMediaSource.endOfStream();
} else {
let buf = new ArrayBuffer();
buf["end"] = true;
speechQuene.push(buf);
}
} else {
if (!voiceBlobURLQuene.length && !voiceIns.src) {
endSpeak();
} else {
voiceBlobURLQuene.push("end");
}
}
};
if (supportMSE) {
autoAddQuene();
}
}
}
};
if (!autoOnlineVoiceFlag && autoVoiceSocket.readyState) {
autoAddQuene();
}
if (supportMSE) {
if (!autoMediaSource) {
autoMediaSource = new MediaSource();
autoMediaSource.onsourceopen = () => {
if (!sourceBuffer) {
sourceBuffer = autoMediaSource.addSourceBuffer(voiceMIME);
sourceBuffer.onupdateend = () => {
speechPushing = false;
if (speechQuene.length) {
let buf = speechQuene.shift();
if (buf["end"]) {
if (!sourceBuffer.buffered.length) notyf.open({type: "warning", message: translations[locale]["cantSpeechTip"]});
autoMediaSource.endOfStream();
} else {
speechPushing = true;
sourceBuffer.appendBuffer(buf);
}
}
};
}
}
}
if (!voiceIns.src) {
voiceIns.src = URL.createObjectURL(autoMediaSource);
voiceIns.play();
voiceIns.onended = voiceIns.onerror = () => {
endSpeak();
}
}
} else {
voiceIns.onended = voiceIns.onerror = () => {
if (voiceBlobURLQuene.length) {
let src = voiceBlobURLQuene.shift();
if (src === "end") {
endSpeak();
} else {
voiceIns.src = src;
voiceIns.currentTime = 0;
voiceIns.play();
}
} else {
voiceIns.currentTime = 0;
voiceIns.removeAttribute("src");
}
}
}
} else {
voiceIns = new SpeechSynthesisUtterance(content);
voiceIns.volume = voiceVolume[1];
voiceIns.rate = voiceRate[1];
voiceIns.pitch = voicePitch[1];
voiceIns.voice = voiceRole[1];
speakEvent(voiceIns, force, end);
}
};
const confirmAction = (prompt) => {
if (window.confirm(prompt)) {return true}
else {return false}
};
let autoVoiceIdx = 0;
let autoVoiceDataIdx;
let controller;
let controllerId;
let refreshIdx;
let currentResEle;
let progressData = "";
const streamGen = async (long) => {
controller = new AbortController();
controllerId = setTimeout(() => {
notyf.error(translations[locale]["timeoutTip"]);
stopLoading();
}, 120000);
let isRefresh = refreshIdx !== void 0;
if (isRefresh) {
currentResEle = chatlog.children[systemRole ? refreshIdx - 1 : refreshIdx];
} else if (!currentResEle) {
currentResEle = createConvEle("response", true, modelVersion);
currentResEle.children[1].innerHTML = "<p class='cursorCls'><br /></p>";
currentResEle.dataset.loading = true;
scrollToBottom();
}
let idx = isRefresh ? refreshIdx : data.length;
if (existVoice && enableAutoVoice && !long) {
if (isRefresh) {
endSpeak();
autoVoiceDataIdx = currentVoiceIdx = idx;
} else if (currentVoiceIdx !== data.length) {
endSpeak();
autoVoiceDataIdx = currentVoiceIdx = idx;
}
};
try {
let dataSlice;
if (long) {
idx = isRefresh ? refreshIdx : data.length - 1;
dataSlice = [data[idx - 1], data[idx]];
if (systemRole) {dataSlice.unshift(data[0]);}
} else if (enableCont) {
dataSlice = data.slice(0, idx);
} else {
dataSlice = [data[idx - 1]];
if (systemRole) {dataSlice.unshift(data[0]);}
}
dataSlice = dataSlice.map(item => {
if (item.role === "assistant") return {role: item.role, content: item.content};
else return item;
})
let headers = {"Content-Type": "application/json"};
if (customAPIKey) headers["Authorization"] = "Bearer " + customAPIKey;
const res = await fetch(apiHost + API_URL, {
method: "POST",
headers,
body: JSON.stringify({
messages: dataSlice,
model: modelVersion,
stream: true,
temperature: roleTemp,
top_p: roleNature
}),
signal: controller.signal
});
clearTimeout(controllerId);
controllerId = void 0;
if (res.status !== 200) {
if (res.status === 401) {
notyf.error(translations[locale]["errorAiKeyTip"])
} else if (res.status === 400 || res.status === 413) {
notyf.error(translations[locale]["largeReqTip"]);
} else if (res.status === 404) {
notyf.error(translations[locale]["noModelPerTip"]);
} else if (res.status === 429) {
notyf.error(res.statusText ? translations[locale]["apiRateTip"] : translations[locale]["exceedLimitTip"]);
} else {
notyf.error(translations[locale]["badGateTip"]);
}
stopLoading();
return;
}
const decoder = new TextDecoder();
const reader = res.body.getReader();
const readChunk = async () => {
return reader.read().then(async ({value, done}) => {
if (!done) {
value = decoder.decode(value);
let chunks = value.match(/[^n]+/g);
if (!chunks) return readChunk();
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
if (chunk) {
let payload;
try {
payload = JSON.parse(chunk.slice(6));
} catch (e) {
break;
}
if (payload.choices[0].finish_reason) {
let lenStop = payload.choices[0].finish_reason === "length";
let longReplyFlag = enableLongReply && lenStop;
let ele = currentResEle.lastChild.children[0].children[0];
if (!enableLongReply && lenStop) {ele.className = "halfRefReq optionItem"; ele.title = translations[locale]["continue"]}
else {ele.className = "refreshReq optionItem"; ele.title = translations[locale]["refresh"]};
if (existVoice && enableAutoVoice && currentVoiceIdx === autoVoiceDataIdx) {
let voiceText = longReplyFlag ? "" : progressData.slice(autoVoiceIdx), stop = !longReplyFlag;
autoSpeechEvent(voiceText, currentResEle, false, stop);
}
break;
} else {
let content = payload.choices[0].delta.content;
if (content) {
if (!progressData && !content.trim()) continue;
if (existVoice && enableAutoVoice && currentVoiceIdx === autoVoiceDataIdx) {
let spliter = content.match(/.|?|!|~|。|?|!|n/);
if (spliter) {
let voiceText = progressData.slice(autoVoiceIdx) + content.slice(0, spliter.index + 1);
autoVoiceIdx += voiceText.length;
autoSpeechEvent(voiceText, currentResEle);
}
}
if (progressData) await delay();
progressData += content;
currentResEle.children[1].innerHTML = md.render(progressData);
if (!isRefresh) {
scrollToBottom();
}
}
}
}
}
return readChunk();
} else {
if (isRefresh) {
data[refreshIdx].content = progressData;
if (longReplyFlag) return streamGen(true);
} else {
if (long) {data[data.length - 1].content = progressData}
else {data.push({role: "assistant", content: progressData, model: modelVersion})}
if (longReplyFlag) return streamGen(true);
}
stopLoading(false);
}
});
};
await readChunk();
} catch (e) {
if (e.message.indexOf("aborted") === -1) {
notyf.error(translations[locale]["badEndpointTip"])
stopLoading();
}
}
};
const loadAction = (bool) => {
loading = bool;
sendBtnEle.disabled = bool;
sendBtnEle.className = bool ? " loading" : "loaded";
stopEle.style.display = bool ? "flex" : "none";
textInputEvent();
};
const updateChatPre = () => {
let ele = activeChatEle.children[1].children[1];
let first = data.find(item => {return item.role === "assistant"});
ele.textContent = first ? first.content.slice(0, 30) : "";
forceRepaint(ele.parentElement)
}
const stopLoading = (abort = true) => {
stopEle.style.display = "none";
if (currentResEle.children[1].querySelector(".cursorCls")) currentResEle.children[1].innerHTML = "<br />";
if (abort) {
controller.abort();
if (controllerId) clearTimeout(controllerId);
if (delayId) clearTimeout(delayId);
if (refreshIdx !== void 0) {data[refreshIdx].content = progressData}
else if (data[data.length - 1].role === "assistant") {data[data.length - 1].content = progressData}
else {data.push({role: "assistant", content: progressData, model: modelVersion})}
if (existVoice && enableAutoVoice && currentVoiceIdx === autoVoiceDataIdx && progressData.length) {
let voiceText = progressData.slice(autoVoiceIdx);
autoSpeechEvent(voiceText, currentResEle, false, true);
}
}
if (activeChatEle.children[1].children[1].textContent === "") updateChatPre();
updateChats();
controllerId = delayId = refreshIdx = autoVoiceDataIdx = void 0;
autoVoiceIdx = 0;
currentResEle.dataset.loading = false;
currentResEle = null;
progressData = "";
loadAction(false);
};
const generateText = (message) => {
loadAction(true);
let requestEle;
let isBottom = isContentBottom();
if (editingIdx !== void 0) {
let idx = editingIdx;
let eleIdx = systemRole ? idx - 1 : idx;
requestEle = chatlog.children[eleIdx];
data[idx].content = message;
resumeSend();
if (idx !== data.length - 1) {
requestEle.children[1].textContent = message;
if (data[idx + 1].role !== "assistant") {
if (currentVoiceIdx !== void 0) {
if (currentVoiceIdx > idx) {currentVoiceIdx++}
}
data.splice(idx + 1, 0, {role: "assistant", content: "", model: modelVersion});
chatlog.insertBefore(createConvEle("response", false, modelVersion), chatlog.children[eleIdx + 1]);
}
chatlog.children[eleIdx + 1].children[1].innerHTML = "<p class='cursorCls'><br /></p>";
chatlog.children[eleIdx + 1].dataset.loading = true;
idx = idx + 1;
data[idx].content = "";
if (idx === currentVoiceIdx) {endSpeak()};
refreshIdx = idx;
updateChats();
streamGen();
return;
}
} else {
requestEle = createConvEle("request");
data.push({role: "user", content: message});
}
requestEle.children[1].textContent = message;
if (chatsData[activeChatIdx].name === translations[locale]["newChatName"]) {
if (message.length > 20) message = message.slice(0, 17) + "...";
chatsData[activeChatIdx].name = message;
activeChatEle.children[1].children[0].textContent = message;
}
updateChats();
if (isBottom) messagesEle.scrollTo(0, messagesEle.scrollHeight);
streamGen();
};
inputAreaEle.onkeydown = (e) => {
if (e.keyCode === 13 && !e.shiftKey) {
e.preventDefault();
genFunc();
} else if (keepListenMic && recing) {
resetRecRes();
}
};
const genFunc = function () {
clearAutoSendTimer();
if (!keepListenMic && recing) {
toggleRecEv();
}
let message = inputAreaEle.value.trim();
if (message.length !== 0 && noLoading()) {
inputAreaEle.value = "";
inputAreaEle.style.height = "47px";
if (keepListenMic && recing) resetRecRes();
generateText(message);
}
};
sendBtnEle.onclick = genFunc;
stopEle.onclick = stopLoading;
clearEle.onclick = () => {
if (editingIdx === void 0) {
if (noLoading() && confirmAction(translations[locale]["clearChatTip"])) {
endSpeak();
if (systemRole) {data.length = 1}
else {data.length = 0}
chatlog.innerHTML = "";
updateChatPre();
updateChats();
}
} else {
resumeSend();
}
}
</script>
<link crossorigin="anonymous" href="//cdn.staticfile.org/highlight.js/11.8.0/styles/github.min.css"
rel="stylesheet">
<link crossorigin="anonymous" href="//cdn.staticfile.org/KaTeX/0.16.7/katex.min.css" rel="stylesheet">
<link crossorigin="anonymous" href="//npm.elemecdn.com/[email protected]/css/texmath.css" rel="stylesheet">
<script defer>
let downRoleController = new AbortController();
const loadPrompt = () => {
downRoleController.abort();
downRoleController = new AbortController();
setTimeout(() => {
downRoleController.abort();
}, 10000);
preEle.options.length = 5;
if (locale === "zh") {
fetch("https://cdn.jsdelivr.net/gh/PlexPt/awesome-chatgpt-prompts-zh/prompts-zh.json", {
signal: downRoleController.signal
}).then(async (response) => {
let res = await response.json();
for (let i = 0; i < res.length; i++) {
let key = "act" + i;
presetRoleData[key] = res[i].prompt.trim();
let optionEle = document.createElement("option");
optionEle.text = res[i].act;
optionEle.value = key;
preEle.options.add(optionEle);
}
}).catch(e => { })
} else {
fetch("https://cdn.jsdelivr.net/gh/f/awesome-chatgpt-prompts/prompts.csv", {
signal: downRoleController.signal
}).then(async (response) => {
let res = await response.text();
let arr = res.split("n");
for (let i = 1; i < arr.length - 1; i++) {
let key = "act" + i;
let index = arr[i].indexOf(",");
presetRoleData[key] = arr[i].slice(index + 2, -1);
let optionEle = document.createElement("option");
optionEle.text = arr[i].slice(1, index - 1);
optionEle.value = key;
preEle.options.add(optionEle);
}
}).catch(e => { })
}
}
loadPrompt();
</script>
</body>

</html>
     
 
what is notes.io
 

Notes is a web-based application for online taking notes. You can take your notes and share with others people. If you like taking long notes, notes.io is designed for you. To date, over 8,000,000,000+ notes created and continuing...

With notes.io;

  • * You can take a note from anywhere and any device with internet connection.
  • * You can share the notes in social platforms (YouTube, Facebook, Twitter, instagram etc.).
  • * You can quickly share your contents without website, blog and e-mail.
  • * You don't need to create any Account to share a note. As you wish you can use quick, easy and best shortened notes with sms, websites, e-mail, or messaging services (WhatsApp, iMessage, Telegram, Signal).
  • * Notes.io has fabulous infrastructure design for a short link and allows you to share the note as an easy and understandable link.

Fast: Notes.io is built for speed and performance. You can take a notes quickly and browse your archive.

Easy: Notes.io doesn’t require installation. Just write and share note!

Short: Notes.io’s url just 8 character. You’ll get shorten link of your note when you want to share. (Ex: notes.io/q )

Free: Notes.io works for 14 years and has been free since the day it was started.


You immediately create your first note and start sharing with the ones you wish. If you want to contact us, you can use the following communication channels;


Email: [email protected]

Twitter: http://twitter.com/notesio

Instagram: http://instagram.com/notes.io

Facebook: http://facebook.com/notesio



Regards;
Notes.io Team

     
 
Shortened Note Link
 
 
Looding Image
 
     
 
Long File
 
 

For written notes was greater than 18KB Unable to shorten.

To be smaller than 18KB, please organize your notes, or sign in.