dsh-api-dashboard 1.1.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +191 -25
- package/client/client.js +847 -163
- package/package.json +2 -2
- package/src/index.js +1546 -194
package/client/client.js
CHANGED
|
@@ -25,11 +25,15 @@ window.__ModuleLoader__.load({
|
|
|
25
25
|
const ICONS = {
|
|
26
26
|
'alibaba': '<svg fill="#FF6A00" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M3.996 4.517h5.291L8.01 6.324L4.153 7.506a1.67 1.67 0 0 0-1.165 1.601v5.786a1.67 1.67 0 0 0 1.165 1.6l3.857 1.183l1.277 1.807H3.996A3.996 3.996 0 0 1 0 15.487V8.513a3.996 3.996 0 0 1 3.996-3.996m16.008 0h-5.291l1.277 1.807l3.857 1.182c.715.227 1.17.889 1.165 1.601v5.786a1.67 1.67 0 0 1-1.165 1.6l-3.857 1.183l-1.277 1.807h5.291A3.996 3.996 0 0 0 24 15.487V8.513a3.996 3.996 0 0 0-3.996-3.996m-4.007 8.345H8.002v-1.804h7.995Z"/></svg>',
|
|
27
27
|
'claude': '<svg fill="#D97757" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="m4.714 15.956l4.718-2.648l.079-.23l-.08-.128h-.23l-.79-.048l-2.695-.073l-2.337-.097l-2.265-.122l-.57-.121l-.535-.704l.055-.353l.48-.321l.685.06l1.518.104l2.277.157l1.651.098l2.447.255h.389l.054-.158l-.133-.097l-.103-.098l-2.356-1.596l-2.55-1.688l-1.336-.972l-.722-.491L2 6.223l-.158-1.008l.656-.722l.88.06l.224.061l.893.686l1.906 1.476l2.49 1.833l.364.304l.146-.104l.018-.072l-.164-.274l-1.354-2.446l-1.445-2.49l-.644-1.032l-.17-.619a3 3 0 0 1-.103-.729L6.287.133L6.7 0l.995.134l.42.364l.619 1.415L9.735 4.14l1.555 3.03l.455.898l.243.832l.09.255h.159V9.01l.127-1.706l.237-2.095l.23-2.695l.08-.76l.376-.91l.747-.492l.583.28l.48.685l-.067.444l-.286 1.851l-.558 2.903l-.365 1.942h.213l.243-.242l.983-1.306l1.652-2.064l.728-.82l.85-.904l.547-.431h1.032l.759 1.129l-.34 1.166l-1.063 1.347l-.88 1.142l-1.263 1.7l-.79 1.36l.074.11l.188-.02l2.853-.606l1.542-.28l1.84-.315l.832.388l.09.395l-.327.807l-1.967.486l-2.307.462l-3.436.813l-.043.03l.049.061l1.548.146l.662.036h1.62l3.018.225l.79.522l.473.638l-.08.485l-1.213.62l-1.64-.389l-3.825-.91l-1.31-.329h-.183v.11l1.093 1.068l2.003 1.81l2.508 2.33l.127.578l-.321.455l-.34-.049l-2.204-1.657l-.85-.747l-1.925-1.62h-.127v.17l.443.649l2.343 3.521l.122 1.08l-.17.353l-.607.213l-.668-.122l-1.372-1.924l-1.415-2.168l-1.141-1.943l-.14.08l-.674 7.254l-.316.37l-.728.28l-.607-.461l-.322-.747l.322-1.476l.388-1.924l.316-1.53l.285-1.9l.17-.632l-.012-.042l-.14.018l-1.432 1.967l-2.18 2.945l-1.724 1.845l-.413.164l-.716-.37l.066-.662l.401-.589l2.386-3.036l1.439-1.882l.929-1.086l-.006-.158h-.055L4.138 18.56l-1.13.146l-.485-.456l.06-.746l.231-.243l1.907-1.312Z"/></svg>',
|
|
28
|
+
'doubao': '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 64 64"><image href="data:image/png;base64,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" width="64" height="64"/></svg>',
|
|
28
29
|
'deepseek': '<svg fill="#4D6BFE" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M23.748 4.651c-.254-.124-.364.113-.512.233c-.051.04-.094.09-.137.137c-.372.397-.806.657-1.373.626c-.829-.046-1.537.214-2.163.848c-.133-.782-.575-1.248-1.247-1.548c-.352-.155-.708-.311-.955-.65c-.172-.24-.219-.509-.305-.774c-.055-.16-.11-.323-.293-.35c-.2-.031-.278.136-.356.276c-.313.572-.434 1.202-.422 1.84c.027 1.436.633 2.58 1.838 3.393c.137.094.172.187.129.323c-.082.28-.18.553-.266.833c-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836c.27-.098.094-.433-.778-.428c-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136a9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653c1.857 1.533 3.997 2.284 6.438 2.14c1.482-.085 3.132-.284 4.994-1.86c.47.234.962.328 1.78.398c.629.058 1.235-.031 1.705-.129c.735-.155.684-.836.418-.961c-2.155-1.004-1.682-.595-2.112-.926c1.095-1.295 2.768-3.598 3.284-6.733c.05-.346.115-.834.108-1.114c-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517c.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16c-.39.024-.32.472-.234.763c.09.288.207.487.371.74c.114.167.192.416-.113.603c-.673.416-1.842-.14-1.897-.168c-1.361-.801-2.5-1.86-3.301-3.306c-.775-1.393-1.225-2.888-1.299-4.482c-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774c.868.86 1.525 1.887 2.202 2.89c.72 1.066 1.494 2.082 2.48 2.915c.348.291.626.513.892.677c-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287a.3.3 0 0 1 .113.074a.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727c-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078a.253.253 0 0 1-.114-.358a1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016c.352.144.618.408 1.001.782c.392.451.462.576.685.915c.176.264.336.536.446.848c.066.194-.02.353-.25.45"/></svg>',
|
|
30
|
+
'hunyuan': '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 31 30" fill="none"><path d="M15.2287 30C23.4603 30 30.1333 23.2843 30.1333 15C30.1333 6.71573 23.4603 0 15.2287 0C6.9972 0 0.324219 6.71573 0.324219 15C0.324219 23.2843 6.9972 30 15.2287 30Z" fill="#B3DDF2"/><path d="M15.2318 29.9966C19.3467 29.9966 22.6824 26.6395 22.6824 22.4983C22.6824 18.3571 19.3467 15 15.2318 15C11.117 15 7.78125 18.3571 7.78125 22.4983C7.78125 26.6395 11.117 29.9966 15.2318 29.9966Z" fill="#0053E0"/><mask id="mask0_621_3115" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="31" height="30"><path d="M15.2326 30C23.4642 30 30.1372 23.2843 30.1372 15C30.1372 6.71573 23.4642 0 15.2326 0C7.00111 0 0.328125 6.71573 0.328125 15C0.328125 23.2843 7.00111 30 15.2326 30Z" fill="white"/></mask><g mask="url(#mask0_621_3115)"><path d="M15.2324 30L30.137 30L30.137 1.14441e-05L15.2324 1.14441e-05L15.2324 30Z" fill="#0053E0"/></g><path d="M15.2318 14.9966C19.3467 14.9966 22.6824 11.6395 22.6824 7.49831C22.6824 3.35711 19.3467 0 15.2318 0C11.117 0 7.78125 3.35711 7.78125 7.49831C7.78125 11.6395 11.117 14.9966 15.2318 14.9966Z" fill="#B3DDF2"/><path d="M15.3712 0.00674309C18.5069 0.0842886 21.0228 2.66015 21.0228 5.83277C21.0228 8.50977 19.2271 10.8328 16.6007 11.5037C16.6007 11.5037 16.6241 11.5003 16.6576 11.4936C14.4097 11.9757 12.7246 13.9852 12.7246 16.3924C12.7246 19.1605 14.9524 21.4026 17.7028 21.4026C18.4164 21.4026 19.3879 21.234 20.001 20.9609C23.9675 19.3493 26.47 15.4552 26.47 10.8867C26.47 4.87188 21.6258 0 15.6559 0C15.5588 0 15.465 0.00337154 15.3712 0.00674309ZM16.9491 11.443C16.9491 11.443 16.9591 11.443 16.9625 11.443C16.9591 11.443 16.9524 11.443 16.9491 11.443Z" fill="#2AB9FF"/><path d="M4.20222 18.9028C4.20222 15.8718 5.4116 13.124 7.36805 11.1213V11.1145C7.87726 10.5549 8.19217 9.81313 8.19217 8.99384C8.19217 7.2575 6.79518 5.85156 5.06989 5.85156C3.87056 5.85156 2.82868 6.53599 2.30607 7.53733C1.04979 9.73558 0.326172 12.2811 0.326172 15.0019C0.326172 23.0936 6.69468 29.685 14.6645 29.9884C8.83873 29.6917 4.20222 24.8435 4.20222 18.9028ZM2.16537 7.84752C2.15532 7.87112 2.14862 7.89135 2.14192 7.91495C2.14862 7.89135 2.15867 7.87112 2.16537 7.84752Z" fill="#0053E0"/><path d="M14.668 29.9844C14.8556 29.9945 15.0465 29.9979 15.2341 29.9979C15.0432 29.9979 14.8556 29.9911 14.668 29.9844Z" fill="#0053E0"/><path d="M15.3756 0.0078125C18.5113 0.085358 21.0272 2.66122 21.0272 5.83384C21.0272 8.51084 19.2014 10.8372 16.6051 11.5048C14.6285 11.9667 13.1679 13.5344 12.8262 15.4023C12.8262 15.4023 13.8948 15.0044 15.2148 15.0044C15.2148 15.0044 15.2081 15.0044 15.2047 15.0044C15.2148 15.0044 15.2215 15.0044 15.2315 15.0044C19.3488 15.0044 22.6821 11.6464 22.6821 7.50612C22.6821 3.36587 19.4225 0.085358 15.3756 0.0078125Z" fill="#ECECEE"/></svg>',
|
|
29
31
|
'gemini': '<svg fill="#4285F4" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68q.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58a12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68q-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96q2.19.93 3.81 2.55t2.55 3.81"/></svg>',
|
|
30
32
|
'groq': '<svg fill="#F55036" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M3 3v18h18V3zm11.72 13.37c-.41.38-.82.66-1.33.87l-.21.09c-.83.3-1.82.21-2.63-.1c-.45-.21-.82-.46-1.19-.8c.33-.41.66-.75 1.07-1.07l.27.21c.5.35 1 .47 1.61.41c.62-.12 1.12-.4 1.52-.9c.37-.61.41-1.09.41-1.8V10.4c0-.72-.15-1.18-.6-1.74c-.61-.49-1.17-.74-1.96-.7c-.66.11-1.19.42-1.59.95c-.33.53-.48 1.07-.37 1.69c.2.68.45 1.25 1.07 1.61c.52.27.98.32 1.56.33h.25c.2.02.4.02.61.03V14c-1.49.06-2.65.06-3.84-.97a4.22 4.22 0 0 1-1.23-2.8c.04-.88.35-1.6.86-2.32l.15-.23c1.43-1.51 3.7-1.61 5.31-.31l.17.14c.58.52.96 1.25 1.08 2.01c0 .16.01.33.01.49v3.6c0 1.05-.3 1.95-1.02 2.74Z"/></svg>',
|
|
31
33
|
'mistral': '<svg fill="#FA520F" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z"/></svg>',
|
|
32
34
|
'moonshot': '<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256"><path d="m11.222 180.43l101.75 27.218a224 224 0 0 0 .64 21.683l63.53 16.99a128 128 0 0 1-59.584 9.226l-1.92-.17l-.47-.044l-.896-.095l-1.002-.107q-.838-.101-1.675-.213l-1.141-.15l-1.174-.17q-1.71-.25-3.413-.544l-.448-.086l-.8-.138l-1.141-.214l-.747-.16l-.992-.202l-.8-.17l-1.013-.214l-1.035-.246l-1.003-.234l-.725-.182l-.939-.234l-.96-.256l-1.013-.267l-.874-.245l-1.163-.32l-.661-.213l-.896-.267l-.992-.299l-1.12-.362l-.62-.203l-.852-.277l-.96-.33l-.704-.257l-.47-.16l-.725-.266l-1.077-.395l-.608-.235l-.854-.32l-.928-.373l-.938-.373l-.843-.342l-1.013-.426l-.672-.299l-.672-.288l-.438-.192l-.704-.32l-1.098-.501l-.555-.256l-1.024-.49l-.661-.32l-.896-.427l-.918-.47l-.992-.5l-.554-.289l-1.1-.586l-.607-.32l-.619-.342l-.49-.277l-1.003-.565l-.64-.363l-.544-.32l-.768-.437l-.875-.533l-.992-.598l-.554-.34l-.896-.566l-.651-.416l-.843-.533l-.746-.502l-.566-.373l-.576-.384l-.469-.32l-.47-.32l-.426-.299l-.608-.426l-.81-.576l-.737-.533l-.789-.576l-.597-.448l-.811-.608l-.81-.63l-.918-.714l-.48-.373l-.683-.555l-.789-.64l-.95-.778l-.49-.416l-.49-.416l-.46-.395l-.48-.427l-.65-.565l-.747-.661l-.725-.64l-.661-.619l-.715-.661l-.565-.533l-.939-.896q-.531-.514-1.056-1.035l-.31-.298l-.437-.448l-.736-.747l-.533-.544l-.533-.565a69 69 0 0 1-1.792-1.91l-.854-.938l-.661-.746l-.757-.854l-.448-.522l-.566-.662l-.618-.725l-.491-.597l-.288-.352l-.48-.587l-.704-.874l-.437-.555l-.534-.682l-.213-.267a128 128 0 0 1-15.36-25.619M.342 118.632l121.098 32.391a218 218 0 0 0-5.002 21.449l115.38 30.866a128.8 128.8 0 0 1-19.68 21.384L7.009 169.838l-.17-.49l-.373-1.11q-.273-.813-.534-1.631l-.074-.246a127 127 0 0 1-2.208-7.903l-.32-1.344l-.192-.853l-.224-1.034l-.192-.864l-.192-.96l-.182-.896l-.192-1.003a126 126 0 0 1-.757-4.543l-.181-1.259l-.118-.885l-.138-1.088a128 128 0 0 1-.203-1.717l-.053-.501c-.797-7.6-.919-15.256-.363-22.878m16.992-54.928L144.78 97.793a218 218 0 0 0-10.773 19.997l120.48 32.232a126.4 126.4 0 0 1-7.126 25.224l-123.2-32.957L1.323 109.44l.16-1.066l.086-.523l.106-.714l.16-.928l.192-1.046c.278-1.578.598-3.146.94-4.714l.298-1.322l.213-.907l.256-1.034c.235-.96.48-1.92.747-2.859l.298-1.088l.246-.885l.32-1.066l.266-.875l.32-1.024l.278-.875l.33-1.013a127 127 0 0 1 10.774-23.805zm47.381-46.928l120.373 32.189a222 222 0 0 0-18.005 18.355l83.445 22.323A127.8 127.8 0 0 1 256 118.068L22.465 55.61l.48-.693l.288-.427l.426-.586l.491-.694l.587-.81l.576-.768l.682-.917l.534-.694l.608-.778l.586-.747l.64-.789l.587-.736l.693-.821l.576-.704l.704-.821l.566-.64l.768-.875l.565-.64l.715-.789l.576-.619l.778-.832l.619-.64l.672-.714l1.792-1.813l1.067-1.045l.629-.598l.81-.757a129 129 0 0 1 24.235-17.886M128.182.01h1.034l.875.01l.736.011l.576.021l.725.022l.491.01l.81.032l.502.022l.64.032l.576.021l.928.053l1.12.075l1.536.117l.939.075l.469.043l.821.085l.875.085l.501.054l1.088.128l.534.064l1.152.149l.864.107l.448.064l.693.106l2.208.341l.747.128l.693.118l1.493.277l.982.192l1.173.235l.49.106l.8.171l.438.107l.661.138l.448.107l.694.16l.522.128l.758.181l1.024.256l1.194.32l1.206.32l1.205.341l.533.16l.747.214l.832.256l.779.245l.533.17l.533.171l.81.267l1.057.352l1.088.384l.512.181l.683.246l.992.362l1.173.437l1.237.48l1.067.427l.501.213l.64.256l.438.192l.672.278l.426.192l.608.266l1.174.512l1.066.49l.79.374l.8.384l.64.299l.98.49l.972.48l1.088.555l.565.298l.523.278l.49.256l.64.352l.438.234l.554.31l.939.533l1.13.64l.929.544l.608.362l.565.342l1.024.629l.939.587l1.045.66l.384.257l.683.437l.896.597l.426.288l.662.448l.66.459l.246.181c.576.395 1.152.8 1.717 1.216l.886.64l.693.512l.597.459l.918.693l.874.683l.427.32l.533.437l.918.736l.842.693l.907.757a129 129 0 0 1 20.363 21.662L77.035 10.612l.662-.288l.693-.298l.864-.363l.917-.373c1.206-.48 2.422-.96 3.638-1.397l1.024-.374l.992-.352l.896-.32l1.024-.33c.928-.32 1.877-.619 2.816-.907l.97-.288l.918-.267l1.088-.32l.906-.245l1.067-.277l.917-.256l.96-.245l.97-.235l1.014-.235l.96-.213l1.045-.224l.971-.213l1.013-.192l.982-.192l1.066-.192l.971-.17l1.045-.182l.982-.15l1.034-.16l.982-.138l1.088-.139l.97-.128l1.12-.128l.96-.106l1.12-.107a69 69 0 0 1 2.987-.256l1.13-.085l.96-.054l1.174-.064l.992-.042l1.067-.043l1.034-.021L126.08.02l2.102-.02z"/></svg>',
|
|
35
|
+
'minimax': '<svg fill="#E73562" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M11.43 3.92a.86.86 0 1 0-1.718 0v14.236a1.999 1.999 0 0 1-3.997 0V9.022a.86.86 0 1 0-1.718 0v3.87a1.999 1.999 0 0 1-3.997 0V11.49a.57.57 0 0 1 1.139 0v1.404a.86.86 0 0 0 1.719 0V9.022a1.999 1.999 0 0 1 3.997 0v9.134a.86.86 0 0 0 1.719 0V3.92a1.998 1.998 0 1 1 3.996 0v11.788a.57.57 0 1 1-1.139 0zm10.572 3.105a2 2 0 0 0-1.999 1.997v7.63a.86.86 0 0 1-1.718 0V3.923a1.999 1.999 0 0 0-3.997 0v16.16a.86.86 0 0 1-1.719 0V18.08a.57.57 0 1 0-1.138 0v2a1.998 1.998 0 0 0 3.996 0V3.92a.86.86 0 0 1 1.719 0v12.73a1.999 1.999 0 0 0 3.996 0V9.023a.86.86 0 1 1 1.72 0v6.686a.57.57 0 0 0 1.138 0V9.022a2 2 0 0 0-1.998-1.997"/></svg>',
|
|
36
|
+
'mimo': '<svg fill="#FF6900" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M12 0C8.016 0 4.756.255 2.493 2.516.23 4.776 0 8.033 0 12.012c0 3.98.23 7.235 2.494 9.497C4.757 23.77 8.017 24 12 24c3.983 0 7.243-.23 9.506-2.491C23.77 19.247 24 15.99 24 12.012c0-3.984-.233-7.243-2.502-9.504C19.234.252 15.978 0 12 0zM4.906 7.405h5.624c1.47 0 3.007.068 3.764.827.746.746.827 2.233.83 3.676v4.54a.15.15 0 0 1-.152.147h-1.947a.15.15 0 0 1-.152-.148V11.83c-.002-.806-.048-1.634-.464-2.051-.358-.36-1.026-.441-1.72-.458H7.158a.15.15 0 0 0-.151.147v6.98a.15.15 0 0 1-.152.148H4.906a.15.15 0 0 1-.15-.148V7.554a.15.15 0 0 1 .15-.149zm12.131 0h1.949a.15.15 0 0 1 .15.15v8.892a.15.15 0 0 1-.15.148h-1.949a.15.15 0 0 1-.151-.148V7.554a.15.15 0 0 1 .151-.149zM8.92 10.948h2.046c.083 0 .15.066.15.147v5.352a.15.15 0 0 1-.15.148H8.92a.15.15 0 0 1-.152-.148v-5.352a.15.15 0 0 1 .152-.147Z"/></svg>',
|
|
33
37
|
'ollama': '<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M16.361 10.26a.9.9 0 0 0-.558.47l-.072.148l.001.207c0 .193.004.217.059.353c.076.193.152.312.291.448c.24.238.51.3.872.205a.86.86 0 0 0 .517-.436a.75.75 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.1 1.1 0 0 0-.466 0m-9.203.005c-.305.096-.533.32-.65.639a1.2 1.2 0 0 0-.06.52c.057.309.31.59.598.667c.362.095.632.033.872-.205c.14-.136.215-.255.291-.448c.055-.136.059-.16.059-.353l.001-.207l-.072-.148a.9.9 0 0 0-.565-.472a1 1 0 0 0-.474.007m4.184 2c-.131.071-.223.25-.195.383c.031.143.157.288.353.407c.105.063.112.072.117.136c.004.038-.01.146-.029.243c-.02.094-.036.194-.036.222c.002.074.07.195.143.253c.064.052.076.054.255.059c.164.005.198.001.264-.03c.169-.082.212-.234.15-.525c-.052-.243-.042-.28.087-.355c.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48a.4.4 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053l-.053-.032c-.219-.13-.259-.145-.391-.143a.4.4 0 0 0-.193.032m.39-2.195c-.373.036-.475.05-.654.086a4.5 4.5 0 0 0-.951.328c-.94.46-1.589 1.226-1.787 2.114c-.04.176-.045.234-.045.53c0 .294.005.357.043.524c.264 1.16 1.332 2.017 2.714 2.173c.3.033 1.596.033 1.896 0c1.11-.125 2.064-.727 2.493-1.571c.114-.226.169-.372.22-.602c.039-.167.044-.23.044-.523c0-.297-.005-.355-.045-.531c-.288-1.29-1.539-2.304-3.072-2.497a7 7 0 0 0-.855-.031zm.645.937a3.3 3.3 0 0 1 1.44.514c.223.148.537.458.671.662c.166.251.26.508.303.82c.02.143.01.251-.043.482c-.08.345-.332.705-.672.957a3 3 0 0 1-.689.348c-.382.122-.632.144-1.525.138c-.582-.006-.686-.01-.853-.042q-.856-.16-1.35-.68c-.264-.28-.385-.535-.45-.946c-.03-.192.025-.509.137-.776c.136-.326.488-.73.836-.963c.403-.269.934-.46 1.422-.512c.187-.02.586-.02.773-.002m-5.503-11a1.65 1.65 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503c0 .544.064 1.24.155 1.721c.02.107.031.202.023.208l-.187.152a5.3 5.3 0 0 0-.949 1.02a5.5 5.5 0 0 0-.94 2.339a6.6 6.6 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195l-.037.064c-.269.452-.498 1.105-.605 1.732c-.084.496-.095.629-.095 1.294c0 .67.009.803.088 1.266c.095.555.288 1.143.503 1.534c.071.128.243.393.264.407c.007.003-.014.067-.046.141a7.4 7.4 0 0 0-.548 1.873a5 5 0 0 0-.071.991c0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597c.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.9.9 0 0 0-.194-.25a1.7 1.7 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451c.124-.486.329-.918.544-1.154a.8.8 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.14 3.14 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834c.563-.814 1.353-1.336 2.237-1.475c.199-.033.57-.028.776.01c.226.04.367.028.512-.041c.179-.085.268-.19.374-.431c.093-.215.165-.333.36-.576c.234-.29.46-.489.822-.729c.413-.27.884-.467 1.352-.561c.17-.035.25-.04.569-.04s.398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602c.034.057.095.177.132.267c.105.241.195.346.374.43c.14.068.286.082.503.045c.343-.058.607-.053.943.016c1.144.23 2.14 1.173 2.581 2.437c.385 1.108.276 2.267-.296 3.153c-.097.15-.193.27-.333.419c-.301.322-.301.722-.001 1.053c.493.539.801 1.866.708 3.036c-.062.772-.26 1.463-.533 1.854a2 2 0 0 1-.224.258a.9.9 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295c.253 1.008.231 2.01-.059 2.581a1 1 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074l.036-.134c.019-.076.057-.3.088-.516a9 9 0 0 0 0-1.258c-.11-.875-.295-1.57-.597-2.226c-.032-.074-.053-.138-.046-.141a1.4 1.4 0 0 0 .108-.152c.376-.569.607-1.284.724-2.228c.031-.26.031-1.378 0-1.628c-.083-.645-.182-1.082-.348-1.525a6 6 0 0 0-.329-.7l-.038-.064l.131-.194c.402-.604.636-1.262.727-2.04a6.6 6.6 0 0 0-.024-1.358a5.5 5.5 0 0 0-.939-2.339a5.3 5.3 0 0 0-.95-1.02l-.186-.152a.7.7 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503c-.19-.924-.535-1.658-.98-2.082c-.354-.338-.716-.482-1.15-.455c-.996.059-1.8 1.205-2.116 3.01a7 7 0 0 0-.097.726c0 .036-.007.066-.015.066a1 1 0 0 1-.149-.078A4.86 4.86 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a1 1 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a7 7 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2 2 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388c.03.113.06.244.069.292c.007.047.026.152.041.233c.067.365.098.76.102 1.24l.002.475l-.12.175l-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.4 8.4 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711c.067-.05.079-.049.157.013m9.825-.012c.17.126.358.46.498.888c.28.854.36 2.028.212 3.145c-.019.14-.024.151-.057.144l-.238-.06a3.7 3.7 0 0 0-.954-.124h-.278l-.119-.178l-.119-.175l.002-.474c.004-.669.066-1.19.214-1.772c.157-.623.434-1.185.68-1.382c.078-.062.09-.063.159-.012"/></svg>',
|
|
34
38
|
'openai': '<svg fill="#10A37F" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M22.282 9.821a6 6 0 0 0-.516-4.91a6.05 6.05 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a6 6 0 0 0-3.998 2.9a6.05 6.05 0 0 0 .743 7.097a5.98 5.98 0 0 0 .51 4.911a6.05 6.05 0 0 0 6.515 2.9A6 6 0 0 0 13.26 24a6.06 6.06 0 0 0 5.772-4.206a6 6 0 0 0 3.997-2.9a6.06 6.06 0 0 0-.747-7.073M13.26 22.43a4.48 4.48 0 0 1-2.876-1.04l.141-.081l4.779-2.758a.8.8 0 0 0 .392-.681v-6.737l2.02 1.168a.07.07 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494M3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085l4.783 2.759a.77.77 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646M2.34 7.896a4.5 4.5 0 0 1 2.366-1.973V11.6a.77.77 0 0 0 .388.677l5.815 3.354l-2.02 1.168a.08.08 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.08.08 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667m2.01-3.023l-.141-.085l-4.774-2.782a.78.78 0 0 0-.785 0L9.409 9.23V6.897a.07.07 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.8.8 0 0 0-.393.681zm1.097-2.365l2.602-1.5l2.607 1.5v2.999l-2.597 1.5l-2.607-1.5Z"/></svg>',
|
|
35
39
|
'openrouter': '<svg fill="#6469FF" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M16.778 1.844v1.919q-.569-.026-1.138-.032q-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237c-2.911 2.066-2.731 1.95-4.14 2.75c-.396.223-1.342.574-2.185.798c-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799c1.41.798 1.228.683 4.14 2.75c2.126 1.509 4.22 2.11 6.148 2.236c.88.058 1.716.041 2.555.005v1.918l7.222-4.168l-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021c-1.364-.09-2.417-.357-3.979-1.465c-2.244-1.593-2.866-2.027-3.68-2.508c.889-.518 1.449-.906 3.822-2.59c1.56-1.109 2.614-1.377 3.978-1.466c.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z"/></svg>',
|
|
@@ -37,6 +41,7 @@ window.__ModuleLoader__.load({
|
|
|
37
41
|
'relay': '<svg fill="#64748B" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M13 19h1a1 1 0 0 1 1 1h7v2h-7a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1H2v-2h7a1 1 0 0 1 1-1h1v-2H4a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-7zM4 3h16a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1m5 4h1V5H9zm0 8h1v-2H9zM5 5v2h2V5zm0 8v2h2v-2z"/></svg>',
|
|
38
42
|
'siliconflow': '<svg fill="#6E29F6" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M22.956 6.521H12.522c-.577 0-1.044.468-1.044 1.044v3.13c0 .577-.466 1.044-1.043 1.044H1.044c-.577 0-1.044.467-1.044 1.044v4.174C0 17.533.467 18 1.044 18h10.434c.577 0 1.044-.467 1.044-1.043v-3.13c0-.578.466-1.044 1.043-1.044h9.391c.577 0 1.044-.467 1.044-1.044V7.565c0-.576-.467-1.044-1.044-1.044" clip-rule="evenodd"/></svg>',
|
|
39
43
|
'together': '<svg fill="#0F6FFF" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill-rule="evenodd"><path d="M17.385 11.23a4.615 4.615 0 1 0 0-9.23a4.615 4.615 0 0 0 0 9.23m0 10.77a4.615 4.615 0 1 0 0-9.23a4.615 4.615 0 0 0 0 9.23m-10.77 0a4.615 4.615 0 1 0 0-9.23a4.615 4.615 0 0 0 0 9.23" opacity=".2"/><circle cx="6.615" cy="6.615" r="4.615"/></g></svg>',
|
|
44
|
+
'xai': '<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg>',
|
|
40
45
|
'zhipu': '<svg fill="#3859FF" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M11.991 23.503a.24.24 0 0 0-.244.248a.24.24 0 0 0 .244.249a.24.24 0 0 0 .245-.249a.24.24 0 0 0-.22-.247zM9.671 5.365a1.697 1.697 0 0 1 1.099 2.132l-.071.172l-.016.04l-.018.054c-.07.16-.104.32-.104.498c-.035.71.47 1.279 1.186 1.314h.366c1.309.053 2.338 1.173 2.286 2.523c-.052 1.332-1.152 2.38-2.478 2.327h-.174c-.715.018-1.274.64-1.239 1.368c0 .124.018.23.053.337c.209.373.54.658.96.8c.75.23 1.517-.125 1.9-.782l.018-.035c.402-.64 1.17-.96 1.92-.711c.854.284 1.378 1.226 1.099 2.167a1.66 1.66 0 0 1-2.077 1.102a1.7 1.7 0 0 1-.907-.711l-.017-.035c-.2-.323-.463-.58-.851-.711l-.056-.018a1.646 1.646 0 0 0-1.954.746a1.66 1.66 0 0 1-1.065.764a1.677 1.677 0 0 1-1.989-1.279c-.209-.906.332-1.83 1.257-2.043a1.5 1.5 0 0 1 .296-.035h.018c.68-.071 1.151-.622 1.116-1.333a1.3 1.3 0 0 0-.227-.693a2.5 2.5 0 0 1-.366-1.403a2.4 2.4 0 0 1 .366-1.208c.14-.195.21-.444.227-.693c.018-.71-.506-1.261-1.186-1.332l-.07-.018a1.4 1.4 0 0 1-.299-.07l-.05-.019a1.7 1.7 0 0 1-1.047-2.114a1.68 1.68 0 0 1 2.094-1.101m-5.575 10.11c.26-.264.639-.367.994-.27s.633.379.728.74c.095.362-.007.748-.267 1.013c-.402.41-1.053.41-1.455 0a1.06 1.06 0 0 1 0-1.482zm14.845-.294c.359-.09.738.024.992.297c.254.274.344.665.237 1.025s-.396.634-.756.718c-.551.128-1.1-.22-1.23-.781a1.05 1.05 0 0 1 .757-1.26zm-.064-4.39c.314.32.49.753.49 1.206s-.176.886-.49 1.206c-.315.32-.74.5-1.185.5c-.444 0-.87-.18-1.184-.5a1.727 1.727 0 0 1 0-2.412a1.654 1.654 0 0 1 2.369 0m-11.243.163c.364.484.447 1.128.218 1.691a1.665 1.665 0 0 1-2.188.923c-.855-.36-1.26-1.358-.907-2.228a1.68 1.68 0 0 1 1.33-1.038a1.66 1.66 0 0 1 1.547.652m11.545-4.221c.368 0 .708.2.892.524s.184.724 0 1.048a1.03 1.03 0 0 1-.892.524a1.04 1.04 0 0 1-1.03-1.048a1.04 1.04 0 0 1 1.03-1.048m-14.358 0c.368 0 .707.2.891.524s.184.724 0 1.048a1.03 1.03 0 0 1-.891.524a1.04 1.04 0 0 1-1.03-1.048c0-.579.461-1.048 1.03-1.048m10.031-1.475c.925 0 1.675.764 1.675 1.706s-.75 1.705-1.675 1.705s-1.674-.763-1.674-1.705s.75-1.706 1.674-1.706m-2.626-.684c.362-.082.653-.356.761-.718a1.06 1.06 0 0 0-.238-1.028a1.02 1.02 0 0 0-.996-.294c-.547.14-.881.7-.752 1.257c.13.558.675.907 1.225.783m0 16.876c.359-.087.644-.36.75-.72a1.06 1.06 0 0 0-.237-1.019a1.02 1.02 0 0 0-.985-.301a1.04 1.04 0 0 0-.762.717c-.108.361-.017.754.239 1.028c.245.263.606.377.953.305zM17.19 3.5a.63.63 0 0 0 .628-.64a.63.63 0 0 0-.628-.64a.63.63 0 0 0-.628.64c0 .355.28.64.628.64m-10.38 0a.63.63 0 0 0 .628-.64c0-.355-.28-.64-.628-.64a.63.63 0 0 0-.628.64c0 .355.279.64.628.64m-5.182 7.852a.63.63 0 0 0-.628.64c0 .354.28.639.628.639a.63.63 0 0 0 .627-.606l.001-.034a.62.62 0 0 0-.628-.64zm5.182 9.13a.63.63 0 0 0-.628.64c0 .355.279.64.628.64a.63.63 0 0 0 .628-.64c0-.355-.28-.64-.628-.64m10.38.018a.63.63 0 0 0-.628.64c0 .355.28.64.628.64a.63.63 0 0 0 .628-.64a.63.63 0 0 0-.628-.64m5.182-9.148a.63.63 0 0 0-.628.64c0 .354.279.639.628.639a.63.63 0 0 0 .628-.64c0-.355-.28-.64-.628-.64zm-.384-4.992a.24.24 0 0 0 .244-.249a.24.24 0 0 0-.244-.249a.24.24 0 0 0-.244.249c0 .142.122.249.244.249M11.991.497a.24.24 0 0 0 .245-.248A.24.24 0 0 0 11.99 0a.24.24 0 0 0-.244.249c0 .133.108.236.223.247zM2.011 6.36a.24.24 0 0 0 .245-.249a.24.24 0 0 0-.244-.249a.24.24 0 0 0-.244.249a.24.24 0 0 0 .244.249zm0 11.263a.24.24 0 0 0-.243.248a.24.24 0 0 0 .244.249a.24.24 0 0 0 .244-.249a.25.25 0 0 0-.244-.248zm19.995-.018a.24.24 0 0 0-.245.248a.24.24 0 0 0 .245.25a.24.24 0 0 0 .244-.25a.25.25 0 0 0-.244-.248"/></svg>',
|
|
41
46
|
};
|
|
42
47
|
//#endregion
|
|
@@ -48,18 +53,25 @@ window.__ModuleLoader__.load({
|
|
|
48
53
|
moonshot: { name: "Kimi", color: "#000000", cat: "国内" },
|
|
49
54
|
stepfun: { name: "阶跃星辰", color: "#FA520F", cat: "国内" },
|
|
50
55
|
siliconflow: { name: "硅基流动", color: "#6E29F6", cat: "国内" },
|
|
51
|
-
minimax: { name: "MiniMax", color: "#
|
|
56
|
+
minimax: { name: "MiniMax", color: "#E73562", cat: "国内" },
|
|
57
|
+
doubao: { name: "豆包 Seed", color: "#3C8CFF", cat: "国内" },
|
|
58
|
+
hunyuan: { name: "腾讯混元", color: "#0052D9", cat: "国内" },
|
|
52
59
|
openrouter: { name: "OpenRouter", color: "#6469FF", cat: "海外" },
|
|
53
60
|
novita: { name: "Novita", color: "#FA520F", cat: "海外" },
|
|
54
61
|
xai: { name: "xAI Grok", color: "#000000", cat: "海外" },
|
|
62
|
+
openai: { name: "OpenAI", color: "#10A37F", cat: "海外" },
|
|
63
|
+
claude: { name: "Anthropic Claude", color: "#D97757", cat: "海外" },
|
|
64
|
+
gemini: { name: "Google Gemini", color: "#4285F4", cat: "海外" },
|
|
65
|
+
qwen: { name: "通义千问", color: "#623AE7", cat: "国内" },
|
|
66
|
+
mimo: { name: "MiMo", color: "#FF6900", cat: "国内" },
|
|
55
67
|
relay: { name: "中转站", color: "#64748B", cat: "中转站" },
|
|
56
68
|
};
|
|
57
69
|
/** 平台 id → 图标文件名 映射 (服务端 icon 字段即文件名) */
|
|
58
70
|
const ICON_ALIAS = {
|
|
59
|
-
stepfun: "mistral", novita: "together",
|
|
71
|
+
stepfun: "mistral", novita: "together", relay: "relay",
|
|
60
72
|
};
|
|
61
|
-
/** 无官方图标、用文字名代替的平台 */
|
|
62
|
-
const TEXT_ONLY = new Set(["stepfun", "novita", "
|
|
73
|
+
/** 无官方图标、用文字名代替的平台 (v1.2.1: minimax/xai/mimo 已换官方 SVG; hunyuan 暂无官方 SVG 用文字) */
|
|
74
|
+
const TEXT_ONLY = new Set(["stepfun", "novita", "relay"]);
|
|
63
75
|
/** 文字图标: 取平台名的首字符做一个彩色圆圈字 */
|
|
64
76
|
function textIcon(id, meta) {
|
|
65
77
|
const color = meta.color || "#64748B";
|
|
@@ -81,6 +93,53 @@ window.__ModuleLoader__.load({
|
|
|
81
93
|
}
|
|
82
94
|
//#endregion
|
|
83
95
|
|
|
96
|
+
//#region 把手下滑关闭 (v1.2.1)
|
|
97
|
+
/** 把手 = 下滑关闭手势区: pointer 跟手拖拽整个抽屉, 超过阈值或快速下滑 → 播滑出动画后关闭, 否则回弹。
|
|
98
|
+
* 三处复用: 看板抽屉(onClose) / 平台详情(onClose) / 设置面板(onBack || onClose)。 */
|
|
99
|
+
function SwipeHandle({ onClose }) {
|
|
100
|
+
const st = react.useRef({ y0: 0, dy: 0, t0: 0, drawer: null, dragging: false, done: false })
|
|
101
|
+
const setT = (el, dy, anim) => {
|
|
102
|
+
if (!el) return
|
|
103
|
+
el.style.transition = anim ? "transform .24s ease" : "none"
|
|
104
|
+
el.style.transform = dy > 0 ? "translateY(" + dy + "px)" : ""
|
|
105
|
+
}
|
|
106
|
+
const down = (e) => {
|
|
107
|
+
const s = st.current
|
|
108
|
+
if (s.done) return
|
|
109
|
+
s.y0 = e.clientY; s.dy = 0; s.t0 = Date.now(); s.dragging = true
|
|
110
|
+
s.drawer = e.currentTarget.closest(".dshadb_drawer")
|
|
111
|
+
if (s.drawer) s.drawer.style.transition = "none"
|
|
112
|
+
try { e.currentTarget.setPointerCapture(e.pointerId) } catch (err) { /* 无 PointerCapture 时靠事件冒泡兜底 */ }
|
|
113
|
+
}
|
|
114
|
+
const move = (e) => {
|
|
115
|
+
const s = st.current
|
|
116
|
+
if (!s.dragging) return
|
|
117
|
+
s.dy = Math.max(0, e.clientY - s.y0) // 只跟手下滑, 不允许上拉越顶
|
|
118
|
+
setT(s.drawer, s.dy, false)
|
|
119
|
+
}
|
|
120
|
+
const end = () => {
|
|
121
|
+
const s = st.current
|
|
122
|
+
if (!s.dragging || s.done) return
|
|
123
|
+
s.dragging = false
|
|
124
|
+
const dt = Math.max(1, Date.now() - s.t0)
|
|
125
|
+
const fast = s.dy > 24 && s.dy / dt > 0.55 // 快速轻扫也算关闭意图
|
|
126
|
+
if (s.dy > 72 || fast) {
|
|
127
|
+
s.done = true
|
|
128
|
+
if (s.drawer) { // 先播滑出动画再卸载, 避免拖到一半突然消失
|
|
129
|
+
s.drawer.style.transition = "transform .2s ease-in"
|
|
130
|
+
s.drawer.style.transform = "translateY(100%)"
|
|
131
|
+
}
|
|
132
|
+
setTimeout(onClose, 180)
|
|
133
|
+
} else setT(s.drawer, 0, true)
|
|
134
|
+
}
|
|
135
|
+
return react.createElement("div", {
|
|
136
|
+
className: "dshadb_handle", key: "handle", title: "下滑关闭",
|
|
137
|
+
style: { touchAction: "none", cursor: "grab" },
|
|
138
|
+
onPointerDown: down, onPointerMove: move, onPointerUp: end, onPointerCancel: end,
|
|
139
|
+
}, react.createElement("div", { className: "dshadb_handle_bar" }))
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
|
|
84
143
|
//#region 精致 SVG 图标组件
|
|
85
144
|
function IconTitle() {
|
|
86
145
|
return react.createElement("img", { src: "/api-dashboard/icon", alt: "哦鲸鲸", style: { width: "100%", height: "100%", borderRadius: "6px", objectFit: "cover" } });
|
|
@@ -121,25 +180,43 @@ window.__ModuleLoader__.load({
|
|
|
121
180
|
tag.textContent = `/* ===== 动画 ===== */
|
|
122
181
|
@keyframes dshadb-fadein{from{opacity:0}to{opacity:1}}
|
|
123
182
|
@keyframes dshadb-slideup{from{transform:translateY(100%)}to{transform:translateY(0)}}
|
|
124
|
-
@keyframes dshadb-pulse{0%,100%{opacity:1}50%{opacity:0.35}}
|
|
125
183
|
|
|
126
184
|
/* ===== 状态条(顶部小条) ===== */
|
|
127
|
-
.
|
|
185
|
+
/* v1.4.1: 定住高度下限 —— 空态(18px 内容)与有数据态(20px 内容)差 2px, 钱数/平台名一进来
|
|
186
|
+
状态条就从 26px 变 28px, 把上面的会话区顶 2px(真机实测 CLS 0.00094+0.00053)。
|
|
187
|
+
用 min-height 而不是 height: 系统字体放大时仍可自然增高, 不会裁字。
|
|
188
|
+
28px = 有数据态的实测总高(20px 内容 + 4px 上下 padding), 所以有数据时外观完全不变。 */
|
|
189
|
+
.dshadb_bar{display:inline-flex;align-items:center;gap:5px;font-size:11px;min-height:28px;box-sizing:border-box;padding:4px 10px 4px 6px;border-radius:10px;background:#f5f6f8;border:1px solid #e7e8ec;color:#777b84;cursor:pointer;white-space:nowrap;user-select:none;-webkit-tap-highlight-color:transparent}
|
|
128
190
|
.dshadb_bar:active{opacity:0.8}
|
|
129
191
|
.dshadb_bar_logo{width:16px;height:16px;border-radius:5px;background:#eef2ff;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:800;color:#4f7cff}
|
|
130
192
|
.dshadb_bar_name{font-weight:700;color:#17181c;font-size:11px}
|
|
131
193
|
.dshadb_bar_amount{font-weight:800;font-variant-numeric:tabular-nums;color:#17181c}
|
|
194
|
+
/* C-4a (v1.4.1): .dshadb_bar_ok/warn/err 以前只有使用处、没有 CSS 规则(死类名),
|
|
195
|
+
于是"负余额/低余额金额变红"从来没生效过, 只有小圆点变色。 */
|
|
196
|
+
.dshadb_bar_ok{color:#2e9e5b}
|
|
197
|
+
.dshadb_bar_warn{color:#c8891a}
|
|
198
|
+
.dshadb_bar_err{color:#d8443f}
|
|
132
199
|
.dshadb_bar_dot{width:6px;height:6px;border-radius:50%;flex:none}
|
|
133
200
|
.dshadb_bar_dot_ok{background:#35b56b}
|
|
134
201
|
.dshadb_bar_dot_warn{background:#e6a72f}
|
|
135
202
|
.dshadb_bar_dot_err{background:#e05252}
|
|
136
|
-
.dshadb_bar_dot_loading{animation:dshadb-pulse 1.2s ease-in-out infinite;background:#c7c7cc}
|
|
137
203
|
.dshadb_bar_peak{font-size:10px;padding:2px 7px;border-radius:999px;font-weight:700}
|
|
138
204
|
.dshadb_bar_peak.peak{background:rgba(230,167,47,0.12);color:#b07d1a;border:1px solid rgba(230,167,47,0.2)}
|
|
139
205
|
.dshadb_bar_peak.valley{background:rgba(79,124,255,0.10);color:#3d5fcc;border:1px solid rgba(79,124,255,0.18)}
|
|
140
206
|
|
|
141
207
|
/* ===== 遮罩 ===== */
|
|
142
|
-
.dshadb_scrim{position:fixed;inset:0;z-index:99998;background:rgba(0,0,0,0.35);animation:dshadb-fadein .15s ease-out}
|
|
208
|
+
.dshadb_scrim{position:fixed;inset:0;z-index:99998;background:rgba(0,0,0,0.35);animation:dshadb-fadein .15s ease-out;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}
|
|
209
|
+
.dshadb_scrim::-webkit-scrollbar{display:none}
|
|
210
|
+
/* v1.4.0: 手机壳 (dsh-web-mobile) 的「左边缘滑入打开侧边栏」手势层 —— 详见 AGENTS.md ①。
|
|
211
|
+
它在 document **捕获**阶段吃 pointerdown, 起手点落在屏幕左侧 45% 内、横向位移占优时,
|
|
212
|
+
整条手势就被判成「开侧边栏」。我们面板里的横向拖拽 (大肥鱼滑块 / 文本选择 / 横向列表)
|
|
213
|
+
因此会把侧边栏拉出来, 而且它先于我们的监听器执行, 拦不住。
|
|
214
|
+
它留了一条明路: 起手元素若属于「真·横向滚动容器」(overflow-x 为 auto/scroll 且
|
|
215
|
+
scrollWidth > clientWidth + 1), beginStroke 直接放弃识别。
|
|
216
|
+
于是给遮罩补 2px **不可见**的横向溢出, 让整块面板都落在它的让路条件里。
|
|
217
|
+
溢出由 0 高度的 .dshadb_swipeguard 提供; 抽屉自身是 position:fixed,
|
|
218
|
+
所以遮罩滚动不会移动面板 —— 视觉与布局零影响。 */
|
|
219
|
+
.dshadb_swipeguard{display:block;width:calc(100% + 2px);height:0;pointer-events:none}
|
|
143
220
|
|
|
144
221
|
/* ===== 抽屉 ===== */
|
|
145
222
|
.dshadb_drawer{position:fixed;left:0;right:0;bottom:0;z-index:99999;max-height:85vh;background:#f5f6f8;border-radius:20px 20px 0 0;box-shadow:0 -8px 32px rgba(0,0,0,0.12);display:flex;flex-direction:column;animation:dshadb-slideup .22s cubic-bezier(.16,1,.3,1);overflow:hidden}
|
|
@@ -187,17 +264,13 @@ window.__ModuleLoader__.load({
|
|
|
187
264
|
.dshadb_card_status_ok{color:#35b56b}
|
|
188
265
|
.dshadb_card_status_warn{color:#e6a72f}
|
|
189
266
|
.dshadb_card_status_err{color:#e05252}
|
|
190
|
-
.dshadb_card_status_loading{color:#9ca0aa}
|
|
191
267
|
.dshadb_status_dot{width:7px;height:7px;border-radius:50%;flex:none}
|
|
192
268
|
.dshadb_status_dot_ok{background:#35b56b}
|
|
193
269
|
.dshadb_status_dot_warn{background:#e6a72f}
|
|
194
270
|
.dshadb_status_dot_err{background:#e05252}
|
|
195
|
-
.dshadb_status_dot_loading{background:#c7c7cc;animation:dshadb-pulse 1.2s ease-in-out infinite}
|
|
196
271
|
.dshadb_card_chevron{color:#9ca0aa;font-size:18px;margin-left:2px}
|
|
197
|
-
.dshadb_card_error{font-size:11px;color:#e05252;line-height:1.3;text-align:right}
|
|
198
272
|
|
|
199
273
|
/* ===== 底部按钮 ===== */
|
|
200
|
-
.dshadb_add_grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:6px 0 0}
|
|
201
274
|
.dshadb_add_btn{display:flex;align-items:center;justify-content:center;gap:5px;padding:10px;border-radius:12px;background:#ffffff;color:#17181c;font-size:12px;font-weight:700;border:1px solid #e7e8ec;cursor:pointer;font-family:inherit;box-shadow:0 6px 20px rgba(0,0,0,0.03);transition:transform .12s ease,box-shadow .2s ease}
|
|
202
275
|
.dshadb_add_btn:active{transform:scale(0.97);opacity:0.9}
|
|
203
276
|
|
|
@@ -225,14 +298,6 @@ window.__ModuleLoader__.load({
|
|
|
225
298
|
.dshadb_settings_body{padding:4px 14px}
|
|
226
299
|
.dshadb_settings_section{margin-bottom:14px}
|
|
227
300
|
.dshadb_settings_group_title{font-size:11px;color:#777b84;font-weight:750;margin:13px 5px 7px;text-transform:uppercase;letter-spacing:0.4px}
|
|
228
|
-
.dshadb_srow{background:#ffffff;border:1px solid #e7e8ec;min-height:52px;padding:11px 13px;display:flex;align-items:center;gap:10px}
|
|
229
|
-
.dshadb_srow:first-of-type{border-radius:15px 15px 4px 4px}
|
|
230
|
-
.dshadb_srow:last-of-type{border-radius:4px 4px 15px 15px}
|
|
231
|
-
.dshadb_srow:only-of-type{border-radius:15px}
|
|
232
|
-
.dshadb_sicon{width:30px;height:30px;border-radius:10px;background:#f0f2f8;display:flex;align-items:center;justify-content:center;font-size:14px;color:#17181c;flex:none}
|
|
233
|
-
.dshadb_srow_label{font-size:13px;font-weight:700;color:#17181c}
|
|
234
|
-
.dshadb_srow_meta{font-size:10px;color:#777b84;margin-top:2px}
|
|
235
|
-
.dshadb_srow_value{margin-left:auto;color:#777b84;font-size:12px;font-weight:600}
|
|
236
301
|
.dshadb_switch{margin-left:auto;width:42px;height:25px;border-radius:20px;background:#c8cad0;padding:3px;cursor:pointer;transition:background .2s ease;border:none;flex:none}
|
|
237
302
|
.dshadb_switch_on{background:#4f7cff}
|
|
238
303
|
.dshadb_switch_knob{display:block;width:19px;height:19px;background:white;border-radius:50%;transition:transform .18s ease}
|
|
@@ -247,11 +312,19 @@ window.__ModuleLoader__.load({
|
|
|
247
312
|
/* 输入框 */
|
|
248
313
|
.dshadb_field{width:100%;padding:11px 14px;border-radius:12px;border:1px solid #e7e8ec;background:#ffffff;color:#17181c;font-size:13px;font-family:inherit;box-sizing:border-box;margin-bottom:10px;transition:border-color .15s ease}
|
|
249
314
|
.dshadb_field:focus{outline:none;border-color:#4f7cff}
|
|
315
|
+
.dshadb_textarea{resize:vertical;min-height:56px;line-height:1.5;margin-bottom:6px}
|
|
316
|
+
/* provider 自动判定结果标签 */
|
|
317
|
+
.dshadb_kinds{margin:8px 0 4px}
|
|
318
|
+
.dshadb_kinds_title{display:block;font-size:10px;font-weight:700;color:#9ca0aa;margin-bottom:5px}
|
|
319
|
+
.dshadb_kinds_empty{display:block;font-size:10px;color:#9ca0aa;line-height:1.5}
|
|
320
|
+
.dshadb_kinds_list{display:flex;flex-wrap:wrap;gap:5px}
|
|
321
|
+
.dshadb_kind{font-size:10px;font-weight:600;padding:3px 8px;border-radius:999px;border:1px solid;white-space:nowrap}
|
|
322
|
+
.dshadb_kind_official{background:rgba(52,199,89,0.10);border-color:rgba(52,199,89,0.28);color:#2a8c4a}
|
|
323
|
+
.dshadb_kind_relay{background:rgba(142,142,147,0.10);border-color:rgba(142,142,147,0.26);color:#6f737c}
|
|
250
324
|
.dshadb_label{font-size:11px;font-weight:700;color:#777b84;margin-bottom:4px;display:block}
|
|
251
325
|
.dshadb_grid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
252
326
|
.dshadb_save_btn{width:100%;padding:14px;border-radius:15px;background:#17181c;color:#fff;font-size:15px;font-weight:800;border:none;cursor:pointer;font-family:inherit;transition:opacity .1s ease}
|
|
253
327
|
.dshadb_save_btn:active{opacity:0.85}
|
|
254
|
-
.dshadb_danger{color:#e05252}
|
|
255
328
|
|
|
256
329
|
/* ===== 详情页 ===== */
|
|
257
330
|
.dshadb_detail{display:flex;flex-direction:column;gap:12px;padding:4px 2px}
|
|
@@ -267,9 +340,6 @@ window.__ModuleLoader__.load({
|
|
|
267
340
|
.dshadb_detail_metric{display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 8px;border-radius:12px;background:#ffffff;border:1px solid #e7e8ec;animation:dshadb-fadein .25s ease-out}
|
|
268
341
|
.dshadb_detail_metric_val{font-size:16px;font-weight:700;color:#17181c;font-variant-numeric:tabular-nums}
|
|
269
342
|
.dshadb_detail_metric_label{font-size:10px;color:#777b84}
|
|
270
|
-
.dshadb_detail_row{display:flex;align-items:center;justify-content:space-between;padding:7px 0;border-bottom:1px solid #eef0f4;font-size:11px}
|
|
271
|
-
.dshadb_detail_row_label{color:#777b84}
|
|
272
|
-
.dshadb_detail_row_val{color:#17181c;font-weight:600;font-variant-numeric:tabular-nums;text-align:right}
|
|
273
343
|
.dshadb_peakcard{display:flex;flex-direction:column;gap:5px;padding:12px 14px;border-radius:12px;margin-top:7px;border:1px solid;transition:all .2s ease}
|
|
274
344
|
.dshadb_peakcard.peak{background:rgba(230,167,47,0.08);border-color:rgba(230,167,47,0.25)}
|
|
275
345
|
.dshadb_peakcard.valley{background:rgba(79,124,255,0.06);border-color:rgba(79,124,255,0.20)}
|
|
@@ -292,6 +362,33 @@ window.__ModuleLoader__.load({
|
|
|
292
362
|
|
|
293
363
|
/* ===== 补缺失CSS ===== */
|
|
294
364
|
.dshadb_bar_cost{display:inline-flex;align-items:center;margin-left:2px;padding-left:8px;border-left:1px solid #e7e8ec;color:#9ca0aa;font-size:11px;font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
365
|
+
/* ===== v1.4.0 子代理消耗行 =====
|
|
366
|
+
设计约束: 无论几个子代理都**只占一行高度**(约 20px), 绝不换行挤压输入框。
|
|
367
|
+
做法 = 固定 flex-wrap:nowrap + 横向滚动(隐藏滚动条) + 名字截断到 72px。
|
|
368
|
+
完整名字/模型/精确金额 → 长按弹 .dshadb_subtip 浮层。
|
|
369
|
+
⚠️ **别指望 title**: 手机上 title 根本不显示, 长按弹的是系统「选择/复制」菜单
|
|
370
|
+
(实测踩到, 用户反馈「长按只能复制」)。已加 user-select:none +
|
|
371
|
+
-webkit-touch-callout:none 抑制系统菜单, 详情改由 pointerdown 计时器弹浮层,
|
|
372
|
+
见 bindSubagentTip()。浮层必须挂在 document.body 上 —— 挂在胶囊里会被
|
|
373
|
+
.dshadb_subs 的 overflow-x:auto 裁掉。
|
|
374
|
+
⚠️ 本段在 JS 模板字符串里, 注释里**不能出现反引号**(会提前结束模板串)。 */
|
|
375
|
+
/* UI v1.4.1(按维护者反馈改):**不再把两行塞进一个容器** —— 余额条恢复成独立的药丸,
|
|
376
|
+
整块(余额条 + 子代理行)**水平居中**:wrap 用 fit-content + margin:auto,
|
|
377
|
+
子项 align-items:center,于是窄的时候两行都居中,宽的时候 wrap 撑到 100% 子代理行在内部横滑。 */
|
|
378
|
+
.dshadb_barwrap{display:flex;flex-direction:column;align-items:center;gap:0;width:fit-content;max-width:100%;min-width:0;margin:0 auto}
|
|
379
|
+
.dshadb_barrow{display:inline-flex;align-items:center;gap:2px;max-width:100%;min-width:0}
|
|
380
|
+
.dshadb_subs{display:flex;flex-wrap:nowrap;align-items:center;gap:4px;margin:3px 0 0 0;max-width:100%;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch}
|
|
381
|
+
/* UI v1.4.1: 子代理行只在**真的溢出**时右侧渐隐(由 syncSubsOverflow 打类名),
|
|
382
|
+
这样"被切一半"看起来是有意的可滑动提示, 而不是被屏幕齐口截断。 */
|
|
383
|
+
.dshadb_subs_overflow{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 20px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 20px),transparent)}
|
|
384
|
+
.dshadb_subs::-webkit-scrollbar{display:none}
|
|
385
|
+
.dshadb_sub{flex:none;display:inline-flex;align-items:center;gap:4px;font-size:10px;line-height:1.15;padding:2px 7px;border-radius:8px;background:#f5f6f8;border:1px solid #e7e8ec;color:#777b84;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
|
|
386
|
+
.dshadb_sub_dot{width:5px;height:5px;border-radius:50%;background:#8b93a7;flex:none}
|
|
387
|
+
.dshadb_sub_name{font-weight:700;color:#17181c;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:72px}
|
|
388
|
+
.dshadb_sub_amt{font-weight:800;font-variant-numeric:tabular-nums;color:#4f7cff;flex:none}
|
|
389
|
+
/* 子代理长按详情浮层 (v1.4.0): 挂在 document.body 上, 不能被 .dshadb_subs 的
|
|
390
|
+
overflow 裁掉; z-index 要高过抽屉(99999)/遮罩。纯展示, 不接收触摸事件。 */
|
|
391
|
+
.dshadb_subtip{position:fixed;z-index:100000;max-width:min(280px,calc(100vw - 24px));padding:9px 11px;border-radius:12px;background:#17181c;color:#ffffff;font-size:11px;font-weight:600;line-height:1.6;white-space:pre-line;word-break:break-word;box-shadow:0 10px 30px rgba(0,0,0,0.3);pointer-events:none;animation:dshadb-fadein .12s ease-out}
|
|
295
392
|
.dshadb_footer_dot{width:4px;height:4px;border-radius:50%;background:#c9cad0;opacity:0.5}
|
|
296
393
|
.dshadb_group_divider{height:1px;background:#eef0f4;margin:2px 6px 4px}
|
|
297
394
|
.dshadb_icon_btn{display:inline-flex;align-items:center;justify-content:center;min-width:38px;min-height:38px;padding:8px;border-radius:10px;background:transparent;border:none;color:#777b84;cursor:pointer;transition:background .1s ease}
|
|
@@ -310,7 +407,7 @@ window.__ModuleLoader__.load({
|
|
|
310
407
|
.dshadb_wf_sub{display:block;font-size:10px;font-weight:500;color:#9ca0aa;line-height:1.4;margin-top:2px}
|
|
311
408
|
.dshadb_wf_head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}
|
|
312
409
|
.dshadb_wf_val{font-size:11px;font-weight:800;color:#4f7cff;font-variant-numeric:tabular-nums;flex:none}
|
|
313
|
-
.dshadb_wf_range{width:100%;height:
|
|
410
|
+
.dshadb_wf_range{width:100%;height:34px;accent-color:#4f7cff;margin:0}
|
|
314
411
|
.dshadb_wf_seg{display:flex;gap:6px;margin-left:auto;flex:none}
|
|
315
412
|
.dshadb_wf_segbtn{font-size:11px;font-weight:700;font-family:inherit;padding:6px 11px;border-radius:10px;border:1px solid #e7e8ec;background:#f5f6f8;color:#9ca0aa;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s ease,color .15s ease}
|
|
316
413
|
.dshadb_wf_segbtn_on{background:#4f7cff;border-color:#4f7cff;color:#ffffff}
|
|
@@ -322,21 +419,79 @@ window.__ModuleLoader__.load({
|
|
|
322
419
|
.dshadb_wf_name{color:#f2f4f8}
|
|
323
420
|
.dshadb_wf_segbtn{background:#1e222a;border-color:#363c48;color:#8b91a0}
|
|
324
421
|
.dshadb_wf_segbtn_on{background:#4f7cff;border-color:#4f7cff;color:#fff}}
|
|
422
|
+
/* ===== v1.4.0 设置界面控件统一 (此前是浏览器默认外观 + 无深色样式) =====
|
|
423
|
+
注意: 曾把「币种」那几个 <select> 换成插件自己的分段按钮, 但在 2 列网格里
|
|
424
|
+
「人民币」会被截成「人民…」, 观感反而更差 —— 已改回 <select>, 只做外观清理:
|
|
425
|
+
去掉系统箭头/系统上下箭头, 让它长得跟旁边的 .dshadb_field 一致。 */
|
|
426
|
+
.dshadb_field_select{-webkit-appearance:none;appearance:none;background-image:linear-gradient(45deg,transparent 50%,#9ca0aa 50%),linear-gradient(135deg,#9ca0aa 50%,transparent 50%);background-position:calc(100% - 18px) calc(50% - 2px),calc(100% - 13px) calc(50% - 2px);background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:36px}
|
|
427
|
+
/* 数字框去掉系统上下箭头 (手机上会挤掉输入区) */
|
|
428
|
+
.dshadb_field[type=number]{-moz-appearance:textfield;appearance:textfield}
|
|
429
|
+
.dshadb_field[type=number]::-webkit-outer-spin-button,.dshadb_field[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
|
|
430
|
+
/* 滑块: accent-color 只管颜色, 这里补齐轨道/滑块的圆润观感 */
|
|
431
|
+
.dshadb_wf_range{-webkit-appearance:none;appearance:none;background:transparent}
|
|
432
|
+
.dshadb_wf_range::-webkit-slider-runnable-track{height:6px;border-radius:999px;background:#e7e8ec}
|
|
433
|
+
.dshadb_wf_range::-webkit-slider-thumb{-webkit-appearance:none;width:24px;height:24px;margin-top:-9px;border-radius:50%;background:#4f7cff;border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.18)}
|
|
434
|
+
.dshadb_wf_range::-moz-range-track{height:6px;border-radius:999px;background:#e7e8ec}
|
|
435
|
+
.dshadb_wf_range::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:#4f7cff;border:2px solid #fff}
|
|
436
|
+
/* 深色模式: 设置面板 + 状态条 + 子代理行 (此前这些一律硬编码白底, 深色下是白块) */
|
|
437
|
+
@media (prefers-color-scheme:dark){
|
|
438
|
+
.dshadb_field{background:#1e222a;border-color:#363c48;color:#f2f4f8}
|
|
439
|
+
.dshadb_field::placeholder{color:#6b7280}
|
|
440
|
+
.dshadb_field:focus{border-color:#4f7cff}
|
|
441
|
+
.dshadb_field_select{background-image:linear-gradient(45deg,transparent 50%,#8b91a0 50%),linear-gradient(135deg,#8b91a0 50%,transparent 50%)}
|
|
442
|
+
.dshadb_switch{background:#3d434f}
|
|
443
|
+
.dshadb_switch_on{background:#4f7cff}
|
|
444
|
+
.dshadb_switch_knob{background:#f2f4f8}
|
|
445
|
+
.dshadb_tab{background:#262a33;color:#9aa1b0}
|
|
446
|
+
.dshadb_tab_active{background:#4f7cff;color:#fff}
|
|
447
|
+
.dshadb_settings_group_title{color:#8b91a0}
|
|
448
|
+
.dshadb_kinds_title,.dshadb_kinds_empty{color:#7e8494}
|
|
449
|
+
.dshadb_kind{background:#262a33;border-color:#363c48;color:#9aa1b0}
|
|
450
|
+
.dshadb_bar{background:#262a33;border-color:#363c48;color:#9aa1b0}
|
|
451
|
+
.dshadb_bar_name,.dshadb_bar_amount{color:#f2f4f8}
|
|
452
|
+
.dshadb_bar_cost{border-left-color:#363c48;color:#8b91a0}
|
|
453
|
+
.dshadb_subs .dshadb_sub,.dshadb_sub{background:#262a33;border-color:#363c48;color:#9aa1b0}
|
|
454
|
+
.dshadb_sub_name{color:#f2f4f8}
|
|
455
|
+
.dshadb_sub_amt{color:#7d9bff}
|
|
456
|
+
.dshadb_subtip{background:#f2f4f8;color:#17181c;box-shadow:0 10px 30px rgba(0,0,0,0.5)}
|
|
457
|
+
.dshadb_sub_dot{background:#8b93a7}
|
|
458
|
+
.dshadb_wf_range::-webkit-slider-runnable-track{background:#363c48}
|
|
459
|
+
.dshadb_wf_range::-moz-range-track{background:#363c48}
|
|
460
|
+
}
|
|
325
461
|
/* ===== 大肥鱼互动挂件 (v1.1.0) ===== */
|
|
326
|
-
.dshadb-whale{position:fixed;left:0;top:60vh;z-index:9600;--dshw-scale:1;--dshw-size:clamp(96px,calc(min(150px,min(100vw,100vh) * 0.30) * var(--dshw-scale)),420px);width:var(--dshw-size);height:var(--dshw-size);pointer-events:none;-webkit-user-select:none;user-select:none;touch-action:none;transition:transform .45s cubic-bezier(.34,1.4,.64,1),left .3s ease,top .3s ease}
|
|
462
|
+
.dshadb-whale{position:fixed;left:0;top:60vh;z-index:9600;--dshw-scale:1;--dshw-size:clamp(calc(96px * var(--dshw-scale)),calc(min(150px,min(100vw,100vh) * 0.30) * var(--dshw-scale)),420px);width:var(--dshw-size);height:var(--dshw-size);pointer-events:none;-webkit-user-select:none;user-select:none;touch-action:none;transition:transform .45s cubic-bezier(.34,1.4,.64,1),left .3s ease,top .3s ease}
|
|
327
463
|
.dshadb-whale.dshadb-whale-dragging{transition:none}
|
|
328
464
|
.dshadb-whale.dshadb-whale-flip .dshadb-whale-sprite{transform:scaleX(-1)}
|
|
329
465
|
.dshadb-whale-sprite{position:absolute;left:0;top:0;width:100%;height:100%;transition:transform .3s ease}
|
|
330
466
|
.dshadb-whale-body{position:absolute;left:0;top:0;width:100%;height:100%;transform-origin:50% 92%;transition:transform .2s cubic-bezier(.34,1.56,.64,1);pointer-events:auto;cursor:grab;-webkit-tap-highlight-color:transparent}
|
|
467
|
+
/* 手机壳侧滑手势层会给「真·横向滚动容器」让路 (dsh-web-mobile 的 findHorizontalScroller:
|
|
468
|
+
overflow-x 为 auto/scroll 且 scrollWidth > clientWidth + 1 → beginStroke 直接放弃识别)。
|
|
469
|
+
挂件本体此前没有这层守卫: 鱼停在屏幕左侧 45% 以内时(典型 = 左边缘吸附位, 可见区只有 ~32px 宽),
|
|
470
|
+
在它身上往右拖会被判成「左边缘滑入开抽屉」(挂件自己的拖拽同时照常执行, 所以是"拖着鱼把侧边栏拉出来")。
|
|
471
|
+
这层透明抓取层就是那个滚动容器 —— 起手点落在它身上, 手机壳就不再识别。
|
|
472
|
+
⚠️ 必须挂在 .dshadb-whale-body **内部**(不能挂 root 下): 这样 pointerdown 照常冒泡到 body 上
|
|
473
|
+
已有的拖拽处理器, 拖拽逻辑一行不用改。
|
|
474
|
+
⚠️ touch-action:none 必须写在这一层: 滚动容器是浏览器判定可触摸行为的终点, 漏了它横向 pan
|
|
475
|
+
会被这个滚动容器自己抢走(pointercancel) → 鱼直接拖不动。
|
|
476
|
+
⚠️ 别改成给 .dshadb-whale-body 加 overflow —— 那会把它里面 img 的 drop-shadow 裁掉, 鱼会显平。 */
|
|
477
|
+
.dshadb-whale-grab{position:absolute;left:0;top:0;width:100%;height:100%;z-index:1;overflow-x:auto;overflow-y:hidden;touch-action:none;overscroll-behavior:contain;scrollbar-width:none;pointer-events:auto;cursor:grab;background:transparent;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}
|
|
478
|
+
.dshadb-whale-grab::-webkit-scrollbar{display:none;width:0;height:0}
|
|
479
|
+
.dshadb-whale-grab-guard{display:block;width:calc(100% + 2px);height:0;pointer-events:none}
|
|
480
|
+
/* v1.4.0: 我们自己的抽屉/面板开着时锁住挂件触摸 —— 否则它会盖在面板上抢走滑块的拖拽。
|
|
481
|
+
注意只关交互、不隐藏: 调「身体大小/露出比例」时还要看实时预览。 */
|
|
482
|
+
.dshadb-whale-locked .dshadb-whale-body{pointer-events:none;cursor:default}
|
|
331
483
|
.dshadb-whale-press .dshadb-whale-body{transform:scale(1.1,.88);cursor:grabbing}
|
|
484
|
+
/* pointer-events 不是"继承即锁": 抓取层自己写了 auto, body 被锁成 none 时它照样吃事件, 所以必须单独上锁 */
|
|
485
|
+
.dshadb-whale-locked .dshadb-whale-grab{pointer-events:none;cursor:default}
|
|
486
|
+
.dshadb-whale-press .dshadb-whale-grab{cursor:grabbing}
|
|
332
487
|
.dshadb-whale-img{width:100%;height:100%;object-fit:contain;display:block;pointer-events:none;-webkit-user-drag:none;filter:drop-shadow(0 6px 14px rgba(0,0,0,.18))}
|
|
333
|
-
.dshadb-whale-bubble{position:absolute;left:50%;bottom:calc(100%
|
|
488
|
+
.dshadb-whale-bubble{position:absolute;left:50%;bottom:calc(100% + 6px);--dshw-bdx:0px;--dshw-btx:50%;transform:translateX(calc(-50% + var(--dshw-bdx))) translateY(10px) scale(.82);transform-origin:var(--dshw-btx) 100%;opacity:0;pointer-events:none;background:#fff;border-radius:16px;padding:10px 14px;min-width:112px;max-width:min(74vw,248px);box-shadow:0 8px 28px rgba(0,0,0,.16);transition:opacity .2s ease,transform .26s cubic-bezier(.34,1.5,.64,1);z-index:2;text-align:center;box-sizing:border-box}
|
|
334
489
|
.dshadb-whale-bubble::after{content:"";position:absolute;left:var(--dshw-btx);top:100%;transform:translateX(-50%);border:8px solid transparent;border-top-color:#fff}
|
|
335
490
|
.dshadb-whale-bubble-on{opacity:1;transform:translateX(calc(-50% + var(--dshw-bdx))) translateY(0) scale(1)}
|
|
336
491
|
/* 连点时气泡不做淡出淡入的来回, 只跟位置走 —— 避免闪烁 */
|
|
337
492
|
.dshadb-whale-bubble-on.dshadb-whale-bubble-keep{transition:transform .16s ease}
|
|
338
493
|
/* 气泡在上方放不下时改到下方, 尾巴翻到顶边 */
|
|
339
|
-
.dshadb-whale-bubble-below{bottom:auto;top:calc(100%
|
|
494
|
+
.dshadb-whale-bubble-below{bottom:auto;top:calc(100% + 6px);transform-origin:var(--dshw-btx) 0}
|
|
340
495
|
.dshadb-whale-bubble-below::after{top:auto;bottom:100%;border-top-color:transparent;border-bottom-color:#fff}
|
|
341
496
|
/* 镜像只作用于鲸鱼本体, 气泡与文字保持正向 */
|
|
342
497
|
.dshadb-whale-flip .dshadb-whale-bubble{transform:translateX(calc(-50% + var(--dshw-bdx))) translateY(10px) scale(.82)}
|
|
@@ -353,12 +508,6 @@ window.__ModuleLoader__.load({
|
|
|
353
508
|
.dshadb-whale-l-a{color:#d3d7e0}.dshadb-whale-l-b{color:#f2f4f8}.dshadb-whale-l-c{color:#9298a6}}`
|
|
354
509
|
|
|
355
510
|
document.head.appendChild(tag);
|
|
356
|
-
/* 渐进增强: 高端机 (逻辑核 >= 6) 才启用毛玻璃等特效 */
|
|
357
|
-
try {
|
|
358
|
-
if (!navigator.hardwareConcurrency || navigator.hardwareConcurrency >= 6) {
|
|
359
|
-
document.documentElement.classList.add("dshadb-high-end");
|
|
360
|
-
}
|
|
361
|
-
} catch (e) { /* ignore */ }
|
|
362
511
|
}
|
|
363
512
|
//#endregion
|
|
364
513
|
|
|
@@ -374,9 +523,10 @@ window.__ModuleLoader__.load({
|
|
|
374
523
|
"detail.total": "当前余额", "detail.topup": "总充值", "detail.used": "总使用", "detail.note": "类型",
|
|
375
524
|
"detail.sessionCost": "本会话消耗", "detail.noBalance": "该平台未开放余额查询",
|
|
376
525
|
"settings.safe": "安全阈值(绿)", "settings.warn": "预警阈值(黄)", "settings.currency": "计价货币",
|
|
526
|
+
"settings.overseasCurrency": "海外模型计价", "settings.overseasFollow": "跟随主货币", "settings.overseasHint": "海外厂商官方价本就是美元, 选美元可免去 ×7 折算误差",
|
|
377
527
|
"settings.safeHint": "余额高于此值显示绿色(安全)",
|
|
378
528
|
"settings.warnHint": "余额低于此值显示黄色(偏低),再低显示红",
|
|
379
|
-
"settings.refresh": "刷新间隔(秒)", "settings.refreshHint": "
|
|
529
|
+
"settings.refresh": "刷新间隔(秒)", "settings.refreshHint": "1~60 秒,最高一分钟",
|
|
380
530
|
"settings.save": "保存并生效", "settings.saved": "已保存",
|
|
381
531
|
"update.title": "版本与更新", "update.current": "当前版本",
|
|
382
532
|
"update.check": "检查更新", "update.checking": "检查中…",
|
|
@@ -396,6 +546,16 @@ window.__ModuleLoader__.load({
|
|
|
396
546
|
"whale.peak.default": "默认", "whale.peak.liangwen": "梁文峰谷", "whale.peak.qiangqiang": "!?强强?!",
|
|
397
547
|
"whale.tip": "拖着大肥鱼可以在屏幕上随意移动;点一下弹全身和台词,再点一下收回。",
|
|
398
548
|
"whale.offTip": "先打开上面的「收养大肥鱼」,才能调下面这些。",
|
|
549
|
+
"brands": "模型品牌",
|
|
550
|
+
"settings.showBrands": "显示模型品牌", "settings.showBrandsHint": "在看板中显示 OpenAI / Claude / Gemini / Qwen / 豆包 / 混元 / MiMo 等无余额接口的品牌",
|
|
551
|
+
"settings.official": "官方直连 provider",
|
|
552
|
+
"settings.officialHint": "逗号或换行分隔。写在这里的 provider 名一律按官方直连处理,状态条显示官方余额;留空则自动判定(按 settings.yaml 里 baseURL 的域名,识别不出按中转站显示「—」)",
|
|
553
|
+
"settings.officialAuto": "自动判定结果",
|
|
554
|
+
"settings.officialKindOfficial": "官方", "settings.officialKindRelay": "中转",
|
|
555
|
+
"settings.officialAutoEmpty": "settings.yaml 里没有写 baseURL 的 provider,无法自动判定",
|
|
556
|
+
"settings.dshProviders": "来自 DSH 的中转站(自动)",
|
|
557
|
+
"settings.dshProvidersHint": "直接读 settings.yaml 的 provider 列表,默认全部自动查询余额。用右侧开关单独关掉某个;关过的不再自动打开。官方直连与没写 baseURL 的 provider 不在此列。",
|
|
558
|
+
"settings.dshNoBaseUrl": "没写 baseURL(不表态)",
|
|
399
559
|
"settings.modelName": "模型名称", "settings.apiUrl": "余额接口 URL", "settings.queryType": "解析方式",
|
|
400
560
|
"settings.totalPath": "总余额字段(可选)", "settings.usedPath": "已用字段(可选)",
|
|
401
561
|
"settings.modelHint": "填你的余额接口(返回 JSON),可选填 API Key 与字段路径,如 data.balance",
|
|
@@ -421,9 +581,10 @@ window.__ModuleLoader__.load({
|
|
|
421
581
|
"detail.total": "Balance", "detail.topup": "Top-up", "detail.used": "Used", "detail.note": "Type",
|
|
422
582
|
"detail.sessionCost": "Session cost", "detail.noBalance": "No balance API",
|
|
423
583
|
"settings.safe": "Safe (green)", "settings.warn": "Warn (yellow)", "settings.currency": "Currency",
|
|
584
|
+
"settings.overseasCurrency": "Overseas models", "settings.overseasFollow": "Follow main", "settings.overseasHint": "Overseas vendors price in USD; picking USD avoids the x7 conversion error",
|
|
424
585
|
"settings.safeHint": "Above this = green (safe)",
|
|
425
586
|
"settings.warnHint": "Below this = yellow, lower = red",
|
|
426
|
-
"settings.refresh": "Refresh (sec)", "settings.refreshHint": "
|
|
587
|
+
"settings.refresh": "Refresh (sec)", "settings.refreshHint": "1-60s, max one minute",
|
|
427
588
|
"settings.save": "Save", "settings.saved": "Saved",
|
|
428
589
|
"update.title": "Version & Updates", "update.current": "Current",
|
|
429
590
|
"update.check": "Check updates", "update.checking": "Checking…",
|
|
@@ -443,6 +604,16 @@ window.__ModuleLoader__.load({
|
|
|
443
604
|
"whale.peak.default": "Default", "whale.peak.liangwen": "Liangwen", "whale.peak.qiangqiang": "!?Qiang?!",
|
|
444
605
|
"whale.tip": "Drag the whale anywhere on screen; tap once for full body and a line, tap again to tuck it back.",
|
|
445
606
|
"whale.offTip": "Turn on \"Adopt Big Whale\" above to edit these.",
|
|
607
|
+
"brands": "Model Brands",
|
|
608
|
+
"settings.showBrands": "Show model brands", "settings.showBrandsHint": "Show OpenAI / Claude / Gemini / Qwen / Doubao / Hunyuan / MiMo etc. (no balance API) in dashboard",
|
|
609
|
+
"settings.official": "Official-direct providers",
|
|
610
|
+
"settings.officialHint": "Comma or newline separated. Providers listed here always count as official direct connections and show their official balance; leave empty to auto-detect from the baseURL host in settings.yaml (undetected ones are treated as relays and show \"—\")",
|
|
611
|
+
"settings.officialAuto": "Auto-detected",
|
|
612
|
+
"settings.officialKindOfficial": "official", "settings.officialKindRelay": "relay",
|
|
613
|
+
"settings.officialAutoEmpty": "No provider in settings.yaml declares a baseURL, so nothing can be auto-detected",
|
|
614
|
+
"settings.dshProviders": "Relays from DSH (automatic)",
|
|
615
|
+
"settings.dshProvidersHint": "Reads the provider list straight from settings.yaml and queries balances automatically. Use the switch to turn one off; switched-off ones stay off. Official-direct providers and ones without a baseURL are not listed here.",
|
|
616
|
+
"settings.dshNoBaseUrl": "no baseURL (stays neutral)",
|
|
446
617
|
"settings.modelName": "Model name", "settings.apiUrl": "Balance API URL", "settings.queryType": "Parse type",
|
|
447
618
|
"settings.totalPath": "Total field (opt)", "settings.usedPath": "Used field (opt)",
|
|
448
619
|
"settings.modelHint": "A JSON endpoint for your balance; optional API key & field paths like data.balance",
|
|
@@ -465,24 +636,71 @@ window.__ModuleLoader__.load({
|
|
|
465
636
|
let snapshot = { status: "loading", balances: [], config: { safeThreshold: 50, warnThreshold: 10 } };
|
|
466
637
|
const listeners = new Set();
|
|
467
638
|
let timer = null, pollMs = DEFAULT_POLL_MS, inflight = null, started = false;
|
|
639
|
+
let lastForceAt = 0, seq = 0, appliedSeq = 0;
|
|
468
640
|
function notify() { for (const fn of [...listeners]) fn(); }
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
641
|
+
/**
|
|
642
|
+
* @param force 显式强刷 (手动刷新按钮 / 保存设置后) —— **阻塞**, 等服务端拉完最新数据
|
|
643
|
+
* @param peek v1.4.0 stale-while-revalidate: 首屏 / 切回前台用 —— 服务端有缓存就**立刻**回,
|
|
644
|
+
* 刷新丢后台。force 那条路要等服务端跑完一次全量轮询(最慢端点可到 8s),
|
|
645
|
+
* 应用切回前台时用它就会「等一段时间」。
|
|
646
|
+
*/
|
|
647
|
+
/**
|
|
648
|
+
* C-3a (v1.4.1): fetch 必须带超时。
|
|
649
|
+
* 以前整份客户端一个 AbortController 都没有 —— 请求一旦既不 resolve 也不 reject
|
|
650
|
+
* (弱网黑洞、WebView 被冻结、服务端被最慢端点拖住), inflight 永远不为 null,
|
|
651
|
+
* schedule() 又只在 promise settle 后才续期 → 插件永久停在 4 条骨架屏, 手动刷新也救不回来。
|
|
652
|
+
*/
|
|
653
|
+
const FETCH_TIMEOUT_MS = 15000;
|
|
654
|
+
/**
|
|
655
|
+
* ⚠️ 余额端点的超时**必须远大于**其他端点。
|
|
656
|
+
* 实测(维护者真实配置:5 个中转站 + 16 个预设平台):服务端一次全量刷新要 **8~13.6 秒** ——
|
|
657
|
+
* `queryCustomRelay` 的 auto 探测是**串行**试 3 个候选端点(billing/subscription → api/user/self
|
|
658
|
+
* → credit_grants),每个都要跑到 `timeoutMs`(默认 8s) 才放弃;冷启动还要叠加 DNS/TLS 首次握手。
|
|
659
|
+
* v1.4.1 第一版把这里也写成 15s,结果**冷启动首屏必超时** → 面板直接显示「余额接口请求失败」。
|
|
660
|
+
* 超时的意义只是"别让一个彻底卡死的 socket 永远挂着",不是给正常慢启动设上限。
|
|
661
|
+
*/
|
|
662
|
+
const BALANCES_TIMEOUT_MS = 90000;
|
|
663
|
+
const fetchT = (url, opts, ms) => {
|
|
664
|
+
const timeout = typeof ms === "number" ? ms : FETCH_TIMEOUT_MS;
|
|
665
|
+
try { return fetch(url, { ...(opts || {}), signal: AbortSignal.timeout(timeout) }); }
|
|
666
|
+
catch (e) { return fetch(url, opts); }
|
|
667
|
+
};
|
|
668
|
+
async function refresh(force = false, peek = false) {
|
|
669
|
+
if (inflight !== null && !force && !peek) return inflight;
|
|
670
|
+
// C-4c (v1.4.1): 强刷节流 2s(AGENTS 里写了「强刷 2 秒节流」, 但代码里一直没有);
|
|
671
|
+
// 并且在飞时直接复用同一个请求, 避免连点刷新打出一串并发。
|
|
672
|
+
if (force) {
|
|
673
|
+
const now = Date.now();
|
|
674
|
+
if (inflight !== null && now - lastForceAt < 2000) return inflight;
|
|
675
|
+
lastForceAt = now;
|
|
676
|
+
}
|
|
677
|
+
if ((force || peek) && snapshot.isRefreshing !== true) { snapshot = { ...snapshot, isRefreshing: true }; notify(); }
|
|
472
678
|
inflight = (async () => {
|
|
473
679
|
try {
|
|
474
|
-
const url =
|
|
475
|
-
|
|
476
|
-
|
|
680
|
+
const url = peek ? "/api-dashboard/balances?stale=1"
|
|
681
|
+
: force ? "/api-dashboard/balances?force=1&_t=" + Date.now()
|
|
682
|
+
: "/api-dashboard/balances";
|
|
683
|
+
// v0.5.0: 走浏览器默认缓存 (配合服务端 ETag), 数据没变时 304 空响应; force 用独立 URL + no-store 强刷
|
|
684
|
+
const mySeq = ++seq;
|
|
685
|
+
const res = await fetchT(url, { headers: { accept: "application/json" }, cache: force ? "no-store" : "default" }, BALANCES_TIMEOUT_MS);
|
|
477
686
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
478
687
|
const data = await res.json();
|
|
479
|
-
|
|
688
|
+
// C-4c: 只接受最新一次请求的结果 —— 否则「新请求先回、旧请求后回」会把界面数据倒退回去
|
|
689
|
+
if (mySeq <= appliedSeq) { inflight = null; return; }
|
|
690
|
+
appliedSeq = mySeq;
|
|
691
|
+
if (data.config?.clientPollIntervalMs >= 1000) pollMs = Math.min(data.config.clientPollIntervalMs, 3600000);
|
|
480
692
|
// v0.5.0 性能适配: 内容无变化就不重建快照引用, useSyncExternalStore 判定 Object.is 相同直接跳过整棵树重渲
|
|
481
693
|
const newBalances = data.balances || [];
|
|
482
694
|
const oldBalances = Array.isArray(snapshot.balances) ? snapshot.balances : [];
|
|
483
695
|
const changed = JSON.stringify(newBalances) !== JSON.stringify(oldBalances) || snapshot.status !== "ok";
|
|
484
696
|
const nextFetchedAt = data.fetchedAt || Date.now();
|
|
485
|
-
|
|
697
|
+
// v1.4.1: 服务端冷启动不再阻塞首屏 —— 立刻回 `loading:true` + 空列表, 刷新丢后台。
|
|
698
|
+
// 这里保持「加载中」状态(骨架屏), 并把轮询临时压到 1.5 秒, 数据一到就上屏。
|
|
699
|
+
// 注意这不是假数据: 显示的是"加载中", 不是 0 元。
|
|
700
|
+
if (data.loading === true) {
|
|
701
|
+
snapshot = { ...snapshot, status: "loading", balances: [], config: data.config || snapshot.config, fetchedAt: nextFetchedAt, isRefreshing: false };
|
|
702
|
+
pollMs = Math.min(pollMs, 1500);
|
|
703
|
+
} else if (!changed) {
|
|
486
704
|
// v1.1.0: config 必须始终跟随最新响应 —— 旧写法在余额未变时保留旧 config,
|
|
487
705
|
// 导致设置里改完开关(如 whaleEnabled)后前端永远读不到新值, 表现为"开了又变回关"
|
|
488
706
|
snapshot = { ...snapshot, config: data.config || snapshot.config, fetchedAt: nextFetchedAt, isRefreshing: false };
|
|
@@ -490,7 +708,7 @@ window.__ModuleLoader__.load({
|
|
|
490
708
|
snapshot = { status: "ok", balances: newBalances, config: data.config || snapshot.config, fetchedAt: nextFetchedAt, isRefreshing: false };
|
|
491
709
|
}
|
|
492
710
|
} catch (error) {
|
|
493
|
-
snapshot = { status: "error", balances: snapshot.balances || [], message: error instanceof Error ? error.message : String(error), isRefreshing: false };
|
|
711
|
+
snapshot = { ...snapshot, status: "error", balances: snapshot.balances || [], message: error instanceof Error ? error.message : String(error), isRefreshing: false };
|
|
494
712
|
}
|
|
495
713
|
inflight = null; notify();
|
|
496
714
|
})();
|
|
@@ -501,7 +719,7 @@ window.__ModuleLoader__.load({
|
|
|
501
719
|
timer = setTimeout(() => { timer = null; if (document.hidden) return; refresh().then(schedule, schedule); }, pollMs);
|
|
502
720
|
}
|
|
503
721
|
const store = {
|
|
504
|
-
subscribe(fn) { listeners.add(fn); if (!started) { started = true; refresh(true).then(schedule, schedule); } return () => { listeners.delete(fn); if (listeners.size === 0) { started = false; if (timer !== null) { clearTimeout(timer); timer = null; } } }; },
|
|
722
|
+
subscribe(fn) { listeners.add(fn); if (!started) { started = true; refresh(false, true).then(schedule, schedule); } return () => { listeners.delete(fn); if (listeners.size === 0) { started = false; if (timer !== null) { clearTimeout(timer); timer = null; } } }; },
|
|
505
723
|
getSnapshot() { return snapshot; },
|
|
506
724
|
forceRefresh() { return refresh(true); },
|
|
507
725
|
};
|
|
@@ -510,17 +728,170 @@ window.__ModuleLoader__.load({
|
|
|
510
728
|
//#region helpers
|
|
511
729
|
const CURRENCY_SYMBOLS = { CNY: "¥", USD: "$", EUR: "€" };
|
|
512
730
|
const currencySymbol = (c) => { if (c === "%" || c === "tokens") return ""; return CURRENCY_SYMBOLS[c] || c + " "; };
|
|
731
|
+
/**
|
|
732
|
+
* v1.3.2: 会话消耗格式化 —— 海外模型可独立走 USD, 一个会话可能同时有两种货币。
|
|
733
|
+
* 不做汇率折算合并 (折算=再引入 ×7 误差, 正是用户「¥1285 被看成 $1285」的成因),
|
|
734
|
+
* 改为两段拼接: ~¥3.40+$12.50。主货币排在前。
|
|
735
|
+
* 返回 { text, title, mixed }; 无数据返回 null。
|
|
736
|
+
*/
|
|
737
|
+
function formatSessionCost(cost) {
|
|
738
|
+
if (!cost || typeof cost.cost !== "number" || cost.waiting === true) return null;
|
|
739
|
+
const mainCur = cost.currency || "CNY";
|
|
740
|
+
const symOf = (c) => (c === "CNY" ? "¥" : c === "USD" ? "$" : c + " ");
|
|
741
|
+
const fixOf = (a) => (a >= 1 ? 2 : a >= 0.01 ? 3 : a >= 0.001 ? 4 : a >= 0.0001 ? 5 : 6);
|
|
742
|
+
const byCur = cost.costByCurrency && typeof cost.costByCurrency === "object" ? cost.costByCurrency : null;
|
|
743
|
+
let segs = [];
|
|
744
|
+
if (byCur) {
|
|
745
|
+
segs = Object.keys(byCur)
|
|
746
|
+
.filter((k) => typeof byCur[k] === "number" && byCur[k] > 0)
|
|
747
|
+
.sort((a, b) => (a === mainCur ? -1 : b === mainCur ? 1 : byCur[b] - byCur[a]))
|
|
748
|
+
.map((k) => ({ cur: k, amount: byCur[k] }));
|
|
749
|
+
}
|
|
750
|
+
// 旧 bundle / 未开启独立币种 / 全零 → 退回单段, 行为同 v1.2.6
|
|
751
|
+
if (segs.length === 0) segs = [{ cur: mainCur, amount: cost.cost }];
|
|
752
|
+
const mixed = segs.length > 1;
|
|
753
|
+
// hasValue: 是否真有非零金额。不能用 cost.cost > 0 判断 —— 开启海外独立币种后,
|
|
754
|
+
// 纯 claude 会话的主货币段就是 0, 金额全在 USD 段里 (详情页会误显示成「—」)。
|
|
755
|
+
const hasValue = segs.some((x) => x.amount > 0);
|
|
756
|
+
return {
|
|
757
|
+
text: segs.map((x) => symOf(x.cur) + x.amount.toFixed(fixOf(x.amount))).join("+"),
|
|
758
|
+
title: segs.map((x) => symOf(x.cur) + x.amount.toFixed(6)).join(" + ") + (mixed ? " (两种货币, 未按汇率合并)" : ""),
|
|
759
|
+
mixed: mixed,
|
|
760
|
+
hasValue: hasValue,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/* ===== v1.4.0 子代理胶囊 · 长按详情浮层 =====
|
|
764
|
+
* 为什么不用 title: **手机上 title 根本不显示**, 长按弹的是系统「选择/复制」菜单
|
|
765
|
+
* (用户实测反馈「长按只能复制」)。所以自己用 pointerdown 计时器弹浮层, 并靠 CSS 的
|
|
766
|
+
* user-select:none + -webkit-touch-callout:none 把系统菜单压掉。
|
|
767
|
+
* 浮层必须挂 document.body —— 挂在胶囊里会被 .dshadb_subs 的 overflow-x:auto 裁掉。
|
|
768
|
+
*/
|
|
769
|
+
const SUBTIP_LONG_PRESS_MS = 420;
|
|
770
|
+
let subTipNode = null;
|
|
771
|
+
let subTipTimer = 0;
|
|
772
|
+
let subTipDismissBound = false;
|
|
773
|
+
function hideSubagentTip() {
|
|
774
|
+
if (subTipTimer) { clearTimeout(subTipTimer); subTipTimer = 0; }
|
|
775
|
+
if (subTipNode && subTipNode.parentNode) subTipNode.parentNode.removeChild(subTipNode);
|
|
776
|
+
subTipNode = null;
|
|
777
|
+
}
|
|
778
|
+
/** 浮层摆在胶囊上方; 上方放不下翻到下方; 左右夹在视口内。 */
|
|
779
|
+
function showSubagentTip(anchor, text) {
|
|
780
|
+
hideSubagentTip();
|
|
781
|
+
if (!anchor || !text) return;
|
|
782
|
+
const el = document.createElement("div");
|
|
783
|
+
el.className = "dshadb_subtip";
|
|
784
|
+
el.textContent = text; // 纯文本, 不走 innerHTML (label 可能来自用户/模型)
|
|
785
|
+
document.body.appendChild(el);
|
|
786
|
+
subTipNode = el;
|
|
787
|
+
const rect = anchor.getBoundingClientRect ? anchor.getBoundingClientRect() : null;
|
|
788
|
+
const vw = window.innerWidth || 360;
|
|
789
|
+
const vh = window.innerHeight || 640;
|
|
790
|
+
const w = el.offsetWidth || Math.min(240, vw - 24);
|
|
791
|
+
const h = el.offsetHeight || 60;
|
|
792
|
+
let top = rect ? rect.top - h - 8 : 16;
|
|
793
|
+
if (top < 8) top = rect ? Math.min(vh - h - 8, rect.bottom + 8) : 8;
|
|
794
|
+
const left = rect ? rect.left : 12;
|
|
795
|
+
el.style.left = Math.max(8, Math.min(vw - w - 8, left)) + "px";
|
|
796
|
+
el.style.top = Math.max(8, top) + "px";
|
|
797
|
+
subTipTimer = setTimeout(hideSubagentTip, 5000);
|
|
798
|
+
}
|
|
799
|
+
function bindSubTipDismiss() {
|
|
800
|
+
if (subTipDismissBound) return;
|
|
801
|
+
subTipDismissBound = true;
|
|
802
|
+
// 点别处 / 滚动 / 转屏都收起。capture 阶段先于胶囊自己的 pointerdown 跑,
|
|
803
|
+
// 所以「已开着一个浮层时再长按另一个胶囊」不会被立刻收掉。
|
|
804
|
+
document.addEventListener("pointerdown", hideSubagentTip, true);
|
|
805
|
+
window.addEventListener("scroll", hideSubagentTip, true);
|
|
806
|
+
window.addEventListener("resize", hideSubagentTip);
|
|
807
|
+
}
|
|
808
|
+
/** 给一个胶囊挂长按: 位移 > 8px(其实是在滑动这一行) 或抬手都取消; 长按抬手后的 click 吞掉。 */
|
|
809
|
+
function bindSubagentTip(node, text) {
|
|
810
|
+
// C-4b (v1.4.1): ref 是内联箭头函数 → 每次 render 都重新调用本函数,
|
|
811
|
+
// 而这里挂 7 个 addEventListener 却从不 remove → 5 秒轮询下每小时约 5000 个监听器
|
|
812
|
+
// 堆在同一个胶囊节点上。改成"同一个节点只挂一次", tip 存在节点上按需读。
|
|
813
|
+
node.__dshadbTip = text;
|
|
814
|
+
if (node.__dshadbTipBound === true) return;
|
|
815
|
+
node.__dshadbTipBound = true;
|
|
816
|
+
let timer = 0, sx = 0, sy = 0, fired = false;
|
|
817
|
+
const cancel = () => { if (timer) { clearTimeout(timer); timer = 0; } };
|
|
818
|
+
node.addEventListener("pointerdown", (e) => {
|
|
819
|
+
bindSubTipDismiss();
|
|
820
|
+
fired = false;
|
|
821
|
+
sx = e.clientX; sy = e.clientY;
|
|
822
|
+
cancel();
|
|
823
|
+
timer = setTimeout(() => { timer = 0; fired = true; showSubagentTip(node, node.__dshadbTip || text); }, SUBTIP_LONG_PRESS_MS);
|
|
824
|
+
});
|
|
825
|
+
node.addEventListener("pointermove", (e) => {
|
|
826
|
+
if (timer && (Math.abs(e.clientX - sx) > 8 || Math.abs(e.clientY - sy) > 8)) cancel();
|
|
827
|
+
});
|
|
828
|
+
node.addEventListener("pointerup", cancel);
|
|
829
|
+
node.addEventListener("pointercancel", cancel);
|
|
830
|
+
node.addEventListener("pointerleave", cancel);
|
|
831
|
+
// Android WebView 有时仍会弹系统「复制」长按菜单, 再压一道
|
|
832
|
+
node.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
833
|
+
node.addEventListener("click", (e) => {
|
|
834
|
+
if (fired) { fired = false; e.preventDefault(); e.stopPropagation(); }
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* v1.4.0: 子代理消耗行节点 (换行显示在状态条下方, 左→右 = 服务端给的创建顺序)。
|
|
839
|
+
* 抽成独立函数便于单测 —— 挂载点里的内联 JSX 测不到。
|
|
840
|
+
* 完整详情同时写进 `data-tip`(长按浮层读它, 也是单测抓手) 与 `title`(桌面 hover)。
|
|
841
|
+
* @param cost 会话消耗投影视图 (取其 subagents 与主币种)
|
|
842
|
+
* @returns 节点, 没有子代理时返回 null
|
|
843
|
+
*/
|
|
844
|
+
function buildSubagentRow(cost) {
|
|
845
|
+
const list = Array.isArray(cost && cost.subagents) ? cost.subagents : [];
|
|
846
|
+
if (list.length === 0) return null;
|
|
847
|
+
const mainCur = (cost && cost.currency) || "CNY";
|
|
848
|
+
// UI v1.4.1: 只在真的横向溢出时才加渐隐类名, 避免内容没满也糊一条边
|
|
849
|
+
const syncSubsOverflow = (n) => {
|
|
850
|
+
if (!n) return;
|
|
851
|
+
try {
|
|
852
|
+
const over = n.scrollWidth > n.clientWidth + 1;
|
|
853
|
+
if (over) n.classList.add("dshadb_subs_overflow");
|
|
854
|
+
else n.classList.remove("dshadb_subs_overflow");
|
|
855
|
+
} catch (e) { /* 忽略 */ }
|
|
856
|
+
};
|
|
857
|
+
return react.createElement("span", { className: "dshadb_subs", key: "subs",
|
|
858
|
+
ref: (n) => { if (n) requestAnimationFrame(() => syncSubsOverflow(n)); },
|
|
859
|
+
onScroll: (e) => syncSubsOverflow(e.currentTarget) },
|
|
860
|
+
list.map((s, i) => {
|
|
861
|
+
const f = formatSessionCost({ cost: s.cost, currency: mainCur, costByCurrency: s.costByCurrency, waiting: false });
|
|
862
|
+
const amt = f !== null && f.hasValue ? "~" + f.text : "~—";
|
|
863
|
+
const kind = s.mode === "continuable" ? "可续聊子代理" : "一次性子代理";
|
|
864
|
+
const models = (s.models || []).join(", ");
|
|
865
|
+
const tip = [
|
|
866
|
+
kind + " " + (s.label || "子代理"),
|
|
867
|
+
"消耗 " + (f !== null ? f.title : "—"),
|
|
868
|
+
models ? "模型 " + models : "",
|
|
869
|
+
s.id ? "ID " + String(s.id).slice(0, 8) : "",
|
|
870
|
+
].filter(Boolean).join("\n");
|
|
871
|
+
return react.createElement("span", {
|
|
872
|
+
className: "dshadb_sub", key: s.id || ("s" + i), title: tip, "data-tip": tip,
|
|
873
|
+
ref: (n) => { if (n) bindSubagentTip(n, tip); },
|
|
874
|
+
}, [
|
|
875
|
+
react.createElement("span", { className: "dshadb_sub_dot", key: "d" }),
|
|
876
|
+
react.createElement("span", { className: "dshadb_sub_name", key: "n" }, s.label || "子代理"),
|
|
877
|
+
react.createElement("span", { className: "dshadb_sub_amt", key: "a" }, amt),
|
|
878
|
+
]);
|
|
879
|
+
}));
|
|
880
|
+
}
|
|
513
881
|
function formatMoney(amount, currency) {
|
|
514
882
|
if (typeof amount !== "number" || isNaN(amount)) return currencySymbol(currency) + "0";
|
|
515
883
|
if (currency === "%") return Math.round(amount) + "%";
|
|
516
884
|
if (currency === "tokens") return (amount >= 1e6 ? (amount/1e6).toFixed(1)+"M" : amount >= 1e3 ? (amount/1e3).toFixed(1)+"K" : Math.round(amount) + " tok");
|
|
517
|
-
const
|
|
885
|
+
const abs = Math.abs(amount);
|
|
886
|
+
const fixed = abs === 0 || abs >= 1 ? 2 : abs >= 0.01 ? 3 : 4;
|
|
518
887
|
return currencySymbol(currency) + amount.toFixed(fixed);
|
|
519
888
|
}
|
|
520
889
|
function formatTime(ms) { if (!ms || ms <= 0) return "—"; return new Date(ms).toLocaleTimeString(); }
|
|
521
|
-
/** 状态条金额文字 (供 BarReadout 的 rAF 节流 state 使用)
|
|
522
|
-
|
|
890
|
+
/** 状态条金额文字 (供 BarReadout 的 rAF 节流 state 使用)
|
|
891
|
+
* viaRelay=true 时走的是中转站, 官方余额与当前用量无关 → 一律显示「—」 */
|
|
892
|
+
function barAmountText(current, t, viaRelay) {
|
|
523
893
|
if (!current) return "";
|
|
894
|
+
if (viaRelay) return "—";
|
|
524
895
|
if (current.status === "ok") {
|
|
525
896
|
const cur = current.currency || "USD";
|
|
526
897
|
const num = typeof current.total === "number" ? current.total : 0;
|
|
@@ -529,15 +900,54 @@ window.__ModuleLoader__.load({
|
|
|
529
900
|
return formatMoney(num, cur);
|
|
530
901
|
}
|
|
531
902
|
if (current.status === "no-key") return t("status.nokey");
|
|
532
|
-
if (current.status === "no-balance-api") return
|
|
903
|
+
if (current.status === "no-balance-api") return "—";
|
|
533
904
|
if (current.status === "local") return t("status.local");
|
|
534
905
|
return t("status.err");
|
|
535
906
|
}
|
|
536
|
-
/**
|
|
907
|
+
/** 当前 provider 是否为中转站 (非官方直连) —— 中转站无余额接口, 金额显示「—」
|
|
908
|
+
*
|
|
909
|
+
* 三层判定, 优先级由高到低 (与服务端注释一致):
|
|
910
|
+
* 1) 用户在设置里显式声明的官方直连名单 config.officialProviders (兜住一切误判)
|
|
911
|
+
* 2) 服务端读 settings.yaml 按 baseURL **域名** 判定的 config.providerKinds
|
|
912
|
+
* (官方域名白名单命中→official, 不命中→relay; 没写 baseURL 的 provider 不在表里)
|
|
913
|
+
* 3) DSH 官方插件命名约定: `-official` / `_official` 后缀 (如 deepseek-official)
|
|
914
|
+
* 都不命中 → 按中转站处理。保守取向: 宁可不显示余额, 也不显示错的余额。
|
|
915
|
+
* (provider 为空表示还不知道走哪条路 → 不判中转站, 照常显示所选平台余额) */
|
|
916
|
+
function isRelayProvider(provider, config) {
|
|
917
|
+
if (typeof provider !== "string" || provider === "") return false;
|
|
918
|
+
const p = provider.toLowerCase();
|
|
919
|
+
// 第 1 层: 用户显式名单
|
|
920
|
+
const official = config && config.officialProviders;
|
|
921
|
+
if (Array.isArray(official)) {
|
|
922
|
+
for (const name of official) {
|
|
923
|
+
if (typeof name === "string" && name.trim().toLowerCase() === p) return false;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
// 第 2 层: 服务端按 baseURL 域名判定
|
|
927
|
+
const kinds = config && config.providerKinds;
|
|
928
|
+
if (kinds && typeof kinds === "object") {
|
|
929
|
+
const kind = kinds[provider] !== undefined ? kinds[provider] : kinds[p];
|
|
930
|
+
if (kind === "official") return false;
|
|
931
|
+
if (kind === "relay") return true;
|
|
932
|
+
}
|
|
933
|
+
// 第 3 层: 官方插件后缀约定
|
|
934
|
+
if (p.endsWith("-official") || p.endsWith("_official")) return false;
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
/** v0.5.3: 模型名 → 平台 id 映射 (仅强特征匹配, 避免误切; 自定义/中转由调用方按名称二次匹配)
|
|
938
|
+
* 只按模型名映射, 不因中转站返回空 —— 否则状态条不会跟着模型切换。
|
|
939
|
+
* 中转站不显示余额由 BarReadout 的 viaRelay 单独处理。 */
|
|
537
940
|
function modelToPlatform(model) {
|
|
538
941
|
if (!model || typeof model !== "string") return "";
|
|
539
942
|
const m = model.toLowerCase();
|
|
540
943
|
if (/^deepseek/.test(m)) return "deepseek";
|
|
944
|
+
if (/^gpt/.test(m) || /^o[1-3]([-.]|$)/.test(m) || m.includes("openai")) return "openai";
|
|
945
|
+
if (/^claude/.test(m)) return "claude";
|
|
946
|
+
if (/^gemini/.test(m)) return "gemini";
|
|
947
|
+
if (/^qwen/.test(m)) return "qwen";
|
|
948
|
+
if (/^doubao/.test(m) || /^seed[-_]/.test(m)) return "doubao";
|
|
949
|
+
if (/hunyuan/.test(m)) return "hunyuan";
|
|
950
|
+
if (/^mimo/.test(m)) return "mimo";
|
|
541
951
|
if (/glm|chatglm/.test(m)) return "zhipu";
|
|
542
952
|
if (/kimi|moonshot/.test(m)) return "moonshot";
|
|
543
953
|
if (/^step[-_]/.test(m)) return "stepfun";
|
|
@@ -574,12 +984,17 @@ window.__ModuleLoader__.load({
|
|
|
574
984
|
|
|
575
985
|
|
|
576
986
|
//#region 一层 bar
|
|
577
|
-
function BarReadout({ t, onOpen, onOpenSettings, selectedId, onSelect, config, cost }) {
|
|
987
|
+
function BarReadout({ t, onOpen, onOpenSettings, selectedId, onSelect, config, cost, provider }) {
|
|
578
988
|
const data = react.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
579
989
|
const balances = data.balances || [];
|
|
580
990
|
let current = balances.find(b => b.platform === selectedId);
|
|
581
991
|
if (!current) current = balances.find(b => b.status === "ok") || balances[0];
|
|
582
|
-
|
|
992
|
+
// 当前走中转站时不显示官方余额 (中转站没有余额接口)
|
|
993
|
+
const viaRelay = isRelayProvider(provider, config);
|
|
994
|
+
// 选中项与当前平台不同步时, commit 后再同步 (render 阶段调用会触发 React 18 警告)
|
|
995
|
+
react.useEffect(() => {
|
|
996
|
+
if (current && selectedId !== current.platform && current.status === "ok") onSelect(current.platform);
|
|
997
|
+
});
|
|
583
998
|
|
|
584
999
|
// v0.5.0 手机端: 点击状态条开看板, 长按 500ms 弹设置菜单 (替代常驻齿轮)
|
|
585
1000
|
const pressTimer = react.useRef(null);
|
|
@@ -600,19 +1015,20 @@ window.__ModuleLoader__.load({
|
|
|
600
1015
|
const handleBarClick = () => { if (longPressed.current) return; onOpen(); };
|
|
601
1016
|
|
|
602
1017
|
// v0.5.0: 金额文字 rAF 延迟一帧写入, 合并同一帧内的重复变化
|
|
603
|
-
const [displayAmount, setDisplayAmount] = react.useState(barAmountText(current, t));
|
|
1018
|
+
const [displayAmount, setDisplayAmount] = react.useState(barAmountText(current, t, viaRelay));
|
|
604
1019
|
react.useEffect(() => {
|
|
605
|
-
const txt = barAmountText(current, t);
|
|
1020
|
+
const txt = barAmountText(current, t, viaRelay);
|
|
606
1021
|
const raf = requestAnimationFrame(() => setDisplayAmount((prev) => (prev === txt ? prev : txt)));
|
|
607
1022
|
return () => cancelAnimationFrame(raf);
|
|
608
1023
|
});
|
|
609
1024
|
|
|
1025
|
+
const hasPeakInfo = typeof config?.isPeak === "boolean";
|
|
610
1026
|
const isPeak = config?.isPeak === true;
|
|
611
1027
|
const isWknd = config?.isWeekend === true;
|
|
612
1028
|
|
|
613
1029
|
// 峰谷标记 (趣味版: 梁文峰 ☀️ / 梁文谷 🌙)
|
|
614
1030
|
let peakNode = null;
|
|
615
|
-
if (
|
|
1031
|
+
if (hasPeakInfo) {
|
|
616
1032
|
const peakTitle = isPeak
|
|
617
1033
|
? t("peak.now") + " " + t("peak.hint")
|
|
618
1034
|
: (isWknd ? t("valley.weekend") : t("valley.now"));
|
|
@@ -638,7 +1054,9 @@ window.__ModuleLoader__.load({
|
|
|
638
1054
|
]);
|
|
639
1055
|
|
|
640
1056
|
const meta = metaFor(current.platform), level = getLevel(current, config);
|
|
641
|
-
const
|
|
1057
|
+
const isNoBalance = viaRelay || (current && current.status === "no-balance-api");
|
|
1058
|
+
const dotClass = "dshadb_bar_dot" + (isNoBalance ? "" : " " + (level === "ok" ? "dshadb_bar_dot_ok" : level === "warn" ? "dshadb_bar_dot_warn" : "dshadb_bar_dot_err"));
|
|
1059
|
+
const dotStyle = isNoBalance ? { background: "#c7c7cc" } : undefined;
|
|
642
1060
|
const amtClass = "dshadb_bar_amount " + (level === "ok" ? "dshadb_bar_ok" : level === "warn" ? "dshadb_bar_warn" : "dshadb_bar_err");
|
|
643
1061
|
return react.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: "4px" } }, [
|
|
644
1062
|
react.createElement("span", {
|
|
@@ -650,7 +1068,7 @@ window.__ModuleLoader__.load({
|
|
|
650
1068
|
react.createElement("span", { className: "dshadb_bar_logo", key: "logo" }, renderLogo(current)),
|
|
651
1069
|
react.createElement("span", { className: "dshadb_bar_name", key: "name" }, meta.name),
|
|
652
1070
|
react.createElement("span", { className: amtClass, key: "amount" }, displayAmount),
|
|
653
|
-
react.createElement("span", { className: dotClass, key: "dot" }),
|
|
1071
|
+
react.createElement("span", { className: dotClass, style: dotStyle, key: "dot" }),
|
|
654
1072
|
peakNode,
|
|
655
1073
|
react.createElement("span", { className: "dshadb_bar_cost", title: cost?.title, key: "cost" }, cost?.text || "~—"),
|
|
656
1074
|
]),
|
|
@@ -659,7 +1077,6 @@ window.__ModuleLoader__.load({
|
|
|
659
1077
|
//#endregion
|
|
660
1078
|
|
|
661
1079
|
//#region 折叠分组 (带展开过渡动画 + 分类色点)
|
|
662
|
-
const GROUP_DOT = { domestic: "domestic", abroad: "abroad", local: "local", relay: "relay", custom: "custom" };
|
|
663
1080
|
function GroupSection({ label, count, open, onToggle, children }) {
|
|
664
1081
|
return react.createElement("div", { key: label }, [
|
|
665
1082
|
react.createElement("div", { className: "dshadb_group_head", onClick: onToggle, role: "button", tabIndex: 0, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onToggle(); } } }, [
|
|
@@ -682,7 +1099,7 @@ window.__ModuleLoader__.load({
|
|
|
682
1099
|
function DashboardDrawer({ isOpen, onClose, t, selectedId, onSelect, onOpenDetail, onAddRelay, onAddCustom, onOpenSettings, config, currentModel }) {
|
|
683
1100
|
const data = react.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
684
1101
|
const [isRefreshing, setRefreshing] = react.useState(false);
|
|
685
|
-
const [openGroups, setOpenGroups] = react.useState({ domestic: false, abroad: false, relay: false,
|
|
1102
|
+
const [openGroups, setOpenGroups] = react.useState({ domestic: false, abroad: false, relay: false, custom: false, nobalance: false });
|
|
686
1103
|
react.useEffect(() => {
|
|
687
1104
|
if (!isOpen) return;
|
|
688
1105
|
const handleKey = (e) => { if (e.key === "Escape") onClose(); };
|
|
@@ -693,13 +1110,27 @@ window.__ModuleLoader__.load({
|
|
|
693
1110
|
const handleRefresh = () => { setRefreshing(true); store.forceRefresh().then(() => setTimeout(() => setRefreshing(false), 500)); };
|
|
694
1111
|
const balances = data.balances || [];
|
|
695
1112
|
const isLoading = data.status === "loading" && balances.length === 0;
|
|
1113
|
+
// v1.4.1: 加载超过 4 秒就在骨架屏下面补一句「首次加载较慢」的解释(冷启动全量刷新实测 8~30s)
|
|
1114
|
+
const [slowLoad, setSlowLoad] = react.useState(false);
|
|
1115
|
+
react.useEffect(() => {
|
|
1116
|
+
if (!isLoading) { setSlowLoad(false); return undefined; }
|
|
1117
|
+
const timer = setTimeout(() => setSlowLoad(true), 4000);
|
|
1118
|
+
return () => clearTimeout(timer);
|
|
1119
|
+
}, [isLoading]);
|
|
696
1120
|
const okCount = balances.filter(b => b.status === "ok").length;
|
|
697
1121
|
|
|
698
1122
|
const groups = { domestic: [], abroad: [], relay: [], custom: [] };
|
|
1123
|
+
const noBalanceBrands = [];
|
|
699
1124
|
let selectedBalance = null;
|
|
1125
|
+
const showBrands = !!(config && config.showNoBalanceBrands);
|
|
700
1126
|
for (const b of balances) {
|
|
1127
|
+
if (b.platform === selectedId) { selectedBalance = b; continue; }
|
|
1128
|
+
// 无余额模型品牌: 设置开关开启才显示, 放入单独分组
|
|
1129
|
+
if (b.status === "no-balance-api" || b.status === "noBalance") {
|
|
1130
|
+
if (showBrands) noBalanceBrands.push(b);
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
701
1133
|
const cat = b.category || metaFor(b.platform).cat;
|
|
702
|
-
if (b.platform === selectedId) { selectedBalance = b; continue; } // ② 选中模型从分组排除,避免重复
|
|
703
1134
|
if (cat === "海外" || cat === "官方") groups.abroad.push(b);
|
|
704
1135
|
else if (cat === "中转站") groups.relay.push(b);
|
|
705
1136
|
else if (cat === "自定义") groups.custom.push(b);
|
|
@@ -711,17 +1142,21 @@ window.__ModuleLoader__.load({
|
|
|
711
1142
|
const meta = metaFor(b.platform), level = getLevel(b, config), isSelected = b.platform === selectedId;
|
|
712
1143
|
const cardClass = "dshadb_card " + (isSelected ? "dshadb_card_active " : "");
|
|
713
1144
|
// descText 逻辑
|
|
1145
|
+
// v1.4.0 修复: 原分支硬编码 "· 配额正常", 与三色灯(level)完全无关 —— GLM 配额耗尽时
|
|
1146
|
+
// (level=err、note 已写「配额已用完(0), 待重置」) 会渲染成「…待重置 · 配额正常」, 自相矛盾。
|
|
1147
|
+
// 改为统一按 level 取状态标签, 保证文案与灯色一致。
|
|
1148
|
+
// (sessionCost/modelId 服务端目前从不产出, 该分支保留仅为将来兼容, 不再是唯一出口。)
|
|
714
1149
|
let descText;
|
|
715
1150
|
if (b.status === "ok" && b.sessionCost != null) {
|
|
716
1151
|
descText = (b.modelId || "—") + " · 会话消耗 " + (typeof b.sessionCost === "number" ? b.sessionCost.toFixed(2) : b.sessionCost);
|
|
717
1152
|
} else if (b.status === "ok" && b.note) {
|
|
718
|
-
descText = b.note + " ·
|
|
1153
|
+
descText = b.note + " · " + statusLabel(level, t);
|
|
719
1154
|
} else if (b.status === "ok") {
|
|
720
|
-
descText =
|
|
1155
|
+
descText = statusLabel(level, t);
|
|
721
1156
|
} else if (b.status === "local") {
|
|
722
1157
|
descText = "本地模型";
|
|
723
1158
|
} else if (b.status === "noBalance" || b.status === "no-balance-api") {
|
|
724
|
-
descText = "
|
|
1159
|
+
descText = "按价格表估算消耗";
|
|
725
1160
|
} else {
|
|
726
1161
|
descText = statusLabel(level, t);
|
|
727
1162
|
}
|
|
@@ -734,15 +1169,16 @@ window.__ModuleLoader__.load({
|
|
|
734
1169
|
amtClass = level === "ok" ? "dshadb_card_status_ok" : level === "warn" ? "dshadb_card_status_warn" : "dshadb_card_status_err";
|
|
735
1170
|
amount = val;
|
|
736
1171
|
} else if (b.status === "no-key") { amount = t("status.nokey"); amtClass = "dshadb_card_amount_sub"; }
|
|
737
|
-
else if (b.status === "no-balance-api") { amount =
|
|
1172
|
+
else if (b.status === "no-balance-api") { amount = "—"; amtClass = "dshadb_card_amount_sub"; }
|
|
738
1173
|
else if (b.status === "local") { amount = t("status.local"); amtClass = "dshadb_card_amount_sub"; }
|
|
739
1174
|
else { amount = b.error || t("error.unknown"); amtClass = "dshadb_card_status_err"; }
|
|
740
|
-
const
|
|
741
|
-
const
|
|
1175
|
+
const isNoBalance = b.status === "no-balance-api";
|
|
1176
|
+
const stClass = isNoBalance ? "dshadb_card_amount_sub" : (level === "ok" ? "dshadb_card_status_ok" : level === "warn" ? "dshadb_card_status_warn" : "dshadb_card_status_err");
|
|
1177
|
+
const statusText = isNoBalance ? "—" : statusLabel(level, t);
|
|
742
1178
|
const rightNode = react.createElement("div", { className: "dshadb_card_right", key: "right" }, [
|
|
743
1179
|
react.createElement("span", { className: "dshadb_card_amount" + (amtClass ? " " + amtClass : ""), key: "amt" }, amount),
|
|
744
1180
|
react.createElement("span", { className: "dshadb_card_status " + stClass, key: "st" }, [
|
|
745
|
-
react.createElement("span", { className: "dshadb_status_dot dshadb_status_dot_" + (level === "ok" ? "ok" : level === "warn" ? "warn" : "err"), key: "dot" }),
|
|
1181
|
+
react.createElement("span", { className: "dshadb_status_dot" + (isNoBalance ? "" : " dshadb_status_dot_" + (level === "ok" ? "ok" : level === "warn" ? "warn" : "err")), key: "dot", style: isNoBalance ? { background: "#c7c7cc" } : undefined }),
|
|
746
1182
|
statusText,
|
|
747
1183
|
]),
|
|
748
1184
|
]);
|
|
@@ -757,19 +1193,35 @@ window.__ModuleLoader__.load({
|
|
|
757
1193
|
]);
|
|
758
1194
|
};
|
|
759
1195
|
|
|
1196
|
+
const slowHint = isLoading && slowLoad;
|
|
760
1197
|
let bodyContent;
|
|
761
1198
|
if (isLoading) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
react.createElement("div", { className: "
|
|
767
|
-
|
|
768
|
-
|
|
1199
|
+
// v1.4.1: 冷启动全量刷新实测要 8~30 秒(中转站 auto 探测是串行的),
|
|
1200
|
+
// 光画骨架屏用户会以为卡死 → 超过 4 秒补一句解释。
|
|
1201
|
+
bodyContent = react.createElement("div", null, [
|
|
1202
|
+
...([0,1,2,3].map(i => react.createElement("div", { className: "dshadb_skeleton", key: i }, [
|
|
1203
|
+
react.createElement("div", { className: "dshadb_skeleton_logo", key: "logo" }),
|
|
1204
|
+
react.createElement("div", { className: "dshadb_skeleton_lines", key: "lines" }, [
|
|
1205
|
+
react.createElement("div", { className: "dshadb_skeleton_line dshadb_skeleton_line_long", key: "l1" }),
|
|
1206
|
+
react.createElement("div", { className: "dshadb_skeleton_line dshadb_skeleton_line_short", key: "l2" }),
|
|
1207
|
+
]),
|
|
1208
|
+
]))),
|
|
1209
|
+
slowHint ? react.createElement("div", { key: "slow", style: { fontSize: 11, color: "#8b91a0", textAlign: "center", lineHeight: 1.6, margin: "10px 4px 2px" } },
|
|
1210
|
+
"首次加载要逐个平台拉取余额(含中转站探测),通常 10~30 秒,请稍候…") : null,
|
|
1211
|
+
]);
|
|
769
1212
|
} else if (balances.length === 0) {
|
|
1213
|
+
// C-3c (v1.4.1): 请求失败时以前只画"空看板" —— 用户分不清「没有平台」和「请求失败」。
|
|
1214
|
+
// 实测 snapshot.message 全文件只被赋值、从不渲染。
|
|
1215
|
+
const isErr = snapshot.status === "error";
|
|
770
1216
|
bodyContent = react.createElement("div", { className: "dshadb_empty" }, [
|
|
771
|
-
react.createElement("span", { key: "icon", style: { fontSize: 30, opacity: 0.5 } }, "📊"),
|
|
772
|
-
react.createElement("span", { key: "title" }, t("title")),
|
|
1217
|
+
react.createElement("span", { key: "icon", style: { fontSize: 30, opacity: 0.5 } }, isErr ? "⚠️" : "📊"),
|
|
1218
|
+
react.createElement("span", { key: "title" }, isErr ? "余额接口请求失败" : t("title")),
|
|
1219
|
+
isErr && snapshot.message ? react.createElement("span", { key: "msg", style: { fontSize: 11, color: "#e05252", wordBreak: "break-all" } }, String(snapshot.message)) : null,
|
|
1220
|
+
isErr ? react.createElement("button", {
|
|
1221
|
+
key: "retry", type: "button", className: "dshadb_add_btn",
|
|
1222
|
+
style: { width: "auto", padding: "6px 18px", marginTop: "4px" },
|
|
1223
|
+
onClick: () => { try { store.forceRefresh(); } catch (e) { /* 忽略 */ } },
|
|
1224
|
+
}, "重试") : null,
|
|
773
1225
|
]);
|
|
774
1226
|
} else {
|
|
775
1227
|
bodyContent = [
|
|
@@ -779,6 +1231,7 @@ window.__ModuleLoader__.load({
|
|
|
779
1231
|
// 国内平台 (含deepseek)
|
|
780
1232
|
groups.domestic.length ? react.createElement(GroupSection, { label: t("domestic"), count: groups.domestic.length, open: openGroups.domestic, onToggle: () => toggle("domestic"), key: "g_domestic" }, groups.domestic.map(card)) : null,
|
|
781
1233
|
groups.abroad.length ? react.createElement(GroupSection, { label: t("abroad"), count: groups.abroad.length, open: openGroups.abroad, onToggle: () => toggle("abroad"), key: "g_abroad" }, groups.abroad.map(card)) : null,
|
|
1234
|
+
noBalanceBrands.length ? react.createElement(GroupSection, { label: t("brands"), count: noBalanceBrands.length, open: openGroups.nobalance, onToggle: () => toggle("nobalance"), key: "g_brands" }, noBalanceBrands.map(card)) : null,
|
|
782
1235
|
groups.relay.length ? react.createElement(GroupSection, { label: t("relay"), count: groups.relay.length, open: openGroups.relay, onToggle: () => toggle("relay"), key: "g_relay" }, groups.relay.map(card)) : null,
|
|
783
1236
|
groups.custom.length ? react.createElement(GroupSection, { label: t("custom"), count: groups.custom.length, open: openGroups.custom, onToggle: () => toggle("custom"), key: "g_custom" }, groups.custom.map(card)) : null,
|
|
784
1237
|
// ③ 添加按钮居中两边对齐
|
|
@@ -793,8 +1246,10 @@ window.__ModuleLoader__.load({
|
|
|
793
1246
|
}
|
|
794
1247
|
|
|
795
1248
|
return react.createElement("div", { className: "dshadb_scrim", onClick: (e) => { if (e.target === e.currentTarget) onClose(); } }, [
|
|
1249
|
+
// v1.4.0: 侧滑守卫 —— 让手机壳的「左边缘开侧边栏」手势层放弃识别 (CSS 与 AGENTS.md ① 有详解)
|
|
1250
|
+
react.createElement("div", { className: "dshadb_swipeguard", "aria-hidden": "true", key: "swipeguard" }),
|
|
796
1251
|
react.createElement("div", { className: "dshadb_drawer", onClick: (e) => e.stopPropagation(), key: "drawer" }, [
|
|
797
|
-
react.createElement(
|
|
1252
|
+
react.createElement(SwipeHandle, { onClose, key: "handle" }),
|
|
798
1253
|
react.createElement("div", { className: "dshadb_header", key: "header" }, [
|
|
799
1254
|
react.createElement("div", { className: "dshadb_header_left", key: "left" }, [
|
|
800
1255
|
// 模型尾椎: 从 currentModel 提取变体名, 如 deepseek-v4-flash → DSH·V4
|
|
@@ -818,7 +1273,7 @@ window.__ModuleLoader__.load({
|
|
|
818
1273
|
t("title"),
|
|
819
1274
|
]),
|
|
820
1275
|
react.createElement("div", { className: "dshadb_header_sub", key: "sub" },
|
|
821
|
-
t("all.ok", { n: okCount }) + " · " + t("refreshing")
|
|
1276
|
+
t("all.ok", { n: okCount }) + (isRefreshing ? " · " + t("refreshing") : "")
|
|
822
1277
|
),
|
|
823
1278
|
]),
|
|
824
1279
|
react.createElement("button", {
|
|
@@ -833,7 +1288,7 @@ window.__ModuleLoader__.load({
|
|
|
833
1288
|
data.fetchedAt ? react.createElement("div", { className: "dshadb_footer", key: "footer" }, [
|
|
834
1289
|
"更新于 " + formatTime(data.fetchedAt),
|
|
835
1290
|
react.createElement("span", { className: "dshadb_footer_dot", key: "d1" }),
|
|
836
|
-
react.createElement("
|
|
1291
|
+
react.createElement("button", { type: "button", className: "dshadb_refresh_badge", key: "ri", onClick: handleRefresh, title: t("refresh") }, "每 " + ((config && config.clientPollIntervalMs) ? Math.round(config.clientPollIntervalMs / 1000) : 5) + "s 刷新"),
|
|
837
1292
|
]) : null,
|
|
838
1293
|
]),
|
|
839
1294
|
]);
|
|
@@ -849,7 +1304,7 @@ window.__ModuleLoader__.load({
|
|
|
849
1304
|
react.useEffect(() => {
|
|
850
1305
|
if (!isOpen || platformId !== "deepseek") return;
|
|
851
1306
|
let live = true;
|
|
852
|
-
|
|
1307
|
+
fetchT("/api-dashboard/prices", { headers: { accept: "application/json" } })
|
|
853
1308
|
.then((r) => (r.ok ? r.json() : null))
|
|
854
1309
|
.then((d) => { if (live && d && d.ok) setPriceData(d); })
|
|
855
1310
|
.catch(() => {});
|
|
@@ -865,21 +1320,9 @@ window.__ModuleLoader__.load({
|
|
|
865
1320
|
const b = (data.balances || []).find(x => x.platform === platformId);
|
|
866
1321
|
if (!b) return null;
|
|
867
1322
|
const meta = metaFor(b.platform), level = getLevel(b, config);
|
|
868
|
-
const
|
|
869
|
-
const costVal =
|
|
1323
|
+
const costFmtDetail = formatSessionCost(cost);
|
|
1324
|
+
const costVal = costFmtDetail !== null && costFmtDetail.hasValue ? costFmtDetail.text : "—";
|
|
870
1325
|
const cur = b.currency || "USD";
|
|
871
|
-
const detailRows = [];
|
|
872
|
-
if (b.status === "ok") {
|
|
873
|
-
detailRows.push(row(t("detail.total"), formatMoney(b.percent != null ? b.percent : b.total, b.percent != null ? "%" : cur) + (b.percent != null ? "%" : "")));
|
|
874
|
-
// v0.5.6: 充值行仅在与余额有差异时显示 (未消耗时 充值==余额 两行重复); 使用行为 0 不显示
|
|
875
|
-
if (b.topup != null && Math.abs(b.topup - (b.total || 0)) > 1e-9) detailRows.push(row(t("detail.topup"), formatMoney(b.topup, cur)));
|
|
876
|
-
if (b.used != null && b.used > 0) detailRows.push(row(t("detail.used"), formatMoney(b.used, cur)));
|
|
877
|
-
if (b.note) detailRows.push(row(t("detail.note"), b.note));
|
|
878
|
-
if (b.staleNote) detailRows.push(row("数据状态", b.staleNote));
|
|
879
|
-
} else {
|
|
880
|
-
detailRows.push(row(t("detail.note"), b.error || "—"));
|
|
881
|
-
}
|
|
882
|
-
detailRows.push(row(t("detail.sessionCost"), costVal));
|
|
883
1326
|
|
|
884
1327
|
// DeepSeek 峰谷趣味卡片
|
|
885
1328
|
let peakCard = null;
|
|
@@ -925,8 +1368,9 @@ window.__ModuleLoader__.load({
|
|
|
925
1368
|
|
|
926
1369
|
// v0.5.3: 回退为底部抽屉样式 (全屏卡片观感不佳, 复用看板同款 scrim+drawer)
|
|
927
1370
|
return react.createElement("div", { className: "dshadb_scrim", onClick: (e) => { if (e.target === e.currentTarget) onClose(); } }, [
|
|
1371
|
+
react.createElement("div", { className: "dshadb_swipeguard", "aria-hidden": "true", key: "swipeguard" }),
|
|
928
1372
|
react.createElement("div", { className: "dshadb_drawer", style: { maxHeight: "70vh" }, onClick: (e) => e.stopPropagation(), key: "drawer" }, [
|
|
929
|
-
react.createElement(
|
|
1373
|
+
react.createElement(SwipeHandle, { onClose, key: "handle" }),
|
|
930
1374
|
react.createElement("div", { className: "dshadb_header", key: "header" }, [
|
|
931
1375
|
react.createElement("div", { className: "dshadb_header_left", key: "left" }, [
|
|
932
1376
|
react.createElement("div", { className: "dshadb_header_title", key: "title", style: { fontSize: "18px" } }, meta.name),
|
|
@@ -987,8 +1431,8 @@ window.__ModuleLoader__.load({
|
|
|
987
1431
|
return metrics.length > 0 ? react.createElement("div", { className: "dshadb_detail_metrics", key: "metrics" }, metrics) : null;
|
|
988
1432
|
})(),
|
|
989
1433
|
// staleNote(暂存数据提示)
|
|
990
|
-
b.staleNote ?
|
|
991
|
-
b.status !== "ok" ?
|
|
1434
|
+
b.staleNote ? react.createElement("div", { className: "dshadb_detail_info", key: "stale" }, b.staleNote) : null,
|
|
1435
|
+
b.status !== "ok" ? react.createElement("div", { className: "dshadb_detail_info", key: "err" }, b.error || "—") : null,
|
|
992
1436
|
peakCard,
|
|
993
1437
|
priceCard,
|
|
994
1438
|
]),
|
|
@@ -1006,8 +1450,26 @@ window.__ModuleLoader__.load({
|
|
|
1006
1450
|
const [safe, setSafe] = react.useState(50);
|
|
1007
1451
|
const [warn, setWarn] = react.useState(10);
|
|
1008
1452
|
const [currency, setCurrency] = react.useState("CNY");
|
|
1453
|
+
// v1.3.2: 海外模型独立计价货币 follow|USD|CNY
|
|
1454
|
+
const [overseasCurrency, setOverseasCurrency] = react.useState("follow");
|
|
1009
1455
|
const [refreshSec, setRefreshSec] = react.useState(5);
|
|
1010
1456
|
const [whaleOn, setWhaleOn] = react.useState(!!(config && config.whaleEnabled));
|
|
1457
|
+
const [showBrands, setShowBrands] = react.useState(!!(config && config.showNoBalanceBrands));
|
|
1458
|
+
// 官方直连 provider 名单 (第 1 层判定): 多行文本, 逗号/换行分隔
|
|
1459
|
+
const [officialText, setOfficialText] = react.useState(
|
|
1460
|
+
Array.isArray(config && config.officialProviders) ? config.officialProviders.join(", ") : "");
|
|
1461
|
+
// 服务端按 baseURL 域名自动判定的结果 (只读, 供用户判断还需不需要手填)
|
|
1462
|
+
const [providerKinds, setProviderKinds] = react.useState(
|
|
1463
|
+
(config && config.providerKinds) || {});
|
|
1464
|
+
// v1.4.0「真自动」: 从 settings.yaml 自动发现的 DSH provider + 用户关掉的名单。
|
|
1465
|
+
// 服务端只下发「名字/baseURL/是否官方」, **不下发 key**。
|
|
1466
|
+
const [dshProviders, setDshProviders] = react.useState(
|
|
1467
|
+
Array.isArray(config && config.dshProviders) ? config.dshProviders : []);
|
|
1468
|
+
const [dshOff, setDshOff] = react.useState(
|
|
1469
|
+
Array.isArray(config && config.dshProviderOptOut) ? config.dshProviderOptOut : []);
|
|
1470
|
+
// v1.4.0: 来自 DSH 的中转站列表默认收起 (跟首页分组同一套折叠手法)。
|
|
1471
|
+
// 维护者原话「应该折叠的是来自 DSH 中转站下面的那一罗列」—— 「自动判定结果」保持平铺不折。
|
|
1472
|
+
const [showDsh, setShowDsh] = react.useState(false);
|
|
1011
1473
|
// v1.1.0: 大肥鱼细项 (独立页签, 走 /api-dashboard/whale/settings, 与看板主配置分开)
|
|
1012
1474
|
const [wf, setWf] = react.useState({
|
|
1013
1475
|
scale: 1, peekRatio: 0.5, soundOn: true, soundSet: "duck",
|
|
@@ -1026,7 +1488,7 @@ window.__ModuleLoader__.load({
|
|
|
1026
1488
|
});
|
|
1027
1489
|
const checkUpdate = react.useCallback((force) => {
|
|
1028
1490
|
setUpd(s => ({ ...s, phase: "checking" }));
|
|
1029
|
-
|
|
1491
|
+
fetchT("/api-dashboard/update" + (force ? "?force=1" : ""), { headers: { accept: "application/json" } })
|
|
1030
1492
|
.then(r => r.json())
|
|
1031
1493
|
.then(d => {
|
|
1032
1494
|
// v0.6.2: 检查完必有反馈 —— 已是最新也提示, 有新版提示版本号, 失败给固定文案
|
|
@@ -1038,13 +1500,15 @@ window.__ModuleLoader__.load({
|
|
|
1038
1500
|
}, []);
|
|
1039
1501
|
react.useEffect(() => { if (isOpen) checkUpdate(false); }, [isOpen]); // 打开面板自动检查
|
|
1040
1502
|
const installUpdate = () => {
|
|
1041
|
-
if (!window.confirm(t("update.available") + " v" + (upd.info?.remote || "") + " ?")) return;
|
|
1042
1503
|
setUpd(s => ({ ...s, phase: "installing" }));
|
|
1043
|
-
|
|
1504
|
+
fetchT("/api-dashboard/update/install", { method: "POST" }, 120000)
|
|
1044
1505
|
.then(r => r.json().then(d => ({ code: r.status, body: d })))
|
|
1045
1506
|
.then(({ code, body }) => {
|
|
1046
1507
|
if (code === 200 && body.ok) {
|
|
1047
1508
|
setUpd({ phase: "done", info: { ok: true, current: body.installed, remote: body.installed, hasUpdate: false }, msg: t("update.done") });
|
|
1509
|
+
// v1.3.3: 更新完成后发 Toast 通知 —— 设置面板底部的 11px 灰色提示太容易被忽略,
|
|
1510
|
+
// 移动端尤其如此。Toast 在屏幕中央弹出, 不会被错过。
|
|
1511
|
+
try { fetch("/app/toast?text=" + encodeURIComponent("插件已更新到 v" + body.installed + ",重启 Web GUI 生效"), { mode: "no-cors" }).catch(() => {}); } catch {}
|
|
1048
1512
|
} else {
|
|
1049
1513
|
setUpd(s => ({ ...s, phase: "fail", msg: body.error === "already up to date" ? t("update.latest") : t("update.fail") }));
|
|
1050
1514
|
checkUpdate(true);
|
|
@@ -1065,20 +1529,32 @@ window.__ModuleLoader__.load({
|
|
|
1065
1529
|
setSafe(config?.safeThreshold ?? 50);
|
|
1066
1530
|
setWarn(config?.warnThreshold ?? 10);
|
|
1067
1531
|
setCurrency(config?.currency ?? "CNY");
|
|
1532
|
+
setOverseasCurrency(config?.overseasCurrency ?? "follow");
|
|
1068
1533
|
setRefreshSec(Math.round((config?.clientPollIntervalMs || 5000) / 1000) || 5);
|
|
1069
1534
|
setWhaleOn(!!(config && config.whaleEnabled));
|
|
1070
1535
|
let cancelled = false;
|
|
1071
|
-
|
|
1536
|
+
fetchT("/api-dashboard/config", { cache: "no-store" }).then(r => r.json()).then(d => {
|
|
1072
1537
|
if (cancelled) return;
|
|
1073
1538
|
if (d.ok) {
|
|
1074
1539
|
setRelays(d.customRelays || []);
|
|
1075
1540
|
setModels(d.customModels || []);
|
|
1076
1541
|
if (d.refreshIntervalSec) setRefreshSec(d.refreshIntervalSec);
|
|
1542
|
+
// C-2 (v1.4.1): 阈值/主币种也必须从 /config 补一次。
|
|
1543
|
+
// 以前只从 /balances 的 config 读, 而冷启动 /balances 要等 8~10s →
|
|
1544
|
+
// 那期间打开面板看到的是默认 50/10/CNY, 一点"保存并生效"就把用户真实配置覆盖了。
|
|
1545
|
+
if (typeof d.safeThreshold === "number") setSafe(d.safeThreshold);
|
|
1546
|
+
if (typeof d.warnThreshold === "number") setWarn(d.warnThreshold);
|
|
1547
|
+
if (typeof d.currency === "string") setCurrency(d.currency);
|
|
1548
|
+
if (typeof d.overseasCurrency === "string") setOverseasCurrency(d.overseasCurrency);
|
|
1077
1549
|
if (typeof d.whaleEnabled === "boolean") setWhaleOn(d.whaleEnabled);
|
|
1550
|
+
if (Array.isArray(d.officialProviders)) setOfficialText(d.officialProviders.join(", "));
|
|
1551
|
+
if (d.providerKinds && typeof d.providerKinds === "object") setProviderKinds(d.providerKinds);
|
|
1552
|
+
if (Array.isArray(d.dshProviders)) setDshProviders(d.dshProviders);
|
|
1553
|
+
if (Array.isArray(d.dshProviderOptOut)) setDshOff(d.dshProviderOptOut);
|
|
1078
1554
|
}
|
|
1079
1555
|
}).finally(() => { if (!cancelled) setLoading(false); });
|
|
1080
1556
|
// 大肥鱼细项与主配置并行拉取, 失败保持默认值
|
|
1081
|
-
|
|
1557
|
+
fetchT("/api-dashboard/whale/settings", { cache: "no-store" }).then(r => r.json()).then(d => {
|
|
1082
1558
|
if (cancelled || !d || !d.ok || !d.settings) return;
|
|
1083
1559
|
const s = d.settings;
|
|
1084
1560
|
setWf(prev => ({
|
|
@@ -1096,13 +1572,20 @@ window.__ModuleLoader__.load({
|
|
|
1096
1572
|
}, [isOpen]);
|
|
1097
1573
|
if (!isOpen) return null;
|
|
1098
1574
|
const save = async () => {
|
|
1575
|
+
// C-1 (v1.4.1): 原来这里夹的是 5 —— 输入框允许 1 秒、服务端 clampRefreshSec 也吃 1 秒,
|
|
1576
|
+
// 只有 save() 把它悄悄改回 5 秒(用户设 1 秒保存后变 5 秒)。四处下限现在一致。
|
|
1577
|
+
const nextRefreshSec = Math.min(Math.max(Number(refreshSec) || 1, 1), 60)
|
|
1578
|
+
setRefreshSec(nextRefreshSec)
|
|
1099
1579
|
setSaving(true);
|
|
1100
1580
|
try {
|
|
1101
|
-
await
|
|
1581
|
+
await fetchT("/api-dashboard/config", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({
|
|
1102
1582
|
customRelays: relays, customModels: models,
|
|
1103
|
-
safeThreshold: Number(safe), warnThreshold: Number(warn), currency,
|
|
1104
|
-
refreshIntervalSec:
|
|
1583
|
+
safeThreshold: Number(safe), warnThreshold: Number(warn), currency, overseasCurrency,
|
|
1584
|
+
refreshIntervalSec: nextRefreshSec,
|
|
1105
1585
|
whaleEnabled: !!whaleOn,
|
|
1586
|
+
officialProviders: officialText,
|
|
1587
|
+
// v1.4.0「真自动」: 关掉的 DSH provider (空数组 = 全部启用)
|
|
1588
|
+
dshProviderOptOut: dshOff,
|
|
1106
1589
|
}) });
|
|
1107
1590
|
// v1.1.0: 保存后立刻强拉一次, 让开关类设置(大肥鱼)即时生效, 不必等下一轮轮询
|
|
1108
1591
|
try { await store.forceRefresh(); } catch (e) { /* 忽略 */ }
|
|
@@ -1129,13 +1612,24 @@ window.__ModuleLoader__.load({
|
|
|
1129
1612
|
setWhaleOn(next);
|
|
1130
1613
|
if (next) ensureWhaleWidget(); else removeWhaleWidget();
|
|
1131
1614
|
try {
|
|
1132
|
-
await
|
|
1615
|
+
await fetchT("/api-dashboard/config", {
|
|
1133
1616
|
method: "POST", headers: { "Content-Type": "application/json" },
|
|
1134
1617
|
body: JSON.stringify({ whaleEnabled: !!next }),
|
|
1135
1618
|
});
|
|
1136
1619
|
await store.forceRefresh();
|
|
1137
1620
|
} catch (e) { /* 网络失败: 本地仍生效, 下次保存/刷新会对齐 */ }
|
|
1138
1621
|
};
|
|
1622
|
+
// v1.1.3: 显示模型品牌开关即时生效 (同 toggleWhale 模式, 不依赖「保存并生效」按钮)
|
|
1623
|
+
const toggleBrands = async (next) => {
|
|
1624
|
+
setShowBrands(next);
|
|
1625
|
+
try {
|
|
1626
|
+
await fetchT("/api-dashboard/config", {
|
|
1627
|
+
method: "POST", headers: { "Content-Type": "application/json" },
|
|
1628
|
+
body: JSON.stringify({ showNoBalanceBrands: !!next }),
|
|
1629
|
+
});
|
|
1630
|
+
await store.forceRefresh();
|
|
1631
|
+
} catch (e) { /* 网络失败: 本地仍生效, 下次保存/刷新会对齐 */ }
|
|
1632
|
+
};
|
|
1139
1633
|
const qtLabel = (q) => t("qt." + (q || "auto"));
|
|
1140
1634
|
|
|
1141
1635
|
// v1.1.0: 大肥鱼细项即时生效 + 即时落盘 (滑块/开关都不依赖「保存并生效」按钮)
|
|
@@ -1143,7 +1637,7 @@ window.__ModuleLoader__.load({
|
|
|
1143
1637
|
setWf(prev => ({ ...prev, ...patch }));
|
|
1144
1638
|
patchWhaleWidget(patch);
|
|
1145
1639
|
try {
|
|
1146
|
-
|
|
1640
|
+
fetchT("/api-dashboard/whale/settings", {
|
|
1147
1641
|
method: "PUT", headers: { "Content-Type": "application/json" },
|
|
1148
1642
|
body: JSON.stringify(patch),
|
|
1149
1643
|
}).catch(() => {});
|
|
@@ -1175,24 +1669,93 @@ window.__ModuleLoader__.load({
|
|
|
1175
1669
|
react.createElement("div", { className: "dshadb_grid2", key: "misc" }, [
|
|
1176
1670
|
react.createElement("div", { key: "cur" }, [
|
|
1177
1671
|
react.createElement("label", { className: "dshadb_label", key: "cur_l" }, t("settings.currency")),
|
|
1178
|
-
react.createElement("select", { className: "dshadb_field", value: currency, onChange: (e) => setCurrency(e.target.value), key: "cur_s" }, [
|
|
1672
|
+
react.createElement("select", { className: "dshadb_field dshadb_field_select", value: currency, onChange: (e) => setCurrency(e.target.value), key: "cur_s" }, [
|
|
1179
1673
|
react.createElement("option", { value: "CNY", key: "c1" }, t("cmn")),
|
|
1180
1674
|
react.createElement("option", { value: "USD", key: "c2" }, t("usd")),
|
|
1181
1675
|
]),
|
|
1182
1676
|
]),
|
|
1677
|
+
// v1.3.2: 海外模型 (gpt/claude/gemini/grok...) 可独立选币种, follow=跟随上面的主货币
|
|
1678
|
+
react.createElement("div", { key: "ocur" }, [
|
|
1679
|
+
react.createElement("label", { className: "dshadb_label", key: "ocur_l" }, t("settings.overseasCurrency")),
|
|
1680
|
+
react.createElement("select", { className: "dshadb_field dshadb_field_select", value: overseasCurrency, onChange: (e) => setOverseasCurrency(e.target.value), key: "ocur_s" }, [
|
|
1681
|
+
react.createElement("option", { value: "follow", key: "o0" }, t("settings.overseasFollow")),
|
|
1682
|
+
react.createElement("option", { value: "USD", key: "o1" }, t("usd")),
|
|
1683
|
+
react.createElement("option", { value: "CNY", key: "o2" }, t("cmn")),
|
|
1684
|
+
]),
|
|
1685
|
+
react.createElement("span", { style: { fontSize: "10px", color: "var(--dsw-alias-label-tertiary)" }, key: "ocur_h" }, t("settings.overseasHint")),
|
|
1686
|
+
]),
|
|
1183
1687
|
react.createElement("div", { key: "refresh" }, [
|
|
1184
1688
|
react.createElement("label", { className: "dshadb_label", key: "rf_l" }, t("settings.refresh")),
|
|
1185
|
-
react.createElement("input", { className: "dshadb_field", type: "number", min:
|
|
1689
|
+
react.createElement("input", { className: "dshadb_field", type: "number", min: 1, max: 60, step: 1, value: refreshSec, onChange: (e) => setRefreshSec(Math.min(Math.max(Number(e.target.value) || 1, 1), 60)), key: "rf_i" }),
|
|
1186
1690
|
react.createElement("span", { style: { fontSize: "10px", color: "var(--dsw-alias-label-tertiary)" }, key: "rf_h" }, t("settings.refreshHint")),
|
|
1187
1691
|
]),
|
|
1188
1692
|
]),
|
|
1693
|
+
// 无余额模型品牌开关
|
|
1694
|
+
react.createElement("div", { className: "dshadb_settings_row", key: "brands", style: { margin: "2px 0 10px" } }, [
|
|
1695
|
+
react.createElement("div", { className: "dshadb_settings_row_main", key: "bm" }, [
|
|
1696
|
+
react.createElement("span", { className: "dshadb_settings_row_title", key: "bt" }, t("settings.showBrands")),
|
|
1697
|
+
react.createElement("span", { className: "dshadb_settings_row_sub", key: "bs" }, t("settings.showBrandsHint")),
|
|
1698
|
+
]),
|
|
1699
|
+
react.createElement(Switch, { checked: showBrands, onChange: toggleBrands, key: "bsw" }),
|
|
1700
|
+
]),
|
|
1701
|
+
// 官方直连 provider 名单 (第 1 层判定, 覆盖自动判定)
|
|
1702
|
+
react.createElement("div", { key: "official" }, [
|
|
1703
|
+
react.createElement("label", { className: "dshadb_label", key: "of_l" }, t("settings.official")),
|
|
1704
|
+
react.createElement("textarea", {
|
|
1705
|
+
className: "dshadb_field dshadb_textarea", rows: 2, value: officialText,
|
|
1706
|
+
placeholder: "deepseek, zhipu-official",
|
|
1707
|
+
onChange: (e) => setOfficialText(e.target.value), key: "of_i",
|
|
1708
|
+
}),
|
|
1709
|
+
react.createElement("span", { style: { fontSize: "10px", color: "var(--dsw-alias-label-tertiary)", display: "block", lineHeight: 1.5 }, key: "of_h" }, t("settings.officialHint")),
|
|
1710
|
+
react.createElement("div", { className: "dshadb_kinds", key: "of_k" }, [
|
|
1711
|
+
react.createElement("span", { className: "dshadb_kinds_title", key: "kt" }, t("settings.officialAuto")),
|
|
1712
|
+
Object.keys(providerKinds || {}).length === 0
|
|
1713
|
+
? react.createElement("span", { className: "dshadb_kinds_empty", key: "ke" }, t("settings.officialAutoEmpty"))
|
|
1714
|
+
: react.createElement("div", { className: "dshadb_kinds_list", key: "kl" },
|
|
1715
|
+
Object.keys(providerKinds).sort().map((name) => react.createElement("span", {
|
|
1716
|
+
className: "dshadb_kind" + (providerKinds[name] === "official" ? " dshadb_kind_official" : " dshadb_kind_relay"),
|
|
1717
|
+
key: name,
|
|
1718
|
+
}, name + " · " + t(providerKinds[name] === "official" ? "settings.officialKindOfficial" : "settings.officialKindRelay")))),
|
|
1719
|
+
]),
|
|
1720
|
+
]),
|
|
1721
|
+
// v1.4.0「真自动」: 从 settings.yaml 自动发现的中转站 (默认全开, 可单独关;
|
|
1722
|
+
// 官方直连与没写 baseURL 的只展示、不可开 —— 前者归预设平台管, 后者按铁律 9 不表态)
|
|
1723
|
+
// v1.4.0: 列表默认收起, 直接复用首页的分组折叠组件 GroupSection
|
|
1724
|
+
// (表头 + 条数 + 箭头 + max-height 过渡), 维护者要求「跟首页一样的折叠方法」。
|
|
1725
|
+
dshProviders.length > 0
|
|
1726
|
+
? react.createElement(GroupSection, {
|
|
1727
|
+
label: t("settings.dshProviders"), count: dshProviders.length,
|
|
1728
|
+
open: showDsh, onToggle: () => setShowDsh(!showDsh), key: "dshprov",
|
|
1729
|
+
}, [
|
|
1730
|
+
react.createElement("span", { className: "dshadb_kinds_empty", key: "dp_h", style: { marginBottom: "6px" } }, t("settings.dshProvidersHint")),
|
|
1731
|
+
react.createElement("div", { key: "dp_list" }, dshProviders.map((p) => {
|
|
1732
|
+
const locked = p.kind === "official" || p.kind === "no-base-url";
|
|
1733
|
+
const name = String(p.name || "");
|
|
1734
|
+
const off = dshOff.some((n) => String(n).toLowerCase() === name.toLowerCase());
|
|
1735
|
+
return react.createElement("div", { className: "dshadb_settings_row", key: name, style: { margin: "6px 0" } }, [
|
|
1736
|
+
react.createElement("div", { className: "dshadb_settings_row_main", key: "m" }, [
|
|
1737
|
+
react.createElement("span", { className: "dshadb_settings_row_title", key: "n" }, name),
|
|
1738
|
+
react.createElement("span", { className: "dshadb_settings_row_sub", key: "s" }, p.baseURL || t("settings.dshNoBaseUrl")),
|
|
1739
|
+
]),
|
|
1740
|
+
locked
|
|
1741
|
+
? react.createElement("span", { className: "dshadb_refresh_badge", key: "lock" },
|
|
1742
|
+
t(p.kind === "official" ? "settings.officialKindOfficial" : "settings.dshNoBaseUrl"))
|
|
1743
|
+
: react.createElement(Switch, {
|
|
1744
|
+
checked: !off, key: "sw",
|
|
1745
|
+
// 关掉 = 加进 optOut; 打开 = 从 optOut 移除 (大小写不敏感, 与后端一致)
|
|
1746
|
+
onChange: (next) => setDshOff(next
|
|
1747
|
+
? dshOff.filter((n) => String(n).toLowerCase() !== name.toLowerCase())
|
|
1748
|
+
: dshOff.concat([name])),
|
|
1749
|
+
}),
|
|
1750
|
+
]);
|
|
1751
|
+
})),
|
|
1752
|
+
])
|
|
1753
|
+
: null,
|
|
1189
1754
|
]);
|
|
1190
1755
|
|
|
1191
1756
|
// 中转站区块
|
|
1192
1757
|
const relaySection = react.createElement("div", { className: "dshadb_settings_section", key: "s_relays" }, [
|
|
1193
1758
|
react.createElement("div", { style: { marginTop: "2px", marginBottom: "8px" }, key: "relay_form" }, [
|
|
1194
|
-
react.createElement("label", { className: "dshadb_label", key: "r1" }, "中转站名称"),
|
|
1195
|
-
react.createElement("input", { className: "dshadb_field", value: form.name, placeholder: "我的中转站", onChange: (e) => setForm({ ...form, name: e.target.value }), key: "ri1" }),
|
|
1196
1759
|
react.createElement("label", { className: "dshadb_label", key: "r1" }, "中转站名称"),
|
|
1197
1760
|
react.createElement("input", { className: "dshadb_field", value: form.name, placeholder: "我的中转站", onChange: (e) => setForm({ ...form, name: e.target.value }), key: "ri1" }),
|
|
1198
1761
|
react.createElement("label", { className: "dshadb_label", key: "r2" }, "Base URL"),
|
|
@@ -1229,7 +1792,7 @@ window.__ModuleLoader__.load({
|
|
|
1229
1792
|
]),
|
|
1230
1793
|
react.createElement("div", { key: "m4" }, [
|
|
1231
1794
|
react.createElement("label", { className: "dshadb_label", key: "ml4" }, t("settings.queryType")),
|
|
1232
|
-
react.createElement("select", { className: "dshadb_field", value: mform.queryType, onChange: (e) => setMform({ ...mform, queryType: e.target.value }), key: "mi4" }, [
|
|
1795
|
+
react.createElement("select", { className: "dshadb_field dshadb_field_select", value: mform.queryType, onChange: (e) => setMform({ ...mform, queryType: e.target.value }), key: "mi4" }, [
|
|
1233
1796
|
react.createElement("option", { value: "auto", key: "q0" }, t("qt.auto")),
|
|
1234
1797
|
react.createElement("option", { value: "openai", key: "q1" }, t("qt.openai")),
|
|
1235
1798
|
react.createElement("option", { value: "quota", key: "q2" }, t("qt.quota")),
|
|
@@ -1255,7 +1818,7 @@ window.__ModuleLoader__.load({
|
|
|
1255
1818
|
]),
|
|
1256
1819
|
react.createElement("div", { key: "m7" }, [
|
|
1257
1820
|
react.createElement("label", { className: "dshadb_label", key: "ml7" }, t("settings.currency")),
|
|
1258
|
-
react.createElement("select", { className: "dshadb_field", value: mform.currency || "CNY", onChange: (e) => setMform({ ...mform, currency: e.target.value }), key: "mi7" }, [
|
|
1821
|
+
react.createElement("select", { className: "dshadb_field dshadb_field_select", value: mform.currency || "CNY", onChange: (e) => setMform({ ...mform, currency: e.target.value }), key: "mi7" }, [
|
|
1259
1822
|
react.createElement("option", { value: "CNY", key: "cc1" }, t("cmn")),
|
|
1260
1823
|
react.createElement("option", { value: "USD", key: "cc2" }, t("usd")),
|
|
1261
1824
|
]),
|
|
@@ -1342,7 +1905,7 @@ window.__ModuleLoader__.load({
|
|
|
1342
1905
|
]);
|
|
1343
1906
|
|
|
1344
1907
|
const output = react.createElement("div", { className: "dshadb_drawer", style: { maxHeight: "86vh" }, onClick: (e) => e.stopPropagation(), key: "drawer" }, [
|
|
1345
|
-
react.createElement(
|
|
1908
|
+
react.createElement(SwipeHandle, { onClose: onBack || onClose, key: "handle" }),
|
|
1346
1909
|
react.createElement("div", { className: "dshadb_header", key: "header" }, [
|
|
1347
1910
|
react.createElement("div", { style: { display: "flex", alignItems: "center", gap: "10px", flex: "1", minWidth: 0 }, key: "leftwrap" }, [
|
|
1348
1911
|
onBack ? react.createElement("button", { type: "button", className: "dshadb_header_back", onClick: onBack, key: "back", title: t("back") }, react.createElement(IconBack, null)) : null,
|
|
@@ -1371,12 +1934,26 @@ window.__ModuleLoader__.load({
|
|
|
1371
1934
|
upd.info?.hasUpdate ? react.createElement("button", { type: "button", className: "dshadb_save_btn", onClick: installUpdate, disabled: upd.phase === "installing", key: "upd_go", style: { flex: 1, padding: "8px", fontSize: "12px" } },
|
|
1372
1935
|
upd.phase === "installing" ? t("update.installing") : t("update.install") + " v" + upd.info.remote) : null,
|
|
1373
1936
|
]),
|
|
1374
|
-
|
|
1375
|
-
|
|
1937
|
+
// v1.3.3: 更新结果改为醒目横幅 —— 原来的 11px 灰色字在移动端几乎看不到,
|
|
1938
|
+
// 用户点完「一键更新」后以为没反应。成功=绿色底, 失败=红色底, 检查有新版=原来的灰色。
|
|
1939
|
+
upd.msg ? react.createElement("div", { key: "upd_msg", style: {
|
|
1940
|
+
color: upd.phase === "fail" ? "#b33" : upd.phase === "done" ? "#1a7a3a" : "#777b84",
|
|
1941
|
+
background: upd.phase === "fail" ? "#fde8e8" : upd.phase === "done" ? "#e8fde8" : "transparent",
|
|
1942
|
+
fontSize: upd.phase === "done" || upd.phase === "fail" ? "13px" : "11px",
|
|
1943
|
+
fontWeight: upd.phase === "done" || upd.phase === "fail" ? "600" : "400",
|
|
1944
|
+
padding: upd.phase === "done" || upd.phase === "fail" ? "8px 10px" : "0",
|
|
1945
|
+
borderRadius: upd.phase === "done" || upd.phase === "fail" ? "6px" : "0",
|
|
1946
|
+
marginTop: "8px",
|
|
1947
|
+
lineHeight: "1.4"
|
|
1948
|
+
} }, upd.msg) : (upd.info?.hasUpdate ? react.createElement("div", { key: "upd_msg", style: { color: "#777b84", fontSize: "11px", marginTop: "6px" } },
|
|
1949
|
+
t("update.available") + " v" + upd.info.remote) : null),
|
|
1376
1950
|
]),
|
|
1377
1951
|
]),
|
|
1378
1952
|
]);
|
|
1379
|
-
return react.createElement("div", { className: "dshadb_scrim", onClick: (e) => { if (e.target === e.currentTarget) onClose(); } },
|
|
1953
|
+
return react.createElement("div", { className: "dshadb_scrim", onClick: (e) => { if (e.target === e.currentTarget) onClose(); } }, [
|
|
1954
|
+
react.createElement("div", { className: "dshadb_swipeguard", "aria-hidden": "true", key: "swipeguard" }),
|
|
1955
|
+
output,
|
|
1956
|
+
]);
|
|
1380
1957
|
}
|
|
1381
1958
|
//#endregion
|
|
1382
1959
|
|
|
@@ -1399,7 +1976,6 @@ window.__ModuleLoader__.load({
|
|
|
1399
1976
|
var whaleCtxInfo = { isPeak: false };
|
|
1400
1977
|
function updateWhaleContext(info) {
|
|
1401
1978
|
whaleCtxInfo = { isPeak: !!(info && info.isPeak) };
|
|
1402
|
-
if (whaleWidget && whaleWidget.onContext) whaleWidget.onContext();
|
|
1403
1979
|
}
|
|
1404
1980
|
function whalePeakWords(peakMode, isPeak) {
|
|
1405
1981
|
if (peakMode === "liangwen") return isPeak ? "梁文峰" : "梁文谷";
|
|
@@ -1441,6 +2017,9 @@ window.__ModuleLoader__.load({
|
|
|
1441
2017
|
}
|
|
1442
2018
|
|
|
1443
2019
|
var whaleWidget = null;
|
|
2020
|
+
var whaleRefs = 0;
|
|
2021
|
+
// 最近一次已知的大肥鱼设置缓存 — 会话切换重建时直接用它初始化, 避免先从默认(右侧)闪一下再跳回用户位置
|
|
2022
|
+
var lastWhaleSettings = null;
|
|
1444
2023
|
// 停止互动后多久缩回 / 气泡存活时长 (用户要求: 3 秒)
|
|
1445
2024
|
var WHALE_IDLE_MS = 3000;
|
|
1446
2025
|
// 左右「边缘带」占屏宽比例: 松手时中心落在带内才吸附缩回, 中间区域原地不动
|
|
@@ -1453,7 +2032,16 @@ window.__ModuleLoader__.load({
|
|
|
1453
2032
|
if (whaleWidget) return;
|
|
1454
2033
|
if (typeof document === "undefined") return;
|
|
1455
2034
|
var st = {};
|
|
2035
|
+
var disposed = false;
|
|
1456
2036
|
for (var k in WHALE_DEFAULTS) st[k] = WHALE_DEFAULTS[k];
|
|
2037
|
+
// 用最近一次已知设置初始化, 避免重建时先从默认(右侧)闪一下再跳回用户位置
|
|
2038
|
+
if (lastWhaleSettings) {
|
|
2039
|
+
for (var k2 in WHALE_DEFAULTS) {
|
|
2040
|
+
if (lastWhaleSettings[k2] !== undefined && lastWhaleSettings[k2] !== null) st[k2] = lastWhaleSettings[k2];
|
|
2041
|
+
}
|
|
2042
|
+
if (lastWhaleSettings.left !== undefined) st.left = lastWhaleSettings.left;
|
|
2043
|
+
if (lastWhaleSettings.top !== undefined) st.top = lastWhaleSettings.top;
|
|
2044
|
+
}
|
|
1457
2045
|
|
|
1458
2046
|
// ---------- DOM ----------
|
|
1459
2047
|
var root = document.createElement("div");
|
|
@@ -1467,6 +2055,15 @@ window.__ModuleLoader__.load({
|
|
|
1467
2055
|
img.src = "/api-dashboard/whale/image.png";
|
|
1468
2056
|
img.alt = "大肥鱼"; img.draggable = false;
|
|
1469
2057
|
body.appendChild(img);
|
|
2058
|
+
// 透明抓取层: 让手机壳的侧滑手势层对挂件让路(它自己是「真·横向滚动容器」,
|
|
2059
|
+
// 由 0 高度的守卫子元素提供 2px 不可见横向溢出)。放在 body 内部 →
|
|
2060
|
+
// pointerdown 照常冒泡到下面绑在 body 上的拖拽处理器, 拖拽代码一行都不用改。
|
|
2061
|
+
var grab = document.createElement("div");
|
|
2062
|
+
grab.className = "dshadb-whale-grab";
|
|
2063
|
+
var grabGuard = document.createElement("div");
|
|
2064
|
+
grabGuard.className = "dshadb-whale-grab-guard";
|
|
2065
|
+
grab.appendChild(grabGuard);
|
|
2066
|
+
body.appendChild(grab);
|
|
1470
2067
|
sprite.appendChild(body);
|
|
1471
2068
|
var bubble = document.createElement("div");
|
|
1472
2069
|
bubble.className = "dshadb-whale-bubble";
|
|
@@ -1482,8 +2079,12 @@ window.__ModuleLoader__.load({
|
|
|
1482
2079
|
bubble.appendChild(bText);
|
|
1483
2080
|
root.appendChild(bubble);
|
|
1484
2081
|
root.appendChild(sprite);
|
|
2082
|
+
// 初始化时禁用 CSS transition, 避免 left:0 → 实际位置 的 300ms 滑动动画
|
|
2083
|
+
// (CSS 里写死了 left:0, JS 第一次设 left 时会触发 transition)
|
|
2084
|
+
root.style.transition = "none";
|
|
2085
|
+
// v1.4.1: 拿到保存的设置之前**先不显示** —— 见下面 reveal() 的注释。
|
|
2086
|
+
root.style.visibility = "hidden";
|
|
1485
2087
|
document.body.appendChild(root);
|
|
1486
|
-
|
|
1487
2088
|
// ---------- 音效 ----------
|
|
1488
2089
|
var audio = { press: null, release: null };
|
|
1489
2090
|
function reloadAudio() {
|
|
@@ -1543,14 +2144,15 @@ window.__ModuleLoader__.load({
|
|
|
1543
2144
|
if (saveTimer) clearTimeout(saveTimer);
|
|
1544
2145
|
saveTimer = setTimeout(function () {
|
|
1545
2146
|
saveTimer = null;
|
|
2147
|
+
lastWhaleSettings = {
|
|
2148
|
+
scale: st.scale, soundOn: st.soundOn, soundSet: st.soundSet, volume: st.volume,
|
|
2149
|
+
bubbleOn: st.bubbleOn, peakMode: st.peakMode, snapOn: st.snapOn,
|
|
2150
|
+
peekRatio: st.peekRatio, left: st.left, top: st.top, side: st.side,
|
|
2151
|
+
};
|
|
1546
2152
|
try {
|
|
1547
|
-
|
|
2153
|
+
fetchT("/api-dashboard/whale/settings", {
|
|
1548
2154
|
method: "PUT", headers: { "Content-Type": "application/json" },
|
|
1549
|
-
body: JSON.stringify(
|
|
1550
|
-
scale: st.scale, soundOn: st.soundOn, soundSet: st.soundSet, volume: st.volume,
|
|
1551
|
-
bubbleOn: st.bubbleOn, peakMode: st.peakMode, snapOn: st.snapOn,
|
|
1552
|
-
peekRatio: st.peekRatio, left: st.left, top: st.top, side: st.side,
|
|
1553
|
-
}),
|
|
2155
|
+
body: JSON.stringify(lastWhaleSettings),
|
|
1554
2156
|
}).catch(function () {});
|
|
1555
2157
|
} catch (e) {}
|
|
1556
2158
|
}, 400);
|
|
@@ -1610,7 +2212,7 @@ window.__ModuleLoader__.load({
|
|
|
1610
2212
|
var tail = bw / 2 - shift;
|
|
1611
2213
|
tail = Math.min(Math.max(tail, 18), bw - 18);
|
|
1612
2214
|
bubble.style.setProperty("--dshw-btx", Math.round(tail) + "px");
|
|
1613
|
-
if (r.top - bh - 6 < margin) bubble.classList.add("dshadb-whale-bubble-below");
|
|
2215
|
+
if (r.top - bh - 6 < margin && (vh() - r.top - r.size - bh - 6 >= margin)) bubble.classList.add("dshadb-whale-bubble-below");
|
|
1614
2216
|
}
|
|
1615
2217
|
function openBubble() {
|
|
1616
2218
|
if (!st.bubbleOn) return;
|
|
@@ -1647,14 +2249,11 @@ window.__ModuleLoader__.load({
|
|
|
1647
2249
|
full = true; applyPeek();
|
|
1648
2250
|
lastLineAt = Date.now();
|
|
1649
2251
|
openBubble();
|
|
1650
|
-
playSound("press");
|
|
1651
2252
|
armHide();
|
|
1652
2253
|
}
|
|
1653
2254
|
function toPeek() {
|
|
1654
2255
|
full = false; applyPeek();
|
|
1655
2256
|
closeBubble();
|
|
1656
|
-
// 只在真的会缩回边缘时才播收回音 (停在屏幕中间时不缩, 也就不该有动静)
|
|
1657
|
-
if (st.side) playSound("release");
|
|
1658
2257
|
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
|
|
1659
2258
|
}
|
|
1660
2259
|
/** 停手 WHALE_IDLE_MS 后自动缩回; 每次互动都重新计时 */
|
|
@@ -1677,7 +2276,7 @@ window.__ModuleLoader__.load({
|
|
|
1677
2276
|
return;
|
|
1678
2277
|
}
|
|
1679
2278
|
var cx = st.left + s / 2;
|
|
1680
|
-
var zone = Math.max(s * 0.75, W * WHALE_EDGE_ZONE); //
|
|
2279
|
+
var zone = Math.min(Math.max(s * 0.75, W * WHALE_EDGE_ZONE), Math.max(1, W / 2 - 1)); // 边缘带宽度, 不超过半屏
|
|
1681
2280
|
if (cx <= zone) { st.side = "left"; st.left = 0; }
|
|
1682
2281
|
else if (cx >= W - zone) { st.side = "right"; st.left = W - s; }
|
|
1683
2282
|
else { st.side = ""; } // 屏幕中间: 不缩
|
|
@@ -1688,6 +2287,8 @@ window.__ModuleLoader__.load({
|
|
|
1688
2287
|
|
|
1689
2288
|
// ---------- 拖拽 ----------
|
|
1690
2289
|
function onDown(e) {
|
|
2290
|
+
// v1.4.0: 面板开着时挂件已被 setLocked 关掉 pointer-events, 这里再兜一道
|
|
2291
|
+
if (root.classList.contains("dshadb-whale-locked")) return;
|
|
1691
2292
|
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
|
|
1692
2293
|
drag = {
|
|
1693
2294
|
id: e.pointerId, sx: e.clientX, sy: e.clientY,
|
|
@@ -1787,9 +2388,14 @@ window.__ModuleLoader__.load({
|
|
|
1787
2388
|
if (full && !drag) toPeek();
|
|
1788
2389
|
};
|
|
1789
2390
|
document.addEventListener("pointerdown", onDocDown);
|
|
2391
|
+
var lastVw = vw();
|
|
1790
2392
|
var onResize = function () {
|
|
2393
|
+
var nowVw = vw();
|
|
1791
2394
|
applyPos();
|
|
1792
|
-
|
|
2395
|
+
// 键盘弹出/收起只改视口高度, 不应触发水平吸附(否则鲸鱼会被吸到边上去);
|
|
2396
|
+
// 只有视口宽度真正变化(横竖屏/分屏)才重新 settle
|
|
2397
|
+
if (st.snapOn && st.side && Math.abs(nowVw - lastVw) > 4) settle(); else applyPeek();
|
|
2398
|
+
lastVw = nowVw;
|
|
1793
2399
|
if (bubbleOpen) placeBubble();
|
|
1794
2400
|
};
|
|
1795
2401
|
window.addEventListener("resize", onResize);
|
|
@@ -1799,16 +2405,24 @@ window.__ModuleLoader__.load({
|
|
|
1799
2405
|
};
|
|
1800
2406
|
document.addEventListener("visibilitychange", onVis);
|
|
1801
2407
|
|
|
2408
|
+
// v1.4.0: 挂件是在面板可能已经开着的时候才挂上来的(用户在设置里刚打开大肥鱼) → 立刻套用锁定态
|
|
2409
|
+
if (whaleLocked) root.classList.add("dshadb-whale-locked");
|
|
1802
2410
|
whaleWidget = {
|
|
1803
|
-
root: root,
|
|
1804
2411
|
applyPatch: applyPatch,
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
2412
|
+
/**
|
|
2413
|
+
* v1.4.0: 我们自己的抽屉/面板开着时, 挂件**不能**再接收触摸。
|
|
2414
|
+
* 挂件是 document.body 的直接子元素 (z-index 9600), 而设置面板嵌在 DSH 的 composer dock 里 ——
|
|
2415
|
+
* 只要面板的某个祖先带 transform/filter/contain, 面板就会被关进一个更低的层叠上下文,
|
|
2416
|
+
* 于是挂件反而盖在面板上面, 拖「身体大小 / 露出比例」滑块时变成**拖挂件**(用户反馈
|
|
2417
|
+
* 「滑动的时候容易把侧边栏拉过来」)。这里只关交互、**不隐藏** ——
|
|
2418
|
+
* 调大小/露出比例时还要看挂件的实时预览。
|
|
2419
|
+
*/
|
|
2420
|
+
setLocked: function (locked) {
|
|
2421
|
+
if (locked) root.classList.add("dshadb-whale-locked");
|
|
2422
|
+
else root.classList.remove("dshadb-whale-locked");
|
|
1809
2423
|
},
|
|
1810
|
-
onContext: function () { /* 峰谷变化下次开泡时自动取用 */ },
|
|
1811
2424
|
dispose: function () {
|
|
2425
|
+
disposed = true;
|
|
1812
2426
|
body.removeEventListener("pointerdown", onDown);
|
|
1813
2427
|
body.removeEventListener("pointermove", onMove);
|
|
1814
2428
|
body.removeEventListener("pointerup", onUp);
|
|
@@ -1819,31 +2433,72 @@ window.__ModuleLoader__.load({
|
|
|
1819
2433
|
if (hideTimer) clearTimeout(hideTimer);
|
|
1820
2434
|
if (bubbleTimer) clearTimeout(bubbleTimer);
|
|
1821
2435
|
if (saveTimer) clearTimeout(saveTimer);
|
|
2436
|
+
if (revealTimer) clearTimeout(revealTimer); // v1.4.1: 卸载后别再回调
|
|
1822
2437
|
if (root.parentNode) root.parentNode.removeChild(root);
|
|
1823
2438
|
whaleWidget = null;
|
|
1824
2439
|
},
|
|
1825
2440
|
};
|
|
1826
2441
|
|
|
1827
2442
|
// ---------- 拉取已存设置 ----------
|
|
2443
|
+
/**
|
|
2444
|
+
* v1.4.1: 拿到保存的设置之前**不显示**挂件。
|
|
2445
|
+
* 旧行为: 先用默认值(scale=1 → 108px、贴右边、top=62% 屏高)画出来, 等 /whale/settings
|
|
2446
|
+
* 回来再改成保存值 —— 真机实测那一下是「往上跳 307px + 从 108px 缩到 65px」,
|
|
2447
|
+
* LayoutShift 记 0.0119(我们这边最大的一笔), 而且冷启动时它发生在首屏之后十几秒
|
|
2448
|
+
* (配置要等一次全量轮询), 用户正好在看设置面板时就以为「打开设置界面才抖」。
|
|
2449
|
+
* 现在: 首帧隐藏 → 设置到位(或失败/2s 兜底)才露出, 鱼直接出现在正确的位置和大小。
|
|
2450
|
+
* ⚠️ 露出必须覆盖**所有**分支(成功/失败/早退), 否则请求异常会让鱼永远不出现。
|
|
2451
|
+
*/
|
|
2452
|
+
var revealed = false;
|
|
2453
|
+
var revealTimer = null;
|
|
2454
|
+
function reveal() {
|
|
2455
|
+
if (revealed) return;
|
|
2456
|
+
revealed = true;
|
|
2457
|
+
if (revealTimer) { clearTimeout(revealTimer); revealTimer = null; }
|
|
2458
|
+
root.style.visibility = "";
|
|
2459
|
+
root.style.transition = ""; // 初始化完成, 恢复过渡动画(拖拽/吸附时平滑)
|
|
2460
|
+
}
|
|
2461
|
+
revealTimer = setTimeout(reveal, 2000); // 兜底: 请求挂住也不能让鱼永远不出现
|
|
1828
2462
|
applyAll();
|
|
1829
|
-
|
|
2463
|
+
fetchT("/api-dashboard/whale/settings", { cache: "no-store" })
|
|
1830
2464
|
.then(function (r) { return r.json(); })
|
|
1831
2465
|
.then(function (d) {
|
|
2466
|
+
if (disposed) return;
|
|
1832
2467
|
if (!d || !d.ok || !d.settings) return;
|
|
1833
|
-
|
|
1834
|
-
|
|
2468
|
+
// 本会话内已拖过/存过位置(缓存非空)时, 不再用服务端旧值覆盖当前落点,
|
|
2469
|
+
// 否则重建(新对话)会把用户刚放好的中间/左边又弹回服务端的旧右侧
|
|
2470
|
+
if (!lastWhaleSettings) {
|
|
2471
|
+
for (var key in WHALE_DEFAULTS) {
|
|
2472
|
+
if (d.settings[key] !== undefined && d.settings[key] !== null) st[key] = d.settings[key];
|
|
2473
|
+
}
|
|
2474
|
+
if (d.settings.left !== undefined) st.left = d.settings.left;
|
|
2475
|
+
if (d.settings.top !== undefined) st.top = d.settings.top;
|
|
1835
2476
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
2477
|
+
lastWhaleSettings = {
|
|
2478
|
+
scale: st.scale, soundOn: st.soundOn, soundSet: st.soundSet, volume: st.volume,
|
|
2479
|
+
bubbleOn: st.bubbleOn, peakMode: st.peakMode, snapOn: st.snapOn,
|
|
2480
|
+
peekRatio: st.peekRatio, left: st.left, top: st.top, side: st.side,
|
|
2481
|
+
};
|
|
1838
2482
|
applyAll();
|
|
1839
2483
|
})
|
|
1840
|
-
.catch(function () {})
|
|
2484
|
+
.catch(function () { /* 网络异常静默, 由 2s 兜底露出 */ })
|
|
2485
|
+
.then(reveal); // 成功 / 失败 / 上面提前 return 都会走到这里
|
|
1841
2486
|
}
|
|
1842
2487
|
function removeWhaleWidget() { if (whaleWidget) whaleWidget.dispose(); }
|
|
2488
|
+
/** 引用计数: 多个 dock 实例并存时, 只有最后一个释放才真正卸载挂件 */
|
|
2489
|
+
function acquireWhaleWidget() { ensureWhaleWidget(); whaleRefs++; }
|
|
2490
|
+
function releaseWhaleWidget() { whaleRefs = Math.max(0, whaleRefs - 1); if (whaleRefs === 0) removeWhaleWidget(); }
|
|
1843
2491
|
/** 设置面板改动后推给活着的挂件 (未挂载时静默忽略, 值已由端点落盘) */
|
|
1844
2492
|
function patchWhaleWidget(patch) {
|
|
1845
2493
|
if (whaleWidget && whaleWidget.applyPatch) whaleWidget.applyPatch(patch);
|
|
1846
2494
|
}
|
|
2495
|
+
/** v1.4.0: 我们自己的抽屉/面板是否开着 —— 开着就锁住挂件的触摸 (见 setLocked 注释)。
|
|
2496
|
+
* 记在模块变量里: 面板开着时挂件才被打开(比如刚在设置里开启大肥鱼)也能立刻套用。 */
|
|
2497
|
+
var whaleLocked = false;
|
|
2498
|
+
function setWhaleLocked(locked) {
|
|
2499
|
+
whaleLocked = !!locked;
|
|
2500
|
+
if (whaleWidget && whaleWidget.setLocked) whaleWidget.setLocked(whaleLocked);
|
|
2501
|
+
}
|
|
1847
2502
|
//#endregion
|
|
1848
2503
|
|
|
1849
2504
|
//#region plugin
|
|
@@ -1887,10 +2542,30 @@ window.__ModuleLoader__.load({
|
|
|
1887
2542
|
// v1.1.0: 大肥鱼互动挂件 — 设置里开启「收养大肥鱼」后挂载/卸载 (纯互动, 不含余额)
|
|
1888
2543
|
const whaleOn = !!(config && config.whaleEnabled);
|
|
1889
2544
|
react.useEffect(() => {
|
|
1890
|
-
if (!whaleOn) {
|
|
1891
|
-
|
|
1892
|
-
return
|
|
2545
|
+
if (!whaleOn) { releaseWhaleWidget(); return; }
|
|
2546
|
+
acquireWhaleWidget();
|
|
2547
|
+
return releaseWhaleWidget;
|
|
1893
2548
|
}, [whaleOn]);
|
|
2549
|
+
// v1.4.0: 看板/详情/设置任一开着 → 锁住大肥鱼挂件的触摸。
|
|
2550
|
+
// 不这么做的话, 挂件(在 body 上, z-index 9600)会盖在设置面板上面,
|
|
2551
|
+
// 拖「身体大小 / 露出比例」滑块时实际拖的是挂件。
|
|
2552
|
+
/**
|
|
2553
|
+
* 大肥鱼挂件锁定(v1.4.1 定稿):**任何界面下都不锁**。
|
|
2554
|
+
*
|
|
2555
|
+
* 演进过程(别再走回头路):
|
|
2556
|
+
* v1.4.0 `overlayOpen = isSettingsOpen || view !== "bar"` —— 一开设置就锁死,
|
|
2557
|
+
* 维护者反馈「在设置里开启大肥鱼,图片动不了,得关掉才能动」。
|
|
2558
|
+
* v1.4.1a 改成只锁「看板 / 详情」两个整屏抽屉 —— 维护者再次要求全部放开。
|
|
2559
|
+
* v1.4.1b **本版:永不锁定**,挂件在任何界面下都能拖。
|
|
2560
|
+
*
|
|
2561
|
+
* 锁的机制本身(`setWhaleLocked` / `.dshadb-whale-locked` CSS / `onDown` 里那道兜底)
|
|
2562
|
+
* **保留着**:万一以后又出现「面板开着时拖滑块被挂件吃掉」这类反馈,
|
|
2563
|
+
* 把下面这行换成 `setWhaleLocked(view !== "bar")` 即可恢复按界面锁定。
|
|
2564
|
+
*/
|
|
2565
|
+
react.useEffect(() => {
|
|
2566
|
+
setWhaleLocked(false);
|
|
2567
|
+
return () => setWhaleLocked(false);
|
|
2568
|
+
}, []);
|
|
1894
2569
|
// 峰谷时段变化同步给挂件 (台词里的「当前时间段」用它, 不涉及任何金额)
|
|
1895
2570
|
react.useEffect(() => {
|
|
1896
2571
|
updateWhaleContext({ isPeak: !!(config && config.isPeak) });
|
|
@@ -1916,7 +2591,11 @@ window.__ModuleLoader__.load({
|
|
|
1916
2591
|
// v0.5.15: 即时信号 — 订阅模型目录 store, 选中模型瞬间就切平台 (不再等投影/请求)
|
|
1917
2592
|
const modelDirSub = modelStore && typeof modelStore.subscribe === "function" ? modelStore.subscribe : () => () => {};
|
|
1918
2593
|
const modelDirSnap = modelStore && typeof modelStore.getSnapshot === "function" ? modelStore.getSnapshot : () => void 0;
|
|
1919
|
-
const
|
|
2594
|
+
const modelDirState = react.useSyncExternalStore(modelDirSub, modelDirSnap, modelDirSnap);
|
|
2595
|
+
const modelDirModel = modelDirState?.current?.model;
|
|
2596
|
+
const modelDirProvider = modelDirState?.current?.provider;
|
|
2597
|
+
// 当前 provider: 模型目录优先(切换瞬间就更新), 投影兜底
|
|
2598
|
+
const curProvider = modelDirProvider || (cost && cost.currentProvider) || "";
|
|
1920
2599
|
const [didModelDir, setDidModelDir] = react.useState(false);
|
|
1921
2600
|
react.useEffect(() => {
|
|
1922
2601
|
const m = modelDirModel;
|
|
@@ -1945,7 +2624,7 @@ window.__ModuleLoader__.load({
|
|
|
1945
2624
|
react.useEffect(() => {
|
|
1946
2625
|
const cachedAt = window.__dshadbUpdateCheckedAt || 0;
|
|
1947
2626
|
if (Date.now() - cachedAt < 5 * 60 * 1000) return; // 会话内查过就不重复
|
|
1948
|
-
|
|
2627
|
+
fetchT("/api-dashboard/update", { headers: { accept: "application/json" } })
|
|
1949
2628
|
.then((r) => r.json())
|
|
1950
2629
|
.then((d) => { if (d && d.ok) { window.__dshadbUpdateInfo = d; window.__dshadbUpdateCheckedAt = Date.now(); } })
|
|
1951
2630
|
.catch(() => { /* 网络异常静默, 打开设置时可见固定错误文案 */ });
|
|
@@ -1954,32 +2633,37 @@ window.__ModuleLoader__.load({
|
|
|
1954
2633
|
// 本会话消耗节点 (无条件渲染, waiting=true 显示 ~— 等待数据, 否则精确到6位)
|
|
1955
2634
|
let costText = "~—"
|
|
1956
2635
|
let costTitle = t("detail.sessionCost") + " — 等待数据"
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
const fixed = amount >= 1 ? 2 : amount >= 0.01 ? 3 : amount >= 0.001 ? 4 : amount >= 0.0001 ? 5 : 6
|
|
1962
|
-
costText = "~" + sym + amount.toFixed(fixed)
|
|
1963
|
-
costTitle = "本会话消耗: " + sym + amount.toFixed(6) + (cost.models && cost.models.length ? " | 模型: " + cost.models.join(", ") : "")
|
|
2636
|
+
const costFmt = formatSessionCost(cost)
|
|
2637
|
+
if (costFmt !== null) {
|
|
2638
|
+
costText = "~" + costFmt.text
|
|
2639
|
+
costTitle = "本会话消耗: " + costFmt.title + (cost.models && cost.models.length ? " | 模型: " + cost.models.join(", ") : "")
|
|
1964
2640
|
} else if (cost !== undefined && cost.waiting === true) {
|
|
1965
2641
|
costText = "~—"
|
|
1966
2642
|
costTitle = "本会话消耗 — 等待数据, 发消息后自动更新"
|
|
1967
2643
|
}
|
|
1968
2644
|
const costDisp = { text: costText, title: costTitle };
|
|
1969
2645
|
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2646
|
+
// v1.4.0: 子代理消耗行 —— 有子代理时在状态条**下面换行**追加一行,
|
|
2647
|
+
// 按父会话里的创建顺序从左到右排列 (顺序由服务端 subagentCatalog 决定, 这里不排序)。
|
|
2648
|
+
// 金额是「该子代理及其后代」的汇总, 与主板数字同一套计价口径。
|
|
2649
|
+
const subRow = buildSubagentRow(cost);
|
|
2650
|
+
|
|
2651
|
+
return react.createElement("span", { className: "dshadb_barwrap" }, [
|
|
2652
|
+
react.createElement("span", { className: "dshadb_barrow", key: "row" }, [
|
|
2653
|
+
// v0.5.7: 本会话消耗并入同一个胶囊, 不再裸露在外
|
|
2654
|
+
react.createElement(BarReadout, { t, onOpen: openList, onOpenSettings: () => openSettings("basic"), selectedId, onSelect: handleSelect, config, cost: costDisp, provider: curProvider, key: "bar" }),
|
|
2655
|
+
view === "list" ? react.createElement(DashboardDrawer, { isOpen: true, onClose: closeList, t, selectedId, onSelect: handleSelect, onOpenDetail: (id) => { setDetailId(id); setView("detail"); }, onAddRelay: () => openSettings("relays"), onAddCustom: () => openSettings("models"), onOpenSettings: () => openSettings("basic"), config, currentModel, key: "drawer" }) : null,
|
|
2656
|
+
view === "detail" ? react.createElement(PlatformDetail, { isOpen: true, onClose: closeDetail, platformId: detailId, t, useProjection, config, key: "detail" }) : null,
|
|
2657
|
+
isSettingsOpen ? react.createElement(SettingsModal, { isOpen: true, onClose: () => setSettingsOpen(false), onBack: () => { setSettingsOpen(false); setView("list"); }, t, config, initialSection: settingsSection, key: "settings" }) : null,
|
|
2658
|
+
]),
|
|
2659
|
+
subRow,
|
|
1976
2660
|
]);
|
|
1977
2661
|
});
|
|
1978
2662
|
return () => { dispose(); };
|
|
1979
2663
|
});
|
|
1980
2664
|
|
|
1981
2665
|
ctx.effect(() => {
|
|
1982
|
-
const onVisibility = () => { if (!document.hidden) refresh(true).then(schedule, schedule); };
|
|
2666
|
+
const onVisibility = () => { if (!document.hidden) refresh(false, true).then(schedule, schedule); };
|
|
1983
2667
|
document.addEventListener("visibilitychange", onVisibility);
|
|
1984
2668
|
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
1985
2669
|
}, "dsh-api-dashboard: visibility resume");
|