You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3581 lines
184 KiB
HTML

11 months ago
<%#
Copyright 2023 sirpdboy Wich <sirpdboy@qq.com> https://github.com/sirpdboy/chatgpt-web.git
Licensed to the public under the Apache License 2.0.
-%>
<%
local uci = require "luci.model.uci".cursor()
local apihosts = uci:get_first('chatgpt-web', 'basic', 'apiHost')
local apikeys = uci:get_first('chatgpt-web', 'basic', 'apikey')
local modelver = uci:get_first('chatgpt-web', 'basic', 'modelver')
local userpic = "/luci-static/chatgpt-web/user/" .. uci:get_first('chatgpt-web', 'basic', 'userpic')
local sysrole = uci:get_first('chatgpt-web', 'basic', 'systemrole')
-%>
<%+header%>
<div class="cbi-map">
<fieldset class="cbi-section">
<link href="/luci-static/chatgpt-web/chatgpt-web.css" rel="stylesheet">
<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="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 41 41" id="aiIcon">
<path fill="white" stroke-width="1.5"
d="M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.998
L20.4018 25.4983L16.071 22.9985V17.9991Z">
</path>
</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>
</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="chatnav">
<div class="navHeader">
<div id="newChat">
<svg width="24" height="24">
<use xlink:href="#addIcon" />
</svg>
<span>new session</span>
</div>
<div id="newFolder" title="new folder">
<svg width="24" height="24">
<use xlink:href="#newFolderIcon" />
</svg>
</div>
</div>
<div class="extraChat">
<input type="text" id="searchChat" placeholder="search" />
<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="clearChat" title="empty">
<svg width="24" height="24">
<use xlink:href="#clearAllIcon" />
</svg>
</div>
<div id="sysSetting" title="set up">
<svg width="24" height="24">
<use xlink:href="#settingIcon" />
</svg>
</div>
</div>
<div class="divider"></div>
<div class="links">
<a href="https://github.com/sirpdboy/chatgpt-web" target="_blank"
rel="noopener noreferrer">Github</a>
</div>
</div>
</nav>
<div class="mainContent">
<div class="top_menu">
<div class="toggler">
<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="setting">
<svg viewBox="0 0 100 100" width="30" height="30">
<title>set up</title>
<circle cx="50" cy="20" r="10" fill="#000" />
<circle cx="50" cy="50" r="10" fill="#000" />
<circle cx="50" cy="80" r="10" fill="#000" />
</svg>
</button>
</div>
<div id="setDialog" style="display:none;">
<div class="setSwitch">
<div data-id="convOption" class="activeSwitch">dialogue</div>
<div data-id="speechOption">speech synthesis</div>
<div data-id="recOption">Speech Recognition</div>
</div>
<div id="convOption">
<div>
<div class="justSetLine presetSelect">
<div>system role</div>
<div>
<label for="preSetSystem">default role</label>
<select id="preSetSystem">
<option value="">default</option>
<option value="normal">assistant</option>
<option value="cat">cat girl</option>
<option value="emoji">expression</option>
<option value="image">There are pictures</option>
</select>
</div>
</div>
<textarea class="inputTextClass areaTextClass" placeholder="<%=sysrole%>" value="<%=sysrole%>" id="systemInput"></textarea>
</div>
<div>
<span>character</span>
<input type="range" id="top_p" min="0" max="1" value="0.7" step="0.05" />
<div class="selectDef">
<span>Accurate and rigorous</span>
<span>flexible innovation</span>
</div>
</div>
<div>
<span>answer quality</span>
<input type="range" id="temp" min="0" max="2" value="1" step="0.05" />
<div class="selectDef">
<span>repeat conservation</span>
<span>nonsense</span>
</div>
</div>
<div>
<span>typewriter speed</span>
<input type="range" id="textSpeed" min="0" max="100" value="88" step="1" />
<div class="selectDef">
<span></span>
<span></span>
</div>
</div>
<div>
<span class="inlineTitle">continuous dialogue</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">long reply</span>
<label class="switch-slide">
<input type="checkbox" id="enableLongReply" hidden />
<label for="enableLongReply" class="switch-slide-label"></label>
</label>
</div>
<div class="setDetail inputDetail apiDetail">
<div id="checkBillBtn" class="loaded">
<span>Query api quota</span>
<svg style="margin:0 auto;height:34px;width:100%;">
<use xlink:href="#loadingIcon"></use>
</svg>
</div>
</div>
<div class="setContent" id="quotaContent" style="display: none;">
<div class="setTitle" id="quotaTitle">free quota</div>
<div class="setDetail">
<div class="progressBar">
<div class="nowProgress" id="usedQuotaBar"></div>
</div>
<div class="progressDetail">
<div><span>used </span><span id="usedQuota"></span></div>
<div><span>available </span><span id="availableQuota"></span></div>
</div>
</div>
</div>
</div>
<div id="speechOption" style="display: none;">
<div class="presetSelect presetModelCls">
<label for="preSetService">speech synthesis service</label>
<select id="preSetService">
<option value="3">Azure Voice</option>
<option selected value="2">Edge voice</option>
<option value="1">system voice</option>
</select>
</div>
<div class="presetSelect presetModelCls">
<label for="preSetAzureRegion">Azure Area</label>
<select id="preSetAzureRegion">
</select>
</div>
<div>
<div>Azure Access Key</div>
<input class="inputTextClass" type="password" placeholder="Azure Key" id="azureKeyInput"
style="-webkit-text-security: disc;" />
</div>
<div id="checkVoiceLoad" style="display: none;">
<svg>
<use xlink:href="#loadingIcon" />
</svg>
<span>load voice</span>
</div>
<div id="speechDetail">
<div>
<div class="justSetLine">
<div>select voice</div>
<div id="voiceTypes">
<span data-type="0">question voice</span>
<span data-type="1" class="selVoiceType">answer voice</span>
</div>
</div>
<select id="preSetSpeech">
</select>
</div>
<div>
<div class="justSetLine">
<input class="inputTextClass" id="testVoiceText" value="Hello, nice to meet you." />
</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>play</span>
</div>
<div class="justSetBtn" onclick="pauseTestVoice()">
<svg width="18" height="18">
<use xlink:href="#pauseVoiceIcon" />
</svg>
<span>pause</span>
</div>
<div class="justSetBtn" onclick="resumeTestVoice()">
<svg width="18" height="18">
<use xlink:href="#resumeVoiceIcon" />
</svg>
<span>continue</span>
</div>
<div class="justSetBtn" style="margin-right: 130px" onclick="stopTestVoice()">
<svg width="18" height="18">
<use xlink:href="#stopIcon" />
</svg>
<span>stop</span>
</div>
</div>
</div>
<div class="justSetLine presetSelect" id="azureExtra" style="display:none;">
<div class="presetModelCls">
<label for="preSetVoiceStyle">style</label>
<select id="preSetVoiceStyle">
</select>
</div>
<div class="presetModelCls">
<label for="preSetVoiceRole">Role</label>
<select id="preSetVoiceRole">
</select>
</div>
</div>
<div>
<span>Volume</span>
<input type="range" id="voiceVolume" min="0" max="1" value="1" step="0.1" />
<div class="selectDef">
<span>Low</span>
<span>high</span>
</div>
</div>
<div>
<span>Speech rate</span>
<input type="range" id="voiceRate" min="0.1" max="2" value="1" step="0.1" />
<div class="selectDef">
<span>slow</span>
<span>quick</span>
</div>
</div>
<div>
<span>tone</span>
<input type="range" id="voicePitch" min="0" max="2" value="1" step="0.1" />
<div class="selectDef">
<span>dull</span>
<span>ups and downs</span>
</div>
</div>
<div>
<span class="inlineTitle">continuous reading</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">automatic reading</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;">The current environment does not support speech recognition, please try to use the chrome kernel browser or redeploy the https page.</div>
<div id="yesRec" style="display: none;">
<div class="presetSelect presetModelCls">
<label for="selectLangOption">语言</label>
<select id="selectLangOption">
</select>
</div>
<div class="presetSelect presetModelCls">
<label for="selectDiaOption">方言</label>
<select id="selectDiaOption">
</select>
</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>stop</div>
</div>
<div class="bottom_wrapper clearfix">
<div class="message_input_wrapper">
<textarea class="message_input_text" spellcheck="false" placeholder="ask something"
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>send</span>
<svg style="margin:0 auto;height:40px;width:100%;">
<use xlink:href="#loadingIcon" />
</svg>
</button>
<button class="clearConv" title="clear session">
<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="sysDialog">
<div id="sysContent">
<div id="closeSet">
<svg width="24" height="24">
<use xlink:href="#closeIcon" />
</svg>
</div>
<div class="sysTitle">set up</div>
<div class="sysDetail">
<div class="setContent">
<div class="setTitle">conversation</div>
<div class="setDetail dataDetail">
<div id="exportChat">
<svg width="24" height="24">
<use xlink:href="#exportIcon" />
</svg>
<span>export</span>
</div>
<label id="importChat" for="importChatInput">
<svg width="24" height="24">
<use xlink:href="#importIcon" />
</svg>
<span>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>empty</span>
</div>
</div>
</div>
<div class="setContent">
<div class="setTitle">set up</div>
<div class="setDetail dataDetail">
<div id="exportSet">
<svg width="24" height="24">
<use xlink:href="#exportSetIcon" />
</svg>
<span>export</span>
</div>
<label id="importSet" for="importSetInput">
<svg width="24" height="24">
<use xlink:href="#importSetIcon" />
</svg>
<span>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>reset</span>
</div>
</div>
</div>
<div class="setContent">
<div class="setTitle">local storage</div>
<div class="setDetail">
<div class="progressBar">
<div class="nowProgress" id="usedStorageBar"></div>
</div>
<div class="progressDetail">
<div><span>used </span><span id="usedStorage"></span></div>
<div><span>available </span><span id="availableStorage"></span></div>
</div>
</div>
</div>
</div>
</div>
</div>
<link href="/luci-static/chatgpt-web/github-markdown-light.min.css" rel="stylesheet">
<link href="/luci-static/chatgpt-web/notyf.min.css" rel="stylesheet">
<script src="/luci-static/chatgpt-web/notyf.min.js"></script>
<script>
const isMobile = navigator.userAgent.match(/iPhone|iPad|iPod|Android|BlackBerry|webOS/);
if (isMobile) {
const script = document.createElement("script");
script.src = "/luci-static/chatgpt-web/mobile-drag-drop3.0.0-rc.0.min.js";
script.defer = true;
script.onload = () => {
MobileDragDrop.polyfill();
}
document.body.appendChild(script);
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "/luci-static/chatgpt-web/mobile-drag-drop3.0.0-rc.0default.css";
document.body.appendChild(link);
}
</script>
<script>
const notyf = new Notyf({
position: {x: 'center', y: 'top'},
types: [
{
type: 'success',
background: '#99c959',
duration: 2000,
},
{
type: 'error',
background: '#e15b64',
duration: 3000,
}
]
});
const windowEle = document.getElementsByClassName("chat_window")[0];
const messagsEle = 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 textarea = document.getElementById("chatinput");
const settingEle = document.getElementById("setting");
const dialogEle = document.getElementById("setDialog");
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");
let voiceType = 1; // Setting 0: Question voice, 1: Answer voice
let voiceRole = []; // voice
let voiceVolume = []; //Volume
let voiceTestText; // test voice text
let voiceRate = []; // Speech rate
let voicePitch = []; // tone
let enableContVoice; // continuous reading
let enableAutoVoice; // automatic reading
let existVoice = 2; //3: Azure voice 2: use edge online voice, 1: use local voice, 0: do not support voice
let azureToken;
let azureTokenTimer;
let azureRegion;
let azureKey;
let azureRole = [];
let azureStyle = [];
let isSafeEnv = location.hostname.match(/127.|localhost/) || location.protocol.match(/https:|file:/); // Https or local security environment
let supportRec = !!window.webkitSpeechRecognition && isSafeEnv; // Whether to support voice recognition input
let recing = false;
let toggleRecEv;
const dayMs = 8.64e7;
const noLoading = () => {
return !loading && (!currentResEle || currentResEle.dataset.loading !== "true")
};
textarea.focus();
const textInputEvent = () => {
if (noLoading()) {
if (textarea.value.trim().length) {
sendBtnEle.classList.add("activeSendBtn");
} else {
sendBtnEle.classList.remove("activeSendBtn");
}
}
textarea.style.height = "47px";
textarea.style.height = textarea.scrollHeight + "px";
};
textarea.oninput = textInputEvent;
document.body.addEventListener("click", event => {
if (event.target.className === "toggler") {
document.body.classList.toggle("show-nav");
if (window.innerWidth > 800) {
localStorage.setItem("pinNav", document.body.classList.contains("show-nav"))
}
} 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("sysContent").contains(ev.target)) {
ev.preventDefault();
ev.stopPropagation();
endSet();
}
}
const endSet = () => {
document.getElementById("sysDialog").style.display = "none";
document.body.removeEventListener("click", endSetEvent, true);
}
document.getElementById("closeSet").onclick = endSet;
document.getElementById("sysSetting").onclick = () => {
document.getElementById("sysDialog").style.display = "flex";
checkStorage();
document.body.addEventListener("click", endSetEvent, true);
};
const initRecSetting = () => {
if (supportRec) {
noRecTip.style.display = "none";
yesRec.style.display = "block";
document.getElementById("voiceRec").style.display = "block";
textarea.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']]
];
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") || "cmn-Hans-CN";
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 recIns = new webkitSpeechRecognition();
// prevent some Android bug
recIns.continuous = !(/\bAndroid\b/i.test(navigator.userAgent));
recIns.interimResults = true;
recIns.maxAlternatives = 1;
let recRes = tempRes = "";
let preRes, affRes;
const resEvent = (event) => {
if (typeof (event.results) === "undefined") {
toggleRecEvent();
return;
}
let isFinal;
for (let i = event.resultIndex; i < event.results.length; ++i) {
isFinal = event.results[i].isFinal;
if (isFinal) {recRes += event.results[i][0].transcript}
else {tempRes = recRes + event.results[i][0].transcript}
}
textarea.value = preRes + (isFinal ? recRes : tempRes) + affRes;
textInputEvent();
textarea.focus();
textarea.selectionEnd = textarea.value.length - affRes.length;
};
const endEvent = (event) => {
if (!(event && event.type === "end")) recIns.stop();
recIns.onresult = null;
recIns.onerror = null;
recIns.onend = null;
recRes = tempRes = "";
voiceRecEle.classList.remove("voiceRecing");
recing = false;
};
const errorEvent = (ev) => {
if (event.error === 'no-speech') {
notyf.error("The voice is not recognized, please adjust the device and try again!")
}
if (event.error === 'audio-capture') {
notyf.error("No microphone found, please make sure it is installed")
}
if (event.error === 'not-allowed') {
notyf.error("Microphone permission not allowed!")
}
endEvent();
}
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 {
preRes = textarea.value.slice(0, textarea.selectionStart);
affRes = textarea.value.slice(textarea.selectionEnd);
recIns.lang = select_dialect.value;
recIns.start();
recIns.onresult = resEvent;
recIns.onerror = errorEvent;
recIns.onend = endEvent;
recing = true;
} catch (e) {
endEvent();
}
} else {
endEvent();
}
};
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.getElementsByClassName("setSwitch")[0].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 (!(window.speechSynthesis && window.SpeechSynthesisUtterance)) {
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);
}
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("Access is denied due to invalid subscription key or bad API endpoint!");
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("Access is denied due to invalid subscription key or bad API endpoint!");
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");
// Support Chinese and English
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)) return 0;
return (a.lang < b.lang) ? 1 : -1; // "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 src="/luci-static/chatgpt-web/markdown-it.min.js"></script>
<script src="/luci-static/chatgpt-web/highlight.min.js"></script>
<script src="/luci-static/chatgpt-web/katex.min.js"></script>
<script src="/luci-static/chatgpt-web/texmath.js"></script>
<script src="/luci-static/chatgpt-web/markdown-it-link-attributes.min.js"></script>
<script>
const API_URL = "v1/chat/completions";
let loading = false;
let presetRoleData = {
"normal": "<%=sysrole%>",
"cat": "You're a cute cat girl, you end every sentence with 'meow'",
"emoji": "Your personality is very lively, there must be at least one emoji icon in every sentence",
"image": "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"
};
let modelVersion = "<%=modelver%>"; //
let apiHost = "<%=apihosts%>"; //
let customAPIKey = "<%=apikeys%>"; //
let systemRole; //
let roleNature; //
let roleTemp; //
let enableCont; //
let enableLongReply; //
let longReplyFlag;
let textSpeed; //
let voiceIns; // Audio or SpeechSynthesisUtterance
const supportMSE = !!window.MediaSource; //
let voiceMIME = "audio/mpeg";
let userAvatar = "<%=userpic%>" || "/luci-static/chatgpt-web/user/girl.jpg"; //
// console.log ( "apihots:" + apiHost +" .. customAPIKey:" +customAPIKey);
const scrollToBottom = () => {
if (messagsEle.scrollHeight - messagsEle.scrollTop - messagsEle.clientHeight < 128) {
messagsEle.scrollTo(0, messagsEle.scrollHeight)
}
}
const scrollToBottomLoad = (ele) => {
if (messagsEle.scrollHeight - messagsEle.scrollTop - messagsEle.clientHeight < ele.clientHeight + 128) {
messagsEle.scrollTo(0, messagsEle.scrollHeight)
}
}
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();
const subRes = await fetch(apiHost + "v1/dashboard/billing/subscription", {
headers,
signal: subCtrl.signal,
credentials: "omit"
});
if (subRes.status !== 200) {
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 (nowDate !== 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 = "$" + parseFloat(used.toFixed(2));
let remain = total - used;
availableQuota.textContent = "$" + Math.floor(remain * 100) / 100;
if (isSub) {
quotaTitle.textContent = "This month's quota";
} else {
let isExpired = new Date() > exp;
if (isExpired) usedQuotaBar.parentElement.classList.add("expiredBar");
let accessDate = formatDate(exp);
quotaTitle.innerHTML = "The free quota is valid until:" + `<span style="color: ${isExpired ? "#e15b64" : "#99c959"} ">${accessDate}</span>`;
}
quotaContent.style.display = "block";
} catch (e) {
notyf.error("Api key error or invalid, please check the api key");
}
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) { //
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 x = {
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 x.getFragment(x.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 = "copy code", // button text
successText = "Copy successfully", // 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 editingIdx;
let originText;
const resumeSend = () => {
if (editingIdx !== void 0) {
chatlog.children[systemRole ? editingIdx - 1 : editingIdx].classList.remove("showEditReq");
}
sendBtnEle.children[0].textContent = "send";
textarea.value = originText;
clearEle.title = "clear session";
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 === "voiceMd") {
let 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 (existVoice >= 2) {
if (voiceIns) voiceIns.pause();
} else {
speechSynthesis.pause();
classList.remove("readyVoice");
classList.remove("pauseVoice");
classList.add("resumeVoice");
}
} else {
if (existVoice >= 2) {
if (voiceIns) voiceIns.play();
} else {
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 = textarea.value;
textarea.value = data[idx].content;
textarea.dispatchEvent(new Event("input"));
textarea.focus();
sendBtnEle.children[0].textContent = "renew";
clearEle.title = "Cancel";
clearEle.classList.add("closeConv");
} else if (id === "refreshMd") {
if (noLoading()) {
if (ev.target.classList.contains("refreshReq")) {
chatlog.children[idx].children[1].innerHTML = "<br />";
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("Copy successfully");
} else if (id === "delMd") {
if (noLoading()) {
if (confirmAction("Do you want to delete this message?")) {
chatlog.removeChild(chatlog.children[idx]);
idx = systemRole ? idx + 1 : idx;
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);
updateChats();
}
}
} else if (id === "downAudio") {
if (chatlog.children[idx].dataset.loading !== "true") {
idx = systemRole ? idx + 1 : idx;
downloadAudio(idx);
}
}
}
}
const formatMdEle = (ele) => {
let avatar = document.createElement("div");
avatar.className = "chatAvatar";
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="edit">
<svg width="18" height="18"><use xlink:href="#chatEditIcon" /></svg>
</div>` : `<div data-id="refreshMd" class="refreshReq optionItem" title="to 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="copy">
<svg width="20" height="20"><use xlink:href="#copyIcon" /></svg>
</div>
<div data-id="delMd" class="optionItem" title="delete">
<svg width="20" height="20"><use xlink:href="#delIcon" /></svg>
</div>` + (existVoice >= 2 ? `<div data-id="downAudio" class="optionItem" title="download voice">
<svg width="20" height="20"><use xlink:href="#downAudioIcon" /></svg>
</div>` : "") + `</div>`;
if (existVoice) {
mdOption.innerHTML += `<div class="voiceCls readyVoice" data-id="voiceMd">
<svg width="21" height="21" role="img"><title>read aloud</title><use xlink:href="#readyVoiceIcon" /></svg>
<svg width="21" height="21" role="img"><title>pause</title><use xlink:href="#pauseVoiceIcon" /></svg>
<svg width="21" height="21" role="img"><title>continue</title><use xlink:href="#resumeVoiceIcon" /></svg>
</div>`
}
mdOption.onclick = mdOptionEvent;
}
let allEle = chatListEle.parentElement;
let folderData = [];
let chatsData = [];
let chatIdxs = [];
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 + "个会话";
if (childLen) {folderEle.classList.add("expandFolder")}
else {folderEle.classList.remove("expandFolder")}
}
folderListEle.ondragenter = chatListEle.ondragenter = function (ev) {
ev.preventDefault();
if (ev.target === dragLi) return;
allEle.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");
allEle.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("Whether to delete the folder?")) {
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;
updateChats();
activeChat();
}
}
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 === "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="folderEdit" style="margin-right:2px" width="24" height="24" role="img"><title>edit</title><use xlink:href="#chatEditIcon" /></svg>
<svg data-type="folderDel" width="24" height="24" role="img"><title>delete</title><use xlink:href="#delIcon" /></svg></div>`
headEle.children[1].children[0].textContent = folder.name;
headEle.children[1].children[1].textContent = folder.idxs.length + "个会话";
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: "new folder", 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" style="margin-right:2px" width="24" height="24" role="img"><title>edit</title><use xlink:href="#chatEditIcon" /></svg>
<svg data-type="chatDel" width="24" height="24" role="img"><title>delete</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: "new session", data: []};
chatsData.push(chat);
chatIdxs.push(chatsData.length - 1);
updateChats();
};
const delChat = (idx, ele, folderIdx, inFolderIdx) => {
if (confirmAction("Do you want to delete session?")) {
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.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.className === "chatLi") {
if (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) => {
let div = document.createElement("div");
div.className = className;
formatMdEle(div);
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"];
allEle.querySelectorAll(".activeChatLi").forEach(ele => {
ele.classList.remove("activeChatLi");
})
allEle.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;
localStorage.setItem("system", systemRole);
} else {
systemRole = "";
systemEle.value = "";
localStorage.setItem("system", systemRole);
}
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").children[1].innerHTML = md.render(data[i].content) || "<br />";
}
}
}
localStorage.setItem("activeChatIdx", activeChatIdx);
};
newChatEle.onclick = () => {
endAll();
addNewChat();
activeChatIdx = chatsData.length - 1;
chatEleAdd(activeChatIdx);
activeChat(chatListEle.lastElementChild);
chatListEle.parentElement.scrollTop = chatListEle.parentElement.scrollHeight;
};
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;
});
let searchIdxs = [];
searchChatEle.oninput = (ev) => {
if (ev.isComposing) lastCompositon = ev.data;
if (compositionFlag) return;
let value = searchChatEle.value;
if (value.length) {
searchIdxs.length = 0;
let data = [];
for (let i = 0; i < chatsData.length; i++) {
let chatEle = getChatEle(i);
chatEle.style.display = null;
let nameIdx = chatsData[i].name.indexOf(value);
let data = chatsData[i].data.find(item => {
return item.content.indexOf(value) !== -1
})
if (nameIdx !== -1 || data) {
let ele = chatEle.children[1];
if (data) {
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.push(i);
} 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);
};
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();
} else {
throw new Error("fmt error");
}
} catch (e) {
notyf.error("Import failed, please check the file format");
}
this.value = "";
})
};
clearChatSet.onclick = clearChat.onclick = () => {
if (confirmAction("Whether to clear all sessions and folders?")) {
chatsData.length = 0;
chatIdxs.length = 0;
folderData.length = 0;
folderListEle.innerHTML = "";
chatListEle.innerHTML = "";
endAll();
addNewChat();
activeChatIdx = 0;
chatEleAdd(activeChatIdx);
updateChats();
checkStorage();
activeChat(chatListEle.firstElementChild);
}
};
let localSetKeys = ['modelVersion', 'system', 'temp', 'top_p', 'textSpeed', 'enableCont', 'enableLongReply', 'existVoice','voiceTestText', 'azureRegion', 'azureKey', 'enableContVoice', 'enableAutoVoice', 'voiceRecLang', '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);
};
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(true);
initVoiceVal();
speechServiceEle.dispatchEvent(new Event("change"));
initRecSetting();
checkStorage();
} catch (e) {
notyf.error("Import failed, please check the file format");
}
this.value = "";
})
};
document.getElementById("resetSet").onclick = () => {
if (confirmAction("Whether to restore all settings to default?")) {
endAll();
let data = {};
if (existVoice === 3) localStorage.removeItem(azureRegion + "voiceData");
for (let i = 0; i < localSetKeys.length; i++) {
let key = localSetKeys[i];
let val = localStorage.removeItem(key);
}
initSetting(true);
initVoiceVal();
speechServiceEle.dispatchEvent(new Event("change"));
initRecSetting();
checkStorage();
}
}
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 initSetting = (force = false) => {
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) {
if (localSystem) {
systemRole = localSystem;
systemEle.value = localSystem;
data.unshift({role: "system", content: systemRole});
processIdx(1);
updateChats();
} else {
systemRole = systemEle.value;
}
} else if (force) {
systemRole = systemEle.value = localSystem || systemEle.getAttribute("value") || "";
systemEle.dispatchEvent(new Event("change"));
}
const preEle = document.getElementById("preSetSystem");
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 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");
};
};
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.config\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${date}\r\nContent-Type: application/json\r\n\r\n{"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.context\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${date}\r\nContent-Type: application/json\r\n\r\n{"synthesis":{"audio":{"metadataOptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":false},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}`
: `X-Timestamp:${date}\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n{"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: ssml\r\nX-RequestId: ${requestId}\r\nX-Timestamp: ${date}\r\nContent-Type: application/ssml+xml\r\n\r\n<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}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${date}Z\r\nPath:ssml\r\n\r\n<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";
let currentVoiceIdx;
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 (existVoice >= 2) {
if (voiceIns) {
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 {
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 (existVoice >= 2) {
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: "The current voice cannot synthesize this message, please choose another voice or message!"
});
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: "The current voice cannot synthesize this message, please choose another voice or message!"});
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});
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);
voiceIns.src = URL.createObjectURL(blob);
}
}
} else {
if (!voiceIns) {
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 (blob.size === 0) notyf.open({type: "warning", message: "The current voice cannot synthesize this message, please choose another voice or message!"});
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 = 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: "The current voice cannot synthesize this message, please choose another voice or message!"});
}
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"]) {
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("请求超时,请稍后重试!");
stopLoading();
}, 60000);
let headers = {"Content-Type": "application/json"};
if (customAPIKey) headers["Authorization"] = "Bearer " + customAPIKey;
let isRefresh = refreshIdx !== void 0;
if (isRefresh) {
currentResEle = chatlog.children[systemRole ? refreshIdx - 1 : refreshIdx];
} else if (!currentResEle) {
currentResEle = createConvEle("response");
currentResEle.children[1].innerHTML = "<br />";
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;
}
}
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]);}
}
try {
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("The Api key is wrong or invalid, please check the api key!")
} else if (res.status === 400) {
notyf.error("The content of the request is too large, please delete part of the conversation or open the settings to close the continuous conversation!");
} else if (res.status === 404) {
notyf.error("Not authorized to use this model, please open the settings to choose another gpt model!");
} else if (res.status === 429) {
notyf.error(res.statusText ? "Trigger api call frequency limit, please try again later!" : "Api usage exceeded limit, please check your bill!");
} else {
notyf.error("Gateway error or timeout, please try again later!");
}
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);
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 = "continue"}
else {ele.className = "refreshReq optionItem"; ele.title = "to 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})}
if (longReplyFlag) return streamGen(true);
}
stopLoading(false);
}
});
};
await readChunk();
} catch (e) {
if (e.message.indexOf("aborted") === -1) {
notyf.error("Failed to access the interface, please check the interface!")
stopLoading();
}
}
};
const loadAction = (bool) => {
loading = bool;
sendBtnEle.disabled = bool;
sendBtnEle.className = bool ? " loading" : "loaded";
stopEle.style.display = bool ? "flex" : "none";
textInputEvent();
}
const stopLoading = (abort = true) => {
stopEle.style.display = "none";
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})}
if (existVoice && enableAutoVoice && currentVoiceIdx === autoVoiceDataIdx && progressData.length) {
let voiceText = progressData.slice(autoVoiceIdx);
autoSpeechEvent(voiceText, currentResEle, false, true);
}
}
if (activeChatEle.children[1].children[1].textContent === "") {
let first = data.find(item => {return item.role === "assistant"});
if (first) {activeChatEle.children[1].children[1].textContent = first.content.slice(0, 30)}
}
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;
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: ""});
chatlog.insertBefore(createConvEle("response", false), chatlog.children[eleIdx + 1]);
}
chatlog.children[eleIdx + 1].children[1].innerHTML = "<br />";
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 === "新的会话") {
if (message.length > 20) message = message.slice(0, 17) + "...";
chatsData[activeChatIdx].name = message;
activeChatEle.children[1].children[0].textContent = message;
}
updateChats();
scrollToBottom();
streamGen();
};
textarea.onkeydown = (e) => {
if (e.keyCode === 13 && !e.shiftKey) {
e.preventDefault();
genFunc();
}
};
const genFunc = function () {
if (recing) {
toggleRecEv();
}
let message = textarea.value.trim();
if (message.length !== 0 && noLoading()) {
textarea.value = "";
textarea.style.height = "47px";
generateText(message);
}
};
sendBtnEle.onclick = genFunc;
stopEle.onclick = stopLoading;
clearEle.onclick = () => {
if (editingIdx === void 0) {
if (noLoading() && confirmAction("Whether to clear the session?")) {
endSpeak();
if (systemRole) {data.length = 1}
else {data.length = 0}
chatlog.innerHTML = "";
updateChats();
}
} else {
resumeSend();
}
}
</script>
<link href="/luci-static/chatgpt-web/github.min.css" rel="stylesheet">
<link href="/luci-static/chatgpt-web/katex.min.css" rel="stylesheet">
<link href="/luci-static/chatgpt-web/texmath.css" rel="stylesheet">
<script defer>
const downRoleController = new AbortController();
setTimeout(() => {
downRoleController.abort();
}, 10000);
const preEle = document.getElementById("preSetSystem");
fetch("/luci-static/chatgpt-web/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 => { })
</script>
</body>
</fieldset>
</div>
<%+footer%>