526 lines
103 KiB
HTML
526 lines
103 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
|
||
<title>小白板</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}:root{--bg:#FDFDF9;--bg2:#FFFFFF;--bg3:#F0F0EE;--c:#1C1917;--c2:#57534E;--c3:#78716C;--bd:#E5E5E4;--r4:8px;--r6:12px;--r8:16px;--shadow-sm:0 1px 3px rgba(0,0,0,0.1);--shadow:0 4px 6px -1px rgba(0,0,0,0.1),0 2px 4px -1px rgba(0,0,0,0.06);--shadow-lg:0 20px 25px -5px rgba(0,0,0,0.1),0 10px 10px -5px rgba(0,0,0,0.04)}html,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;background:var(--bg);color:var(--c)}.fc,.fcc{display:flex;align-items:center}.fcc{justify-content:center}.g4{gap:4px}.g6{gap:6px}.g10{gap:10px}.g20{gap:20px}.p12{padding:12px}.r6{border-radius:var(--r6)}.bd{border:1px solid var(--bd)}.bg3{background:var(--bg3)}.fs12{font-size:12px}.fw6{font-weight:600}.c2{color:var(--c2)}.usn{user-select:none}.f1{flex:1}.jsb{justify-content:space-between}.mt6{margin-top:6px}.mt8{margin-top:8px}.mt10{margin-top:10px}.mt12{margin-top:12px}.mt16{margin-top:16px}.mb4{margin-bottom:4px}.mb8{margin-bottom:8px}.mb10{margin-bottom:10px}.mb12{margin-bottom:12px}.lh15{line-height:1.5}.lh165{line-height:1.65}.w100{width:100px}.w140{width:140px}.btn{padding:8px 16px;background:var(--bg2);font-size:13px;font-weight:500;cursor:pointer;border:1px solid var(--bd);border-radius:var(--r4);color:var(--c);transition:all .2s;box-shadow:var(--shadow-sm);display:inline-flex;align-items:center;justify-content:center;gap:6px}.btn:hover{background:#fafafa;border-color:#dcdcdc;box-shadow:var(--shadow);transform:translateY(-1px)}.btn:active{transform:translateY(0)}.btn:disabled{opacity:.5;cursor:not-allowed;box-shadow:none;transform:none}.btn-p{background:var(--c);color:#fff;border-color:var(--c)}.btn-p:hover{background:#2a2a28;color:#fff;border-color:#2a2a28}.btn-due{background:#ffe1e1 !important;border-color:#ff9b9b !important;color:#7a1f1f !important}.btn-s{padding:6px 12px;font-size:12px}.btn-c,.btn-add{padding:0;border-radius:50%;flex-shrink:0}.btn-c{width:32px;height:32px;background:var(--bg3);border-color:var(--bd);color:var(--c2)}.btn-c:hover{background:#e7e5e4;color:var(--c)}.btn-add{width:36px;height:36px;background:var(--bg2);color:var(--c);box-shadow:var(--shadow-sm)}.fold{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r6);margin-bottom:8px;overflow:hidden;box-shadow:var(--shadow-sm);transition:box-shadow .2s}.fold:hover{box-shadow:var(--shadow)}.fold-h{padding:12px 16px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}.fold-a{color:var(--c3);font-size:10px;transition:transform .15s}.fold.exp .fold-a{transform:rotate(180deg)}.fold-b{max-height:0;overflow:hidden;transition:max-height .2s cubic-bezier(0,1,0,1)}.fold.exp .fold-b{max-height:500px;transition:max-height .3s ease-in-out}.side-nav-wrap{position:fixed;left:12px;top:50%;transform:translateY(-50%);z-index:500;display:flex;flex-direction:column;gap:8px}.side-glass{background:rgba(255,255,255,.8);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,0.5);box-shadow:var(--shadow)}.side-nav,.side-menu{display:flex;flex-direction:column;border-radius:24px}.side-nav{gap:6px;padding:8px 6px}.side-menu{position:relative;padding:6px 6px;align-items:center}.side-menu-btn,.nav-i{display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--c3);transition:all .2s}.side-menu-btn{width:32px;height:32px;font-size:12px;border-radius:10px}.nav-i{width:36px;height:36px;border-radius:50%}.nav-i i{font-size:14px}.side-menu-btn:hover,.nav-i:hover{background:var(--bg3);color:var(--c)}.side-menu-btn.act,.nav-i.act{background:var(--c);color:#fff;box-shadow:var(--shadow-sm)}.side-menu-panel{position:absolute;left:100%;top:50%;transform:translateY(-50%);margin-left:12px;display:none;flex-direction:column;gap:6px;padding:8px;background:var(--bg2);border-radius:var(--r6);border:1px solid var(--bd);white-space:nowrap;z-index:600;box-shadow:var(--shadow)}.side-menu-panel.show{display:flex}.side-menu-panel .btn{font-size:11px;padding:6px 12px;width:100%;justify-content:flex-start}.toolbar{height:52px;background:var(--bg2);border-bottom:1px solid var(--bd);padding:0 16px 0 64px;position:relative;z-index:200;box-shadow:var(--shadow-sm)}.toolbar-t{font-size:16px;font-weight:700;margin-right:auto;letter-spacing:-0.5px;color:var(--c)}.toolbar-t span{font-weight:400;color:var(--c3);margin-left:8px;font-size:12px;letter-spacing:0}.main-wrap{width:100%;height:calc(100% - 52px);position:relative}.page{position:absolute;inset:0;background:var(--bg);display:none;overflow:hidden}.page.act{display:block}.page-pad{padding:24px 24px 24px 72px;overflow-y:auto;height:100%}.banner{width:100%;height:180px;border-radius:var(--r8);overflow:hidden;margin-bottom:24px;position:relative;background:var(--bg3);box-shadow:var(--shadow-sm)}.banner img{width:100%;height:100%;object-fit:cover}.banner-ov{position:absolute;inset:0;background:linear-gradient(transparent 40%,rgba(0,0,0,.6));display:flex;align-items:flex-end;padding:20px}.banner-ov div{color:#fff;font-size:14px;font-weight:500;text-shadow:0 2px 4px rgba(0,0,0,0.3)}.sec-t{font-size:13px;color:var(--c3);margin-bottom:12px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase}.sec-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.sec-hd .sec-t{margin-bottom:0}.news-sec{margin-bottom:32px}.news-t{font-size:14px;font-weight:600;color:var(--c)}.news-time{font-size:11px;color:var(--c3)}.fold.exp .news-b{padding:0 16px 16px}.news-b p{font-size:13px;color:var(--c2);line-height:1.7}.user-guide{padding:20px;background:var(--bg2);border-radius:var(--r6);border:1px solid var(--bd);margin-bottom:24px;box-shadow:var(--shadow-sm)}.user-guide-state{font-size:14px;font-weight:500;margin-bottom:12px;color:var(--c)}.user-guide-actions{display:flex;flex-direction:column;gap:8px}.user-guide-action{padding:10px 14px;background:var(--bg);border:1px solid var(--bd);border-radius:var(--r4);font-size:13px;color:var(--c2);cursor:pointer;transition:all .2s}.user-guide-action:hover{background:var(--bg2);border-color:var(--c3)}#mapWrap{width:100%;height:100%;background:var(--bg);cursor:grab;overflow:hidden;position:relative;touch-action:none}#inner,#lines{position:absolute;width:4000px;height:4000px;transform-origin:0 0}#lines{pointer-events:none}.item{position:absolute;padding:8px 16px;background:var(--bg2);border:1px solid var(--bd);border-radius:20px;font-size:12px;font-weight:600;white-space:nowrap;cursor:pointer;user-select:none;box-shadow:var(--shadow-sm);color:var(--c);transition:transform .1s,border-color .1s}.item.node-main{background:var(--c);color:#fff;border-color:var(--c);z-index:10}.item.node-home{background:#fff;color:var(--c);border:2px solid var(--c);z-index:11}.item.node-sub{background:var(--bg2)}.item:hover{transform:scale(1.05);z-index:20}.item.hl{border-color:var(--c);box-shadow:0 0 0 3px rgba(68,64,60,0.2);z-index:20}.map-act{position:absolute;top:16px;right:16px;z-index:100}#btn-goto{display:none;background:var(--c);color:#fff;border:none;box-shadow:var(--shadow)}#btn-goto.show{display:flex}.map-lbl{position:absolute;top:16px;left:72px;z-index:100;background:var(--bg2);border:1px solid var(--bd);border-radius:20px;padding:8px 16px;font-size:12px;color:var(--c);cursor:pointer;display:flex;align-items:center;box-shadow:var(--shadow-sm)}.map-lbl i:first-child{margin-right:8px;color:var(--c3)}.map-lbl .fa-chevron-down{margin-left:8px;font-size:9px;color:var(--c3)}.map-lbl-sel{position:absolute;opacity:0;cursor:pointer;inset:0;border:none;background:transparent;-webkit-appearance:none;appearance:none}.panel{position:fixed;background:var(--bg2);padding:12px 16px;border-radius:var(--r6);font-size:12px;color:var(--c2);border:1px solid var(--bd);box-shadow:var(--shadow)}#zoom-ind{bottom:20px;right:20px;font-family:monospace}#tip{bottom:20px;left:72px;max-width:280px;line-height:1.6;max-height:220px;overflow-y:auto;display:none}#tip .desc{display:none}#tip .info-w{display:block}#tip.show{display:block}.loc-lk{color:var(--c);font-weight:600;cursor:pointer;border-bottom:1px dashed var(--c3);transition:color .2s}.loc-lk:hover{color:#000;border-bottom-style:solid}.local-map-title{font-size:15px;font-weight:700;color:var(--c);margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid var(--bd)}.info-h{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.info-t{font-weight:700;color:var(--c);font-size:14px}.info-bk{font-size:11px;color:var(--c3);cursor:pointer;padding:4px 8px;border-radius:4px;background:var(--bg3)}.info-c{color:var(--c2);font-size:13px;line-height:1.6}.comm-hd{display:flex;align-items:center;gap:12px;margin-bottom:20px}.comm-tabs{display:flex;gap:4px;background:var(--bg3);padding:4px;border-radius:var(--r6)}.comm-tab{padding:8px 20px;text-align:center;border-radius:var(--r4);cursor:pointer;font-size:12px;font-weight:600;color:var(--c3);transition:all .2s;border:none}.comm-tab:first-child{border-radius:var(--r4)}.comm-tab:last-child{border-radius:var(--r4);border-left:none}.comm-tab.act{background:var(--bg2);color:var(--c);box-shadow:var(--shadow-sm)}.comm-sec{display:none}.comm-sec.act{display:block}.ct-hd{gap:12px}.ct-av,.chat-av{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:600;font-size:14px;flex-shrink:0;box-shadow:var(--shadow-sm)}.ct-info{flex:1;min-width:0}.ct-name{font-size:14px;font-weight:600;color:var(--c)}.ct-st{font-size:12px;color:var(--c3);margin-top:2px}.ct-det{padding:0 16px 16px}.ct-info-text{font-size:13px;color:var(--c2);line-height:1.7;margin:0 0 16px;background:var(--bg);padding:10px;border-radius:var(--r4)}.ct-acts{display:flex;gap:10px}.ct-acts .btn{flex:1;justify-content:center;font-size:12px;height:32px}.empty{text-align:center;padding:60px 20px;color:var(--c3);font-size:13px;background:var(--bg3);border-radius:var(--r6);border:1px dashed var(--bd)}.modal{position:fixed;inset:0;z-index:10000;display:none;justify-content:center;align-items:center;backdrop-filter:blur(2px);transition:all .3s}.modal.act{display:flex}.modal-bd{position:absolute;inset:0;background:rgba(0,0,0,.15)}.modal-p{position:relative;width:90%;max-width:700px;max-height:85vh;background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r8);overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--shadow-lg)}.modal-p.sm{max-width:400px}.modal-p.lg{max-width:800px}.modal-hd,.modal-ft{padding:16px 20px;background:var(--bg2)}.modal-hd{justify-content:space-between;border-bottom:1px solid var(--bd)}.modal-hd h2{font-size:16px;font-weight:700;color:var(--c)}.modal-ft{justify-content:flex-end;gap:12px;border-top:1px solid var(--bd);background:#fafaf9}.modal-x{width:32px;height:32px;background:transparent;cursor:pointer;border:none;border-radius:50%;color:var(--c3);font-size:16px}.modal-x:hover{background:var(--bg3);color:var(--c)}.modal-by{flex:1;overflow-y:auto;padding:24px}.form-g{margin-bottom:20px}.form-l{display:block;font-size:12px;font-weight:600;margin-bottom:8px;color:var(--c2);text-transform:uppercase;letter-spacing:0.5px}.form-in{width:100%;padding:10px 14px;border:1px solid var(--bd);border-radius:var(--r4);font-size:14px;outline:none;background:var(--bg);color:var(--c);transition:all .2s;box-shadow:inset 0 1px 2px rgba(0,0,0,0.03)}.form-in:focus{background:#fff;border-color:var(--c3);box-shadow:0 0 0 3px rgba(0,0,0,0.05)}.form-ta{min-height:100px;resize:vertical;font-family:inherit;line-height:1.6}.goto-d{font-size:14px;color:var(--c);font-weight:500;padding:12px 16px;background:var(--bg3);border-radius:var(--r4);margin-bottom:16px;border:1px solid var(--bd)}.ed-ta{width:100%;min-height:120px;padding:16px;background:var(--bg);border:1px solid var(--bd);border-radius:var(--r4);font-family:"SF Mono",Monaco,Consolas,monospace;font-size:12px;line-height:1.6;color:var(--c);resize:vertical;outline:0;overflow:auto}.ed-ta:focus{background:#fff;border-color:var(--c3)}.ed-preview{margin-top:12px;padding:16px;background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r4);font-size:11px;font-family:"SF Mono",Monaco,Consolas,monospace;white-space:pre-wrap;display:none;color:var(--c2)}.ed-err{padding:12px;background:#fef2f2;border:1px solid #fecaca;border-radius:var(--r4);color:#b91c1c;font-size:13px;margin-top:12px;display:none}.ed-err.vis{display:block}.chat{position:fixed;top:0;right:-420px;width:380px;height:100%;background:var(--bg2);border-left:1px solid var(--bd);z-index:600;display:flex;flex-direction:column;box-shadow:var(--shadow-lg);transition:right .3s cubic-bezier(0.16,1,0.3,1)}.chat.act{right:0}.chat-hd{padding:16px 20px;border-bottom:1px solid var(--bd);display:flex;align-items:center;gap:14px;flex-shrink:0}.chat-t{flex:1;min-width:0}.chat-nm{font-size:15px;font-weight:700;color:var(--c)}.chat-st{font-size:12px;color:var(--c3);margin-top:2px}.chat-hd-acts{display:flex;align-items:center;gap:6px}.chat-compress,.chat-clr,.chat-x{width:36px;height:36px;border:none;background:transparent;cursor:pointer;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--c3);transition:background .2s}.chat-compress:hover,.chat-clr:hover,.chat-x:hover{background:var(--bg3);color:var(--c)}.chat-compress:disabled{opacity:.4;cursor:not-allowed}.chat-divider{width:100%;text-align:center;padding:12px 0;color:var(--c3);font-size:11px;position:relative}.chat-divider:before,.chat-divider:after{content:"";position:absolute;top:50%;width:30%;height:1px;background:var(--bd)}.chat-divider:before{left:0}.chat-divider:after{right:0}.chat-msgs{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:16px;background:var(--bg)}.chat-msg{max-width:85%;padding:12px 16px;border-radius:18px;font-size:14px;line-height:1.6;white-space:pre-wrap;box-shadow:var(--shadow-sm)}.chat-msg.sent{align-self:flex-end;background:var(--c);color:#fff;border-bottom-right-radius:4px}.chat-msg.recv{align-self:flex-start;background:var(--bg2);border-bottom-left-radius:4px;border:1px solid var(--bd);color:var(--c)}.chat-msg.typing{font-style:italic;opacity:.7;background:transparent;box-shadow:none;border:none;padding:0}.chat-in-w{padding:16px;border-top:1px solid var(--bd);display:flex;gap:12px;flex-shrink:0;background:var(--bg2)}.chat-in{flex:1;padding:12px 16px;border:1px solid var(--bd);border-radius:24px;font-size:14px;outline:none;background:var(--bg);transition:all .2s}.chat-in:focus{border-color:var(--c);background:#fff;box-shadow:var(--shadow-sm)}.chat-in:disabled,.chat-send:disabled{opacity:.5;cursor:not-allowed}.chat-send{width:40px;height:40px;border:none;background:var(--c);color:#fff;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--shadow-sm);transition:transform .2s}.chat-send:hover{transform:scale(1.05)}.chat-emp{text-align:center;color:var(--c3);font-size:13px;padding:60px 20px}.loc-list{max-height:300px;overflow-y:auto}.loc-i{padding:14px 16px;border:1px solid var(--bd);border-radius:var(--r6);margin-bottom:10px;cursor:pointer;background:var(--bg2);transition:all .2s}.loc-i:hover{border-color:var(--c3);transform:translateY(-1px)}.loc-i.sel{border-color:var(--c);background:var(--c);color:#fff;box-shadow:var(--shadow)}.loc-i.sel .loc-i-info{color:rgba(255,255,255,0.8)}.loc-i-nm{font-size:14px;font-weight:600}.loc-i-info{font-size:12px;opacity:.7;margin-top:4px}.mob-pop{position:fixed;bottom:0;left:0;right:0;background:var(--bg2);border-top:1px solid var(--bd);border-radius:20px 20px 0 0;z-index:101;display:none;flex-direction:column;box-shadow:var(--shadow-lg)}.mob-pop.act{display:flex}.pop-hd{padding:12px;cursor:grab;touch-action:none;flex-shrink:0}.pop-handle{width:40px;height:5px;background:var(--bd);border-radius:3px;margin:0 auto}.pop-ct{flex:1;overflow-y:auto;padding:0 24px 24px;-webkit-overflow-scrolling:touch}.pop-desc{display:none}.pop-info{display:block}.pop-info-h{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.pop-info-t{font-weight:700;font-size:16px;color:var(--c)}.pop-info-bk{font-size:12px;color:var(--c3);cursor:pointer;padding:6px 12px;border-radius:var(--r4);background:var(--bg3)}.pop-h-ind{position:absolute;top:50%;right:8px;transform:translateY(-50%);display:flex;flex-direction:column;gap:4px;opacity:0}.mob-pop.drag .pop-h-ind{opacity:1}.pop-h-ind span{width:4px;height:10px;background:var(--bd);border-radius:2px}.pop-h-ind span.act{background:var(--c)}.side-pop{position:fixed;top:44px;right:0;bottom:0;background:var(--bg2);border-left:1px solid var(--bd);z-index:90;display:none;width:12px;transition:width 0.3s cubic-bezier(0.4,0,0.2,1);overflow:hidden}.side-pop.show{display:flex}.side-pop.act{width:332px;box-shadow:var(--shadow-lg)}.side-pop-handle{width:12px;background:var(--bg3);cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;touch-action:none;border-right:1px solid var(--bd);transition:background 0.2s}.side-pop-handle:hover{background:var(--bd)}.side-pop-bar{width:3px;height:36px;background:var(--bd);border-radius:2px}.side-pop-ct{flex:1;overflow-y:auto;overflow-x:hidden;padding:16px;margin-bottom:30vh;min-width:0}.side-pop-hd{font-size:11px;color:var(--c3);margin-bottom:10px}.side-pop-desc{font-size:13px;color:var(--c2);line-height:1.7;margin-top:2em}.set-row{display:flex;align-items:center;gap:8px;margin-bottom:10px}.set-row .form-in{flex:1}.set-row .btn{flex-shrink:0}.set-hint{font-size:11px;color:var(--c3);margin-top:4px}.set-test{display:flex;align-items:center;gap:8px;margin-top:8px}.set-test-res{font-size:12px;padding:6px 10px;border-radius:var(--r4);display:none}.set-test-res.ok{display:block;background:#dcfce7;color:#166534;border:1px solid #86efac}.set-test-res.err{display:block;background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.warn{color:#f80}.settings-body{height:480px;overflow-y:auto;background:var(--bg2);padding:24px}.settings-nav{display:flex;align-items:center;gap:4px;background:var(--bg3);padding:4px;border-radius:var(--r6);height:40px}.set-nav-item{padding:0 16px;height:32px;background:transparent;border:none;border-radius:var(--r4);cursor:pointer;font-size:13px;font-weight:500;color:var(--c3);display:flex;align-items:center;gap:8px;transition:all .2s;white-space:nowrap}.set-nav-item:hover{color:var(--c2)}.set-nav-item.act{background:var(--bg2);color:var(--c);box-shadow:var(--shadow-sm);font-weight:600}.set-tab-page{display:none;animation:fadeIn .3s}.set-tab-page.act{display:block}.set-group{background:#fff;border:1px solid var(--bd);border-radius:var(--r6);padding:20px;margin-bottom:20px;box-shadow:var(--shadow-sm)}.set-group-t{font-size:14px;font-weight:700;color:var(--c);margin-bottom:16px;display:flex;align-items:center;gap:8px;padding-bottom:10px;border-bottom:1px dashed var(--bd)}.tips-box{background:var(--bg3);padding:10px 12px;border-radius:var(--r4);font-size:12px;color:var(--c2);line-height:1.5;margin-top:8px}.btn.w100{width:100%}#data-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px}.data-item{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--bg);border:1px solid var(--bd);border-radius:var(--r6);cursor:pointer;transition:all .2s;position:relative;min-height:60px}.data-item:hover{border-color:var(--c3);transform:translateY(-1px);box-shadow:var(--shadow-sm)}.data-item.sel{background:#fff;border-color:var(--c);box-shadow:0 0 0 1px var(--c) inset}.data-ck{width:20px;height:20px;border:1px solid var(--bd);background:var(--bg3);border-radius:4px;display:flex;align-items:center;justify-content:center;color:transparent;font-size:10px;flex-shrink:0}.data-item.sel .data-ck{background:var(--c);border-color:var(--c);color:#fff}.data-info{flex:1;min-width:0}.data-nm{font-size:13px;font-weight:600;color:var(--c);margin-bottom:2px}.data-desc{font-size:11px;color:var(--c3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.data-edit{width:28px;height:28px;border-radius:4px;border:1px solid transparent;background:transparent;color:var(--c3);display:flex;align-items:center;justify-content:center;font-size:12px;transition:all .2s;cursor:pointer}.data-edit:hover{background:var(--bg3);color:var(--c);border-color:var(--bd)}@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@media (max-width:600px){.modal-p.lg{width:100%;height:100%;max-height:100%;border-radius:0}.settings-body{height:auto;flex:1;padding:16px}.modal-hd{padding:10px 16px}.set-nav-item{padding:0 10px;font-size:12px}.set-nav-item i{display:none}#data-list{grid-template-columns:1fr}.set-row{flex-wrap:wrap}.mt-mob-8{margin-top:8px}}.data-item{display:flex;align-items:flex-start;gap:10px;padding:12px;background:var(--bg);border:1px solid var(--bd);border-radius:var(--r6);margin-bottom:8px;cursor:pointer}.data-item.sel{border-color:var(--c);background:rgba(34,34,34,.05)}.data-ck{width:18px;height:18px;border:2px solid var(--bd);border-radius:var(--r4);display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:2px}.data-item.sel .data-ck{background:var(--c);border-color:var(--c);color:#fff}.data-ck i{font-size:10px;opacity:0}.data-item.sel .data-ck i{opacity:1}.data-info{flex:1;min-width:0}.data-nm{font-size:13px;font-weight:500;margin-bottom:4px}.data-desc{font-size:11px;color:var(--c3);line-height:1.4}.data-edit{width:28px;height:28px;border:1px solid var(--bd);border-radius:var(--r4);background:var(--bg2);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--c3)}#set-model-list{display:none;margin-top:8px}#world-gen-status,#world-sim-status{display:none;color:#4a9}#adv-u1,#adv-a1,#adv-u2,#adv-a2{height:85px}#adv-json{min-height:140px}#data-edit-ta{min-height:300px}#res-msg{margin-bottom:10px;line-height:1.5}#res-record-box{display:none}#res-action{display:none}#res-record{max-height:200px;overflow-y:auto;background:var(--bg3);padding:8px;border-radius:var(--r4);font-size:12px;white-space:pre-wrap;word-break:break-all}.adv-modal .modal-p{max-width:900px;height:80vh;max-height:800px;display:flex;flex-direction:column;padding:0;background:var(--bg2);border-radius:var(--r8);box-shadow:var(--shadow-lg);border:1px solid var(--bd)}.adv-modal .modal-hd{padding:16px 24px;border-bottom:1px solid var(--bd);background:var(--bg2);flex-shrink:0}.adv-modal .modal-hd h2{font-size:18px;font-weight:700;color:var(--c);letter-spacing:-0.5px}.adv-layout{display:flex;flex:1;overflow:hidden;height:100%}.adv-sidebar{width:260px;background:var(--bg3);border-right:1px solid var(--bd);padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:24px;flex-shrink:0}.adv-sidebar .form-g{margin-bottom:0}.adv-sidebar .set-hint{font-size:12px;color:var(--c3);line-height:1.6}.adv-main{flex:1;padding:24px 32px;overflow-y:auto;background:var(--bg2);display:flex;flex-direction:column;gap:32px;min-width:0}.adv-var-group-title{font-size:12px;font-weight:700;color:var(--c2);text-transform:uppercase;letter-spacing:0.5px;margin:16px 0 8px 0;padding-bottom:4px;border-bottom:1px solid var(--bd)}.adv-var-item{font-size:12px;color:var(--c2);padding:4px 0;display:flex;flex-direction:column;gap:2px}.adv-var-item code{font-family:"SF Mono",Monaco,Consolas,monospace;background:rgba(0,0,0,0.06);padding:2px 5px;border-radius:4px;color:var(--c);font-size:11px;align-self:flex-start;border:1px solid rgba(0,0,0,0.05)}.adv-var-desc{color:var(--c3);font-size:11px;line-height:1.4;padding-left:2px}.adv-section{background:var(--bg);border:1px solid var(--bd);border-radius:var(--r6);padding:20px;box-shadow:var(--shadow-sm)}.adv-h-title{font-size:14px;font-weight:700;color:var(--c);margin-bottom:16px;display:flex;align-items:center;gap:8px}.adv-h-title::before{content:'';width:4px;height:16px;background:var(--c);border-radius:2px;display:block}.uaua-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.ed-ta-adv{font-family:"SF Mono",Monaco,Consolas,monospace;font-size:12px;line-height:1.5;padding:12px;border:1px solid var(--bd);border-radius:var(--r4);background:#fff;width:100%;resize:vertical;min-height:120px;transition:border-color 0.2s;outline:none}.ed-ta-adv:focus{border-color:var(--c);box-shadow:0 0 0 2px rgba(0,0,0,0.05)}.adv-tag-list{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}.adv-tag{font-size:10px;padding:4px 8px;background:var(--bg2);border:1px solid var(--bd);border-radius:12px;color:var(--c2);cursor:pointer;transition:all 0.2s;user-select:none}.adv-tag:hover{border-color:var(--c3);color:var(--c);background:#fff}@media(max-width:768px){.adv-layout{flex-direction:column;overflow-y:auto}.adv-sidebar{width:100%;height:auto;border-right:none;border-bottom:1px solid var(--bd);padding:16px}.adv-main{overflow:visible;padding:16px}.uaua-grid{grid-template-columns:1fr}}@media(max-width:550px){.chat{width:100%;right:-100%}.side-pop{bottom:0}.side-nav-wrap{left:6px}.side-nav{padding:6px 4px}.side-menu{padding:5px 4px}.nav-i{width:28px;height:28px}.nav-i i{font-size:11px}.toolbar{padding:0 12px 0 56px}.toolbar-t span{display:none}.btn{padding:6px 10px;font-size:11px}.modal-p{max-width:100%;max-height:100%;border-radius:0}#tip{display:none !important}.page-pad{padding:14px 14px 14px 48px}.map-lbl{left:48px}}
|
||
</style></style>
|
||
</head>
|
||
|
||
<body>
|
||
<!-- Side Nav -->
|
||
<div class="side-nav-wrap">
|
||
<div class="side-menu side-glass">
|
||
<div class="side-menu-btn" id="btn-side-menu-toggle" title="快捷操作"><i class="fa-solid fa-ellipsis"></i></div>
|
||
<div class="side-menu-panel" id="side-menu-panel">
|
||
<button class="btn btn-s fc g4" id="btn-gen-local-map"><i class="fa-solid fa-plus"></i>局部地图</button>
|
||
<button class="btn btn-s fc g4" id="btn-simulate"><i class="fa-solid fa-rotate"></i>世界推演</button>
|
||
<button class="btn btn-s fc g4" id="btn-gen-local-scene"><i
|
||
class="fa-solid fa-feather-pointed"></i>局部剧情</button>
|
||
</div>
|
||
</div>
|
||
<nav class="side-nav side-glass">
|
||
<div class="nav-i" data-p="world"><i class="fa-solid fa-earth-americas"></i></div>
|
||
<div class="nav-i act" data-p="map"><i class="fa-solid fa-map"></i></div>
|
||
<div class="nav-i" data-p="comm"><i class="fa-solid fa-phone"></i></div>
|
||
</nav>
|
||
</div>
|
||
|
||
<!-- Toolbar -->
|
||
<div class="toolbar fc g10 usn">
|
||
<div class="toolbar-t">小白板<span>预测试</span></div>
|
||
<button class="btn btn-s fc g6" id="btn-deduce"><i class="fa-solid fa-wand-magic-sparkles"></i>生成</button>
|
||
<button class="btn btn-s fc g6" id="btn-settings"><i class="fa-solid fa-gear"></i>设置</button>
|
||
<button class="btn btn-c fcc" id="btn-close">✕</button>
|
||
</div>
|
||
|
||
<!-- Main -->
|
||
<div class="main-wrap">
|
||
<!-- World -->
|
||
<div class="page page-pad" id="page-world">
|
||
<div class="banner">
|
||
<img src="https://picsum.photos/800/300" alt="" />
|
||
<div class="banner-ov">
|
||
<div>探索未知的世界...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="news-sec">
|
||
<div class="sec-hd">
|
||
<h3 class="sec-t">最新消息</h3>
|
||
<button class="btn btn-add fcc" id="btn-refresh-world-news" title="只刷新世界新闻"><i
|
||
class="fa-solid fa-rotate"></i></button>
|
||
</div>
|
||
<div id="news-list"></div>
|
||
</div>
|
||
|
||
<div class="user-guide" id="user-guide">
|
||
<h3 class="sec-t">当前状态</h3>
|
||
<div class="user-guide-state" id="ug-state">尚未生成世界数据...</div>
|
||
<h3 class="sec-t mt12">行动指南</h3>
|
||
<div class="user-guide-actions" id="ug-actions">
|
||
<div class="user-guide-action">等待世界生成...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Map -->
|
||
<div class="page act" id="page-map">
|
||
<div id="mapWrap">
|
||
<div class="map-lbl" id="map-lbl">
|
||
<i class="fa-solid fa-map-location-dot"></i>
|
||
<span id="map-lbl-t">大地图</span>
|
||
<i class="fa-solid fa-chevron-down"></i>
|
||
<select id="map-lbl-select" class="map-lbl-sel"></select>
|
||
</div>
|
||
<div class="map-act">
|
||
<button class="btn btn-s btn-p fc g6" id="btn-goto"><i class="fa-solid fa-location-arrow"></i><span
|
||
id="goto-t">前往</span></button>
|
||
</div>
|
||
<div id="inner"><svg id="lines"></svg></div>
|
||
</div>
|
||
|
||
<div class="panel" id="zoom-ind">100%</div>
|
||
<div class="panel" id="tip">
|
||
<div class="desc" id="desc"></div>
|
||
<div class="info-w">
|
||
<div class="info-h">
|
||
<div class="info-t" id="info-t"></div>
|
||
<div class="info-bk" id="info-bk">← 返回</div>
|
||
</div>
|
||
<div class="info-c" id="info-c"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Contacts -->
|
||
<div class="page page-pad" id="page-comm">
|
||
<div class="comm-hd">
|
||
<div class="comm-tabs">
|
||
<div class="comm-tab act" data-t="stranger">陌路人</div>
|
||
<div class="comm-tab" data-t="contact">联络人</div>
|
||
</div>
|
||
<button class="btn btn-add fcc" id="btn-refresh-strangers" title="微信摇一摇,寻找遇过的陌生人"><i
|
||
class="fa-solid fa-street-view"></i></button>
|
||
<button class="btn btn-add fcc" id="btn-add-ct"><i class="fa-solid fa-plus"></i></button>
|
||
</div>
|
||
<div id="sec-stranger" class="comm-sec act"></div>
|
||
<div id="sec-contact" class="comm-sec"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chat -->
|
||
<div class="chat" id="chat">
|
||
<div class="chat-hd">
|
||
<div class="chat-av" id="chat-av"></div>
|
||
<div class="chat-t">
|
||
<div class="chat-nm" id="chat-nm"></div>
|
||
<div class="chat-st" id="chat-st"></div>
|
||
</div>
|
||
<div class="chat-hd-acts">
|
||
<button class="chat-compress" id="chat-compress" title="压缩总结"><i
|
||
class="fa-solid fa-compress"></i></button>
|
||
<button class="chat-clr" id="chat-clr" title="清空记录"><i class="fa-solid fa-trash"></i></button>
|
||
<button class="chat-x" id="chat-x"><i class="fa-solid fa-xmark"></i></button>
|
||
</div>
|
||
</div>
|
||
<div class="chat-msgs" id="chat-msgs"></div>
|
||
<div class="chat-in-w">
|
||
<button class="chat-send" id="chat-back" title="回退"><i class="fa-solid fa-rotate-left"></i></button>
|
||
<input type="text" class="chat-in" id="chat-in" placeholder="输入消息..." />
|
||
<button class="chat-send" id="chat-send"><i class="fa-solid fa-paper-plane"></i></button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right Panel -->
|
||
<div class="side-pop" id="side-pop">
|
||
<div class="side-pop-handle" id="side-pop-handle">
|
||
<div class="side-pop-bar"></div>
|
||
</div>
|
||
<div class="side-pop-ct">
|
||
<div class="side-pop-hd fc jsb">
|
||
<span>场景描述</span>
|
||
<button class="btn btn-s fc g4" id="btn-refresh-local-map"><i
|
||
class="fa-solid fa-rotate-right"></i>刷新</button>
|
||
</div>
|
||
<div class="side-pop-desc" id="side-desc"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Mobile Bottom Sheet -->
|
||
<div class="mob-pop" id="mob-pop">
|
||
<div class="pop-h-ind"><span data-l="2"></span><span data-l="1"></span><span data-l="0"></span></div>
|
||
<div class="pop-hd" id="pop-hd">
|
||
<div class="pop-handle"></div>
|
||
</div>
|
||
<div class="pop-ct">
|
||
<div class="pop-desc" id="mob-desc"></div>
|
||
<div class="pop-info">
|
||
<div class="pop-info-h">
|
||
<div class="pop-info-t" id="mob-info-t"></div>
|
||
<div class="pop-info-bk" id="mob-info-bk">← 返回</div>
|
||
</div>
|
||
<div id="mob-info-c"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Settings Modal (Redesigned) -->
|
||
<div class="modal" id="m-settings">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p lg settings-modal">
|
||
<div class="modal-hd fc">
|
||
<nav class="settings-nav">
|
||
<button class="set-nav-item act" onclick="clickTab(this, 'tab-general')"><i
|
||
class="fa-solid fa-sliders"></i> <span class="nav-txt">常规</span></button>
|
||
<button class="set-nav-item" onclick="clickTab(this, 'tab-conn')"><i class="fa-solid fa-link"></i>
|
||
<span class="nav-txt">连接</span></button>
|
||
<button class="set-nav-item" onclick="clickTab(this, 'tab-data')"><i
|
||
class="fa-solid fa-database"></i> <span class="nav-txt">数据</span></button>
|
||
</nav>
|
||
<button class="modal-x fcc">✕</button>
|
||
</div>
|
||
|
||
<div class="settings-body">
|
||
<!-- Content -->
|
||
<div class="settings-content">
|
||
<!-- Tab: General -->
|
||
<div class="set-tab-page act" id="tab-general">
|
||
<div class="set-group">
|
||
<div class="set-group-t">运行模式</div>
|
||
<div class="form-g mb4">
|
||
<select class="form-in" id="set-mode">
|
||
<option value="story">故事模式 (Story Mode)</option>
|
||
<option value="assist">辅助模式 (Assist Mode)</option>
|
||
</select>
|
||
<div class="tips-box"><i class="fa-solid fa-circle-info"></i>
|
||
故事模式包含完整的世界模拟;辅助模式仅用于生成辅助地图/剧情。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="set-group">
|
||
<div class="set-group-t">剧情控制</div>
|
||
<div class="set-row g20">
|
||
<div class="form-g f1">
|
||
<label class="form-l">当前阶段 (Stage)</label>
|
||
<input type="number" class="form-in" id="set-stage" min="0" max="10" value="0" />
|
||
</div>
|
||
<div class="form-g f1">
|
||
<label class="form-l">偏离分数 (Deviation)</label>
|
||
<input type="number" class="form-in" id="set-deviation" min="0" max="100"
|
||
value="0" />
|
||
</div>
|
||
</div>
|
||
<div class="form-g">
|
||
<label class="form-l">推演倒计时 (Sim Countdown)</label>
|
||
<div class="fc g10">
|
||
<input type="number" class="form-in f1" id="set-sim-target" value="5" />
|
||
<span class="fs12 c2">行动数</span>
|
||
</div>
|
||
<div class="set-hint">当数值≤0时提醒进行世界推演。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="set-group">
|
||
<div class="set-group-t">历史记录</div>
|
||
<div class="set-test">
|
||
<label class="form-l">上下文楼层数</label>
|
||
<input type="number" class="form-in w100" id="set-history-count" min="0" max="200"
|
||
value="50" />
|
||
<label class="fc g4 fs12 c2 usn" style="margin-left:auto">
|
||
<input type="checkbox" id="set-use-stream" /> <span style="font-weight:600">启用流式
|
||
(Streaming)</span>
|
||
</label>
|
||
</div>
|
||
<div class="set-test-res" id="test-res"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab: Connection -->
|
||
<div class="set-tab-page" id="tab-conn">
|
||
<div class="set-group">
|
||
<div class="set-group-t">API 设置</div>
|
||
<div class="form-g">
|
||
<label class="form-l">API 端点 (URL)</label>
|
||
<input type="text" class="form-in" id="set-api-url" placeholder="默认使用 SillyTavern 设置" />
|
||
</div>
|
||
<div class="form-g">
|
||
<label class="form-l">API 密钥 (Key)</label>
|
||
<input type="password" class="form-in" id="set-api-key" placeholder="如需覆盖请填写" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="set-group">
|
||
<div class="set-group-t">模型选择 (Model)</div>
|
||
<div class="set-row g10">
|
||
<input type="text" class="form-in f1" id="set-model" placeholder="Model ID" />
|
||
<button class="btn btn-s" id="btn-fetch-models">获取列表</button>
|
||
</div>
|
||
<select class="form-in mt6" id="set-model-list"></select>
|
||
<div class="mt10">
|
||
<button class="btn btn-s btn-p w100" id="btn-test-conn">测试连接</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab: Data -->
|
||
<div class="set-tab-page" id="tab-data">
|
||
<div class="set-group">
|
||
<div class="set-group-t">NPC 生成设置</div>
|
||
<div class="set-row g20">
|
||
<div class="form-g f1">
|
||
<label class="form-l">插入位置</label>
|
||
<select class="form-in" id="set-npc-position">
|
||
<option value="0">Char 前</option>
|
||
<option value="1">Char 后</option>
|
||
<option value="2">AN 前</option>
|
||
<option value="3">AN 后</option>
|
||
<option value="5">EM 前</option>
|
||
<option value="6">EM 后</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-g f1">
|
||
<label class="form-l">条目顺序</label>
|
||
<input type="number" class="form-in" id="set-npc-order" min="0" max="1000"
|
||
value="100" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="set-group">
|
||
<div class="set-group-t">预设数据 (Presets)</div>
|
||
<div class="set-hint mb12">勾选要写入预设的数据块:</div>
|
||
<div id="data-list" class="data-grid"></div>
|
||
</div>
|
||
|
||
<div class="set-group">
|
||
<div class="set-group-t">高级模板</div>
|
||
<div class="set-hint mb10">自定义提示词与JSON格式。</div>
|
||
<button class="btn btn-s w100" id="btn-adv-prompts"><i class="fa-solid fa-pen"></i>
|
||
编辑高级模板</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-ft fc">
|
||
<button class="btn btn-s m-cancel">取消</button>
|
||
<button class="btn btn-s btn-p" id="set-save">保存设置</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Advanced Prompts Modal -->
|
||
<div class="modal adv-modal" id="m-adv-prompts">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p">
|
||
<div class="modal-hd fc">
|
||
<h2>高级设置 · 模板编辑</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="adv-layout">
|
||
<!-- Sidebar -->
|
||
<div class="adv-sidebar">
|
||
<div class="form-g">
|
||
<label class="form-l">选择模板</label>
|
||
<select class="form-in" id="adv-key"></select>
|
||
<div class="set-hint mt8">优先级:角色卡 > 原保存 > 默认。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content -->
|
||
<div class="adv-main">
|
||
<div class="adv-section">
|
||
<div class="adv-h-title">UAUA 提示词 (JS Function String)</div>
|
||
<div class="uaua-grid">
|
||
<div class="form-g"><label class="form-l">User 1 (u1)</label><textarea class="ed-ta-adv"
|
||
id="adv-u1"></textarea></div>
|
||
<div class="form-g"><label class="form-l">Assistant 1 (a1)</label><textarea
|
||
class="ed-ta-adv" id="adv-a1"></textarea></div>
|
||
<div class="form-g"><label class="form-l">User 2 (u2)</label><textarea class="ed-ta-adv"
|
||
id="adv-u2"></textarea></div>
|
||
<div class="form-g"><label class="form-l">Assistant 2 (a2)</label><textarea
|
||
class="ed-ta-adv" id="adv-a2"></textarea></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="adv-section" id="adv-json-wrap">
|
||
<div class="adv-h-title">JSON Template</div>
|
||
<textarea class="ed-ta-adv" id="adv-json" style="min-height:300px;font-size:13px"></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-ft fc">
|
||
<div class="fc g10 f1">
|
||
<button class="btn btn-s" id="adv-reset"><i class="fa-solid fa-rotate-left"></i> 重置</button>
|
||
<select class="form-in w140" id="adv-scope" title="选择来源">
|
||
<option value="character">角色</option>
|
||
<option value="global">通用</option>
|
||
</select>
|
||
</div>
|
||
<button class="btn btn-s" id="adv-save-global">通用保存</button>
|
||
<button class="btn btn-s btn-p" id="adv-save-char">角色卡保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Data Edit Modal -->
|
||
<div class="modal" id="m-data-edit">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p">
|
||
<div class="modal-hd fc">
|
||
<h2 id="data-edit-title">编辑数据</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="modal-by">
|
||
<textarea class="ed-ta" id="data-edit-ta"></textarea>
|
||
<pre class="ed-preview" id="data-edit-preview"></pre>
|
||
<div class="ed-err" id="data-edit-err"></div>
|
||
</div>
|
||
<div class="modal-ft fc"><button class="btn btn-s m-cancel">取消</button><button class="btn btn-s btn-p"
|
||
id="data-edit-save">保存</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Goto Modal -->
|
||
<div class="modal" id="m-goto">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p sm">
|
||
<div class="modal-hd fc">
|
||
<h2>确认前往</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="modal-by">
|
||
<div class="goto-d" id="goto-d"></div>
|
||
<div class="form-g"><label class="form-l">任务目标(可选)</label><textarea class="form-in form-ta"
|
||
id="goto-task" placeholder="描述你要做什么..."></textarea></div>
|
||
</div>
|
||
<div class="modal-ft fc"><button class="btn btn-s m-cancel">取消</button><button class="btn btn-s btn-p"
|
||
id="goto-ok">确认前往</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Invite Modal -->
|
||
<div class="modal" id="m-invite">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p sm">
|
||
<div class="modal-hd fc">
|
||
<h2>邀请前往</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="modal-by">
|
||
<div class="goto-d" id="inv-t"></div>
|
||
<div class="form-g"><label class="form-l">选择地点</label>
|
||
<div class="loc-list" id="loc-list"></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-ft fc"><button class="btn btn-s m-cancel">取消</button><button class="btn btn-s btn-p"
|
||
id="inv-ok">发送邀请</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- World Gen Modal -->
|
||
<div class="modal" id="m-world-gen">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p">
|
||
<div class="modal-hd fc">
|
||
<h2>世界生成</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="modal-by">
|
||
<div class="form-g">
|
||
<label class="form-l">玩家特殊需求(可选)</label>
|
||
<textarea class="form-in form-ta" id="world-gen-req" rows="4"
|
||
placeholder="例如:希望有更多悬疑元素、增加一个神秘组织..."></textarea>
|
||
<div class="set-hint">AI 会根据当前世界观和你的需求生成完整的世界数据</div>
|
||
</div>
|
||
<div id="world-gen-status" class="set-hint"></div>
|
||
</div>
|
||
<div class="modal-ft fc"><button class="btn btn-s m-cancel">取消</button><button class="btn btn-s btn-p"
|
||
id="world-gen-ok"><i class="fa-solid fa-wand-magic-sparkles"></i> 开始生成</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- World Sim Modal -->
|
||
<div class="modal" id="m-world-sim">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p">
|
||
<div class="modal-hd fc">
|
||
<h2>世界推演</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="modal-by">
|
||
<div class="form-g">
|
||
<div class="set-hint mb12">
|
||
<strong>推演模式</strong>:根据玩家行为和时间流逝,演化世界状态。
|
||
<ul class="lh15" style="margin:8px 0;padding-left:20px;font-size:12px">
|
||
<li>L1/L2 将大幅更新</li>
|
||
<li>L3/L4 适度调整</li>
|
||
<li>L5 保持不变</li>
|
||
<li>Stage 将推进到下一阶段</li>
|
||
</ul>
|
||
</div>
|
||
<div class="set-hint warn">⚠️ 此操作会覆盖当前世界数据,建议先备份</div>
|
||
</div>
|
||
<div id="world-sim-status" class="set-hint"></div>
|
||
</div>
|
||
<div class="modal-ft fc"><button class="btn btn-s m-cancel">取消</button><button class="btn btn-s btn-p"
|
||
id="world-sim-ok"><i class="fa-solid fa-rotate"></i> 开始推演</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Contact Modal -->
|
||
<div class="modal" id="m-add-ct">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p sm">
|
||
<div class="modal-hd fc">
|
||
<h2>添加联络人</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="modal-by">
|
||
<div class="form-g">
|
||
<label class="form-l">UID(世界书条目UID)</label>
|
||
<div class="set-row">
|
||
<input type="text" class="form-in" id="add-uid" placeholder="请输入世界书条目UID" />
|
||
<button class="btn btn-s" id="btn-check-uid"><i class="fa-solid fa-search"></i> 检查</button>
|
||
</div>
|
||
<div class="set-hint">输入角色卡绑定世界书的条目UID</div>
|
||
</div>
|
||
<div class="form-g" id="name-select-group" style="display:none">
|
||
<label class="form-l">选择名字</label>
|
||
<select class="form-in" id="add-name">
|
||
<option value="">-- 请选择 --</option>
|
||
</select>
|
||
</div>
|
||
<div id="uid-check-err" class="ed-err"></div>
|
||
</div>
|
||
<div class="modal-ft fc"><button class="btn btn-s m-cancel">取消</button><button class="btn btn-s btn-p"
|
||
id="add-ct-ok" disabled>确定</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Result Modal -->
|
||
<div class="modal" id="m-result">
|
||
<div class="modal-bd"></div>
|
||
<div class="modal-p sm">
|
||
<div class="modal-hd fc">
|
||
<h2 id="res-title">操作结果</h2><button class="modal-x fcc">✕</button>
|
||
</div>
|
||
<div class="modal-by">
|
||
<div id="res-msg"></div>
|
||
<div id="res-record-box">
|
||
<div class="set-hint mb4">详细记录:</div>
|
||
<pre id="res-record"></pre>
|
||
</div>
|
||
</div>
|
||
<div class="modal-ft fc">
|
||
<button class="btn btn-s" id="res-action"></button>
|
||
<button class="btn btn-s btn-p m-cancel">确定</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const D={stage:0,deviationScore:0,simulationTarget:5,meta:{truth:null,onion_layers:null,timeline:null,user_guide:null},world:{},maps:{outdoor:{nodes:[]},indoor:null},sceneSetup:null,contacts:{strangers:[],contacts:[{name:"{{characterName}}",avatar:"",color:"#555",location:"在线",info:"角色卡联络人",online:!0,worldbookUid:"__CHARACTER_CARD__",messages:[],summarizedCount:0}]}};let charSmsHistory={messages:[],summarizedCount:0,summaries:{}};const $=t=>document.getElementById(t),$$=t=>document.querySelectorAll(t),isMob=()=>innerWidth<=550,escHtml=t=>t.replace(/[&<>"']/g,(t=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[t]))),h=t=>escHtml(String(t??"")),stripXml=t=>t?t.replace(/<(\w+)[^>]*>[\s\S]*?<\/\1>/g,"").replace(/<[^>]+\/?>/g,"").trim():"",parseLinks=t=>h(t).replace(/\*\*([^*]+)\*\*/g,'<span class="loc-lk" data-loc="$1">$1</span>'),PARENT_ORIGIN=(()=>{try{return new URL(document.referrer).origin}catch{return window.location.origin}})(),post=(t,e={})=>parent.postMessage({source:"LittleWhiteBox-OutlineFrame",type:t,...e},PARENT_ORIGIN),syncSimDueUI=()=>{const t=(Number(D.simulationTarget)||0)<=0;$("btn-simulate")?.classList.toggle("btn-due",t),$("world-sim-ok")?.classList.toggle("btn-due",t)},BtnState={load:(t,e)=>{t.disabled=!0,t._o=t.innerHTML,t.innerHTML=`<i class="fa-solid fa-spinner fa-spin"></i> ${e}`},reset:(t,e)=>{t.disabled=!1,t.innerHTML=e||t._o}},Req={_p:{},_id:0,create(t){const e=t+"_"+ ++this._id;return this._p[t]={id:e},e},get(t){return this._p[t]},match(t){const e=t?.split("_")[0];return this._p[e]?.id===t?this._p[e]:null},set(t,e){Object.assign(this._p[t]||{},e)},clear(t){delete this._p[t]}},openM=t=>$(t).classList.add("act"),closeM=t=>$(t).classList.remove("act"),dirMap={north:[0,-1],south:[0,1],east:[1,0],west:[-1,0],northeast:[1,-1],northwest:[-1,-1],southeast:[1,1],southwest:[-1,1]};let playerLocation="未知",curNode=null,drag=!1,scale=1,offX=0,offY=0,seed=123456789,nodes=[],lines=[],anim=!1,chatTgt=null,invTgt=null,selLoc=null,smsGen=!1,selectedMapValue="current";const inner=$("inner"),svg=$("lines"),mapWrap=$("mapWrap"),popup=$("mob-pop"),chat=$("chat"),sidePop=$("side-pop"),rand=()=>(seed=(9301*seed+49297)%233280)/233280,getCurInside=()=>D.maps?.indoor?.[playerLocation]||null,snaps=()=>[$("pop-hd")?.offsetHeight||0,.3*innerHeight,.65*innerHeight];let popH=0,popDrag=!1,popSY=0,popSH=0,popLv=1;const inds=popup.querySelectorAll(".pop-h-ind span"),setPopH=t=>{const e=snaps();popH=Math.max(e[0],Math.min(.85*innerHeight,t)),popup.style.height=popH+"px",popLv=e.map(((t,e)=>[e,Math.abs(popH-t)])).sort(((t,e)=>t[1]-e[1]))[0][0],inds.forEach(((t,e)=>t.classList.toggle("act",e===popLv)))},snapTo=t=>{popLv=Math.max(0,Math.min(2,t)),setPopH(snaps()[popLv])},openPop=(t=1)=>{popup.classList.add("act"),snapTo(t)};$("side-pop-handle").onclick=()=>sidePop.classList.toggle("act"),$("pop-hd").onmousedown=t=>{popDrag=!0,popSY=t.clientY,popSH=popH||snaps()[1],popup.classList.add("drag"),t.preventDefault()},$("pop-hd").ontouchstart=t=>{t.preventDefault(),popDrag=!0,popSY=t.touches[0].clientY,popSH=popH||snaps()[1],popup.classList.add("drag")},document.onmousemove=t=>{popDrag&&setPopH(popSH+popSY-t.clientY)},document.ontouchmove=t=>{popDrag&&t.touches.length&&(t.preventDefault(),setPopH(popSH+popSY-t.touches[0].clientY))};const endDrag=()=>{popDrag&&(popDrag=!1,popup.classList.remove("drag"),snapTo(popLv))};document.onmouseup=endDrag,document.ontouchend=endDrag,document.ontouchcancel=endDrag;const bindLinks=t=>t.querySelectorAll(".loc-lk").forEach((t=>t.onclick=e=>{e.stopPropagation();const s=t.dataset.loc,o=nodes.find((t=>t.name===s));if(o)return panTo(o),void showInfo(o);const a=getCurInside(),n=a?.nodes?.find((t=>t.name===s));n&&($("info-t").textContent=n.name,$("info-c").textContent=n.info||"暂无信息...",$("tip").classList.add("show"),$("btn-goto").classList.remove("show"),isMob()&&($("mob-info-t").textContent=n.name,$("mob-info-c").textContent=n.info||"暂无信息...",popup.classList.contains("act")||openPop(1)))})),bindFold=t=>t.querySelector(".fold-h").onclick=()=>t.classList.toggle("exp");$$(".modal-bd,.modal-x,.m-cancel").forEach((t=>t.onclick=()=>t.closest(".modal").classList.remove("act")));const openChat=t=>{chatTgt=t,$("chat-av").textContent=t.avatar,$("chat-av").style.background=t.color,$("chat-nm").textContent=t.name,$("chat-st").textContent=t.online?"● 在线":t.location,!t.worldbookUid||t.messages&&t.messages.length?renderMsgs():post("LOAD_SMS_HISTORY",{worldbookUid:t.worldbookUid}),chat.classList.add("act"),$("chat-in").focus()},closeChat=()=>{chat.classList.remove("act"),chatTgt=null,smsGen=!1},renderMsgs=()=>{if(!chatTgt)return;const t=chatTgt.messages||[],e=chatTgt.summarizedCount||0;let s="";t.length?t.forEach(((t,o)=>{e>0&&o===e&&(s+='<div class="chat-divider">—— 以上为已总结消息 ——</div>'),s+=`<div class="chat-msg ${"sent"===t.type?"sent":"recv"}${t.typing?" typing":""}"><div>${escHtml(stripXml(t.text))}</div></div>`})):s='<div class="chat-emp">暂无消息,开始聊天吧</div>',$("chat-msgs").innerHTML=s,$("chat-msgs").scrollTop=$("chat-msgs").scrollHeight,$("chat-compress").disabled=t.length-e<2||smsGen,$("chat-back").disabled=!t.length||smsGen},sendMsg=()=>{const t=$("chat-in").value.trim();if(!t||!chatTgt||smsGen)return;chatTgt.messages=chatTgt.messages||[],chatTgt.messages.push({type:"sent",text:t}),$("chat-in").value="",renderMsgs(),smsGen=!0,$("chat-in").disabled=$("chat-send").disabled=$("chat-back").disabled=!0,chatTgt.messages.push({type:"received",text:"正在输入...",typing:!0}),renderMsgs();const e=Req.create("sms");Req.set("sms",{tgt:chatTgt}),post("SEND_SMS",{requestId:e,contactName:chatTgt.name,worldbookUid:chatTgt.worldbookUid,userMessage:t,chatHistory:chatTgt.messages.filter((t=>!t.typing)).slice(-20),summarizedCount:chatTgt.summarizedCount||0})},isCharCardContact=t=>"__CHARACTER_CARD__"===t?.worldbookUid,contactsForSave=()=>(D.contacts.contacts||[]).filter((t=>!isCharCardContact(t))),saveCt=()=>post("SAVE_CONTACTS",{contacts:contactsForSave(),strangers:D.contacts.strangers}),saveChat=t=>post("SAVE_SMS_HISTORY",{worldbookUid:t.worldbookUid,contactName:t.name,messages:t.messages.filter((t=>!t.typing)),summarizedCount:t.summarizedCount||0});$("chat-x").onclick=closeChat,$("chat-back").onclick=()=>{if(!chatTgt||smsGen)return;const t=chatTgt.messages||[];if(t.length){for(t.pop();t[t.length-1]?.typing;)t.pop();chatTgt.summarizedCount=Math.min(chatTgt.summarizedCount||0,t.length),renderMsgs(),saveCt(),chatTgt.worldbookUid&&saveChat(chatTgt)}},$("chat-clr").onclick=()=>{chatTgt&&confirm(`确定要清空与 ${chatTgt.name} 的所有聊天记录吗?`)&&(chatTgt.messages=[],chatTgt.summarizedCount=0,renderMsgs(),saveCt(),chatTgt.worldbookUid&&saveChat(chatTgt))},$("chat-compress").onclick=()=>{if(!chatTgt||smsGen)return;const t=chatTgt.messages||[],e=chatTgt.summarizedCount||0,s=t.slice(e);if(s.length<2)return void alert("至少需要2条未总结的消息才能压缩");if(!confirm(`确定要压缩总结 ${s.length} 条消息吗?`))return;smsGen=!0,$("chat-compress").disabled=$("chat-in").disabled=$("chat-send").disabled=$("chat-back").disabled=!0;const o=Req.create("compress");Req.set("compress",{tgt:chatTgt}),post("COMPRESS_SMS",{requestId:o,contactName:chatTgt.name,worldbookUid:chatTgt.worldbookUid,messages:t.filter((t=>!t.typing)),summarizedCount:e})},$("chat-send").onclick=sendMsg;const chatIn=$("chat-in");["keydown","keypress","keyup"].forEach((t=>chatIn.addEventListener(t,(t=>t.stopPropagation())))),chatIn.addEventListener("keydown",(t=>{"Enter"!==t.key||t.shiftKey||(t.preventDefault(),sendMsg())}));const openInv=t=>{invTgt=t,selLoc=null,$("inv-t").textContent=`邀请:${t.name}`,$("loc-list").innerHTML=D.maps.outdoor.nodes.map((t=>`<div class="loc-i" data-n="${h(t.name)}"><div class="loc-i-nm">${h(t.name)}</div><div class="loc-i-info">${h(t.info||"")}</div></div>`)).join(""),$$("#loc-list .loc-i").forEach((t=>t.onclick=()=>{$$("#loc-list .loc-i").forEach((t=>t.classList.remove("sel"))),t.classList.add("sel"),selLoc=t.dataset.n})),openM("m-invite")};$("inv-ok").onclick=()=>{if(!selLoc||!invTgt)return;const t=$("inv-ok");BtnState.load(t,"询问中...");const e=Req.create("invite"),s=canonicalLoc(selLoc);Req.set("invite",{contact:invTgt,loc:s,btn:t}),post("SEND_INVITE",{requestId:e,contactName:invTgt.name,contactUid:invTgt.worldbookUid,targetLocation:s,smsHistory:(invTgt.messages||[]).map((t=>"sent"===t.type?`{{user}}: ${t.text}`:`${invTgt.name}: ${t.text}`)).join("\n")})};let addCtState={uid:"",name:"",keys:[]};const resetAddCt=()=>{addCtState={uid:"",name:"",keys:[]},$("add-uid").value="",$("add-name").value="",$("add-name").innerHTML='<option value="">-- 请选择 --</option>',$("name-select-group").style.display="none",$("uid-check-err").classList.remove("vis"),$("add-ct-ok").disabled=!0,BtnState.reset($("btn-check-uid"),'<i class="fa-solid fa-search"></i> 检查')},showUidErr=t=>{$("uid-check-err").textContent=t,$("uid-check-err").classList.add("vis")};$("btn-check-uid").onclick=()=>{const t=$("add-uid").value.trim();t?($("uid-check-err").classList.remove("vis"),BtnState.load($("btn-check-uid"),"检查中"),$("name-select-group").style.display="none",$("add-ct-ok").disabled=!0,addCtState.uid=t,post("CHECK_WORLDBOOK_UID",{uid:t,requestId:Req.create("uidck")})):showUidErr("请输入UID")},$("btn-add-ct").onclick=()=>{resetAddCt(),openM("m-add-ct")},$("add-ct-ok").onclick=()=>{const t=addCtState.uid||$("add-uid").value.trim(),e=addCtState.name||$("add-name").value.trim();t&&e&&(D.contacts.contacts.some((e=>e.worldbookUid===t))?showUidErr("该联络人已存在"):(D.contacts.contacts.push({name:e,avatar:e[0],color:"#"+Math.floor(16777215*Math.random()).toString(16).padStart(6,"0"),location:"未知",worldbookUid:t,messages:[]}),saveCt(),closeM("m-add-ct"),render()))};const genAddCt=(t,e,s)=>{BtnState.load(s,"检查中");const o=Req.create("stgwb");Req.set("stgwb",{name:t,info:e,btn:s}),post("CHECK_STRANGER_WORLDBOOK",{requestId:o,strangerName:t})};function canonicalLoc(t){return String(t||"").trim().replace(/^\u90ae\u8f6e/,"")}$("btn-refresh-strangers").onclick=()=>{const t=$("btn-refresh-strangers");BtnState.load(t,"");const e=Req.create("extract");Req.set("extract",{btn:t}),post("EXTRACT_STRANGERS",{requestId:e,existingContacts:D.contacts.contacts,existingStrangers:D.contacts.strangers})},$("world-gen-ok").onclick=()=>{const t=$("world-gen-ok"),e=$("world-gen-status");BtnState.load(t,"生成中"),e.style.display="block",e.textContent="正在生成世界数据,请稍候...",post("GENERATE_WORLD",{requestId:Req.create("wgen"),playerRequests:$("world-gen-req").value.trim()})},$("world-sim-ok").onclick=()=>{if(!D.meta&&!D.timeline&&!D.maps?.outdoor)return void alert("请先生成世界数据,再进行推演");const t=$("world-sim-ok"),e=$("world-sim-status");BtnState.load(t,"推演中"),e.style.display="block",e.style.color="#4a9",e.textContent="正在分析玩家行为并推演世界变化...",post("SIMULATE_WORLD",{requestId:id,currentData:JSON.stringify({meta:D.meta,timeline:D.timeline,world:D.world,maps:D.maps},null,2)})},$("btn-deduce").onclick=()=>openM("m-world-gen"),$("btn-simulate").onclick=()=>openM("m-world-sim"),$("btn-side-menu-toggle").onclick=()=>{const t=$("side-menu-panel"),e=$("btn-side-menu-toggle");t.classList.toggle("show"),e.classList.toggle("act",t.classList.contains("show"))},document.addEventListener("click",(t=>{t.target.closest(".side-menu")||($("side-menu-panel")?.classList.remove("show"),$("btn-side-menu-toggle")?.classList.remove("act"))}));const getWaitingContacts=t=>{const e=canonicalLoc(t);return e?(D.contacts.contacts||[]).filter((t=>t?.waitingAt&&canonicalLoc(t.waitingAt)===e)):[]},finishTravel=(t,e)=>{playerLocation=t,selectedMapValue="current";const s=getWaitingContacts(t);s.forEach((t=>delete t.waitingAt)),saveAll(),render(),hideInfo();const o=$("goto-task")?.value;let a=`{{user}}离开了${e||"上一地点"},来到${t}。${o?"意图:"+o:""}`;s.length&&(a+=` ${s.map((t=>t.name)).join("、")}已经在这里等你了。`),post("EXECUTE_SLASH_COMMAND",{command:`/sendas name="剧情任务" ${a}`})};$("goto-ok").onclick=()=>{if(!curNode)return;const t=$("goto-ok"),e=D.maps?.outdoor?.nodes?.find((t=>t.name===playerLocation)),s={name:playerLocation,info:e?.info||""},o=curNode.name,a=D.maps?.indoor?.[o];if(a?.description)return BtnState.reset(t,"确认前往"),closeM("m-goto"),finishTravel(o,s.name),void switchMapView("current");let n="main"===curNode.type?"main":"home"===curNode.type?"home":"sub";const r=Req.create("scene");Req.set("scene",{node:curNode,prev:s.name}),BtnState.load(t,"生成中"),post("SCENE_SWITCH",{requestId:r,prevLocationName:s.name,prevLocationInfo:s.info,targetLocationName:curNode.name,targetLocationType:n,targetLocationInfo:curNode.data?.info||"",playerAction:$("goto-task").value||""})},$("btn-gen-local-map").onclick=()=>{const t=$("btn-gen-local-map");BtnState.load(t,"生成中");const e=Req.create("localmap");Req.set("localmap",{btn:t}),post("GENERATE_LOCAL_MAP",{requestId:e,outdoorDescription:D.maps?.outdoor?.description||""})},$("btn-refresh-local-map").onclick=()=>{if(!playerLocation)return void showResultModal("提示","请先生成世界数据",!0);const t=$("btn-refresh-local-map"),e=D.maps?.indoor?.[playerLocation],s=e;if(!s)return void showResultModal("提示","当前区域没有局部地图,请先生成",!0);BtnState.load(t,"刷新中");const o=Req.create("localmaprf");Req.set("localmaprf",{btn:t,loc:playerLocation}),post("REFRESH_LOCAL_MAP",{requestId:o,locationName:playerLocation,currentLocalMap:s,outdoorDescription:D.maps?.outdoor?.description||""})},$("btn-gen-local-scene").onclick=()=>{if(!playerLocation||"未知"===playerLocation)return void showResultModal("提示","请先生成世界数据",!0);const t=$("btn-gen-local-scene");BtnState.load(t,"生成中");const e=D.maps?.outdoor?.nodes?.find((t=>t.name===playerLocation)),s=D.maps?.indoor?.[playerLocation],o=s?.description||e?.info||e?.data?.info||"",a=Req.create("localscene");Req.set("localscene",{btn:t,loc:playerLocation}),post("GENERATE_LOCAL_SCENE",{requestId:a,locationName:playerLocation,locationInfo:o})},$("btn-refresh-world-news").onclick=()=>{const t=$("btn-refresh-world-news");if(!t)return;t.disabled=!0,t._o=t._o??t.innerHTML,t.innerHTML='<i class="fa-solid fa-rotate fa-spin"></i>';const e=Req.create("newsrf");Req.set("newsrf",{btn:t}),post("REFRESH_WORLD_NEWS",{requestId:e})};const saveAll=()=>post("SAVE_ALL_DATA",{allData:{meta:D.meta,world:D.world,outdoor:D.maps.outdoor,indoor:D.maps.indoor,sceneSetup:D.sceneSetup,strangers:D.contacts.strangers,contacts:contactsForSave()},playerLocation}),dataKeys=[["meta","大纲","核心真相、洋葱结构、时间线、用户指南",()=>D.meta,t=>D.meta=t],["world","世界资讯","世界新闻等信息",()=>D.world,t=>D.world=t],["outdoor","大地图","室外区域的地点和路线",()=>D.maps.outdoor,t=>D.maps.outdoor=t],["indoor","局部地图","隐藏的室内/局部场景地图",()=>D.maps.indoor,t=>D.maps.indoor=t],["sceneSetup","区域剧情","当前区域的 Side Story",()=>D.sceneSetup,t=>D.sceneSetup=t],["characterContactSms","角色卡短信","角色卡联络人的短信记录",()=>({messages:charSmsHistory?.messages||[],summarizedCount:charSmsHistory?.summarizedCount||0,summaries:charSmsHistory?.summaries||{}}),t=>{t&&"object"==typeof t&&(charSmsHistory={messages:charSmsHistory?.messages||[],summarizedCount:charSmsHistory?.summarizedCount||0,...t||{}})}],["strangers","陌路人","已遇见但未建立联系的角色",()=>D.contacts.strangers,t=>D.contacts.strangers=t],["contacts","联络人","已添加的联系人",()=>contactsForSave(),t=>{const e=(D.contacts.contacts||[]).find(isCharCardContact);D.contacts.contacts=(e?[e]:[]).concat(Array.isArray(t)?t:[])}]];let gSet={apiUrl:"",apiKey:"",model:"",mode:"assist"},dataCk={},editCtx=null,commSet={historyCount:50,npcPosition:0,npcOrder:100,stream:!1},promptDefaults={jsonTemplates:{},promptSources:{}},promptStores={global:{jsonTemplates:{},promptSources:{}},character:{jsonTemplates:{},promptSources:{}}};const reqSet=()=>post("GET_SETTINGS"),renderDataList=()=>{$("data-list").innerHTML=dataKeys.map((([t,e,s])=>`<div class="data-item ${dataCk[t]?"sel":""}" data-k="${t}"><div class="data-ck"><i class="fa-solid fa-check"></i></div><div class="data-info"><div class="data-nm">${e}</div><div class="data-desc">${s}</div></div><button class="data-edit" data-k="${t}" title="编辑"><i class="fa-solid fa-pen"></i></button></div>`)).join(""),$$("#data-list .data-item").forEach((t=>t.onclick=e=>{if(e.target.closest(".data-edit"))return;const s=t.dataset.k;dataCk[s]=!dataCk[s],t.classList.toggle("sel",dataCk[s])})),$$("#data-list .data-edit").forEach((t=>t.onclick=e=>{e.stopPropagation(),openDataEdit(t.dataset.k)}))},ADV_PROMPT_ITEMS=[["sms","短信回复"],["invite","邀请回复"],["npc","NPC 生成"],["stranger","提取陌路人"],["worldGenStep1","大纲生成"],["worldGenStep2","世界生成"],["worldSim","世界推演(故事模式)"],["worldSimAssist","世界推演(辅助模式)"],["worldNewsRefresh","世界新闻刷新"],["sceneSwitch","场景切换"],["localMapGen","局部地图生成"],["localMapRefresh","局部地图刷新"],["localSceneGen","局部剧情生成"],["summary","总结压缩"]],advHasJsonTemplate=t=>{const e=promptDefaults?.jsonTemplates||{};return Object.prototype.hasOwnProperty.call(e,t)},advGetScope=()=>"global"===$("adv-scope")?.value?"global":"character",advStoreHasKey=(t,e)=>{const s=("global"===t?promptStores.global:promptStores.character)||{};return!(!Object.prototype.hasOwnProperty.call(s?.promptSources||{},e)&&!Object.prototype.hasOwnProperty.call(s?.jsonTemplates||{},e))},advGetPromptObj=(t,e="character")=>{const s=promptDefaults?.promptSources||{},o=promptStores?.global?.promptSources||{},a=promptStores?.character?.promptSources||{},n=("global"===e?o[t]||s[t]:a[t]||o[t]||s[t])||{};return{u1:"string"==typeof n.u1?n.u1:"",a1:"string"==typeof n.a1?n.a1:"",u2:"string"==typeof n.u2?n.u2:"",a2:"string"==typeof n.a2?n.a2:""}},advNormalizeDisplayText=t=>{let e=String(t??"").replace(/\r\n/g,"\n").replace(/\r/g,"\n");return!e.includes("\n")&&e.includes("\\n")&&(e=e.replaceAll("\\n","\n")),e.includes("\\t")&&(e=e.replaceAll("\\t","\t")),e.includes("\\`")&&(e=e.replaceAll("\\`","`")),e},advNormalizeSaveText=t=>String(t??"").replace(/\r\n/g,"\n").replace(/\r/g,"\n"),advGetJsonTemplate=(t,e="character")=>{const s=promptDefaults?.jsonTemplates||{},o=promptStores?.global?.jsonTemplates||{},a=promptStores?.character?.jsonTemplates||{};if(!(advHasJsonTemplate(t)||Object.prototype.hasOwnProperty.call(o,t)||Object.prototype.hasOwnProperty.call(a,t)))return"";const n="global"===e?Object.prototype.hasOwnProperty.call(o,t)?o[t]:s[t]:Object.prototype.hasOwnProperty.call(a,t)?a[t]:Object.prototype.hasOwnProperty.call(o,t)?o[t]:s[t];return"string"==typeof n?n:""},advApplyToUI=(t,e="character")=>{const s=advGetPromptObj(t,e),o=(t,e)=>{const s=$(t);s&&(s.value=advNormalizeDisplayText(e))};o("adv-u1",s.u1),o("adv-a1",s.a1),o("adv-u2",s.u2),o("adv-a2",s.a2),$("adv-json-wrap").style.display="";const a=advGetJsonTemplate(t,e),n=$("adv-json");n&&(n.value=String(a??"")),advUpdateVarHelp(t)},advUpdateVarHelp=t=>{$$(".adv-vars-group").forEach((e=>{const s=String(e.dataset.advFor||"").split(",").map((t=>t.trim())).filter(Boolean);e.style.display=!s.length||s.includes(t)?"":"none"}))},advBuildEdits=()=>{const t=t=>advNormalizeSaveText($(t)?.value??"");return{prompt:{u1:t("adv-u1"),a1:t("adv-a1"),u2:t("adv-u2"),a2:t("adv-a2")},jsonTemplate:advNormalizeSaveText($("adv-json")?.value??"")}},advInit=()=>{const t=$("adv-key");if(!t||t._inited)return;t._inited=!0,t.innerHTML=ADV_PROMPT_ITEMS.map((([t,e])=>`<option value="${t}">${e} (${t})</option>`)).join(""),t.onchange=()=>advApplyToUI(t.value,advGetScope());const e=$("adv-scope");e&&!e._inited&&(e._inited=!0,e.onchange=()=>{const t=$("adv-key")?.value;t&&advApplyToUI(t,advGetScope())})},advOpen=()=>{advInit();const t=$("adv-key"),e=t?.value||ADV_PROMPT_ITEMS[0]?.[0];if(e){const t=$("adv-scope");t&&(t.value=advStoreHasKey("character",e)?"character":"global"),advApplyToUI(e,advGetScope())}openM("m-adv-prompts")},advSaveTo=t=>{advInit();const e=$("adv-key")?.value;if(!e)return;const{prompt:s,jsonTemplate:o}=advBuildEdits();post("SAVE_PROMPTS",{scope:t,key:e,prompt:s,jsonTemplate:o}),closeM("m-adv-prompts")},advReset=()=>{advInit();const t=$("adv-key")?.value;t&&(post("SAVE_PROMPTS",{scope:advGetScope(),key:t,reset:!0}),closeM("m-adv-prompts"))},parseJsonLoose=t=>{const e=String(t??"").trim();if(!e)throw new Error("空内容");try{return JSON.parse(e)}catch{}const s=e.match(/```[^\n]*\n([\s\S]*?)\n```/);if(s?.[1]){const t=s[1].trim();try{return JSON.parse(t)}catch{}}const o=(t,s)=>{const o=e.indexOf(t),a=e.lastIndexOf(s);return-1===o||-1===a||a<=o?null:e.slice(o,a+1)},a=o("{","}")??o("[","]");return a?JSON.parse(a):JSON.parse(e)},updateEditPreview=()=>{const t=$("data-edit-preview");t&&(t.style.display="none",t.textContent="")},setEditContent=(t,e)=>{$("data-edit-title").textContent=t,$("data-edit-ta").value=e,$("data-edit-err").classList.remove("vis"),updateEditPreview(),openM("m-data-edit")},openDataEdit=t=>{const e=dataKeys.find((([e])=>e===t));e&&(editCtx={type:"characterContactSms"===t?"charSms":"data",key:t},setEditContent(`编辑 - ${e[1]}`,JSON.stringify(e[3](),null,2)))};$("data-edit-save").onclick=()=>{if(editCtx)try{const t=parseJsonLoose($("data-edit-ta").value);if("data"===editCtx.type){const e=dataKeys.find((([t])=>t===editCtx.key));if(!e)return;e[4](t),render(),saveAll()}else if("charSms"===editCtx.type){const e=t?.summaries??t;if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("需要 summaries 对象");charSmsHistory.summaries=e,post("SAVE_CHAR_SMS_HISTORY",{summaries:e})}closeM("m-data-edit"),editCtx=null}catch(t){$("data-edit-err").textContent=`JSON错误: ${t.message}`,$("data-edit-err").classList.add("vis")}},$("data-edit-ta").addEventListener("input",updateEditPreview);const showTestRes=(t,e)=>{const s=$("test-res");s.textContent=e,s.className="set-test-res "+(t?"ok":"err")},showResultModal=(t,e,s=!1,o=null)=>{$("res-title").textContent=t,$("res-title").style.color=s?"#ff6b6b":"",$("res-msg").textContent=e;const a=$("res-record-box"),n=$("res-record");o?(a.style.display="block",n.textContent="object"==typeof o?JSON.stringify(o,null,2):String(o)):(a.style.display="none",n.textContent="");const r=$("res-action");r.style.display="none",r.textContent="",r.onclick=null,openM("m-result")};function render(){const t=D.world?.news||[];$("news-list").innerHTML=t.length?t.map((t=>`<div class="fold"><div class="fold-h"><div><div class="news-t">${h(t.title)}</div><div class="news-time">${h(t.time||"")}</div></div><i class="fa-solid fa-chevron-down fold-a"></i></div><div class="fold-b news-b"><p>${h(t.content)}</p></div></div>`)).join(""):'<div class="empty">暂无新闻</div>',$$("#news-list .fold").forEach(bindFold);const e=D.meta?.user_guide;e&&($("ug-state").textContent=e.current_state||"未知状态",$("ug-actions").innerHTML=(e.guides||[]).map(((t,e)=>`<div class="user-guide-action" data-idx="${e}">${e+1}. ${h(t)}</div>`)).join("")||'<div class="user-guide-action">暂无行动指南</div>');const s=(t,e)=>(t||[]).length?t.map((t=>`<div class="fold" data-name="${h(t.name||"")}" data-info="${h(t.info||"")}" data-uid="${h(t.worldbookUid||"")}"><div class="fold-h ct-hd fc"><div class="ct-av" style="background:${h(t.color||"")}">${h(t.avatar||"")}</div><div class="ct-info"><div class="ct-name">${h(t.name||"")}</div><div class="ct-st">${t.online?"● 在线":h(t.location)}</div></div><i class="fa-solid fa-chevron-down fold-a"></i></div><div class="fold-b"><div class="ct-det">${t.info?`<div class="ct-info-text">${h(t.info)}</div>`:""}<div class="ct-acts">${e?`<button class="btn btn-s btn-p fc add-btn" data-name="${h(t.name||"")}" data-info="${h(t.info||"")}"><i class="fa-solid fa-user-plus"></i> 添加</button><button class="btn btn-s fc ignore-btn" data-name="${h(t.name||"")}"><i class="fa-solid fa-eye-slash"></i> 忽略</button>`:`<button class="btn btn-s fc msg-btn" data-uid="${h(t.worldbookUid||"")}"><i class="fa-solid fa-message"></i> 短信</button><button class="btn btn-s btn-p fc inv-btn" data-uid="${h(t.worldbookUid||"")}"><i class="fa-solid fa-paper-plane"></i> 邀请</button>`}</div></div></div></div>`)).join(""):'<div class="empty">暂无</div>';if($("sec-stranger").innerHTML=s(D.contacts.strangers,!0),$("sec-contact").innerHTML=s(D.contacts.contacts,!1),$$(".comm-sec .fold").forEach(bindFold),$$(".add-btn").forEach((t=>t.onclick=e=>{e.stopPropagation(),genAddCt(t.dataset.name,t.dataset.info||"",t)})),$$(".ignore-btn").forEach((t=>t.onclick=e=>{e.stopPropagation();const s=D.contacts.strangers.findIndex((e=>e.name===t.dataset.name));s>-1&&(D.contacts.strangers.splice(s,1),saveCt(),render())})),$$(".msg-btn").forEach((t=>t.onclick=e=>{e.stopPropagation();const s=D.contacts.contacts.find((e=>e.worldbookUid===t.dataset.uid));s&&openChat(s)})),$$(".inv-btn").forEach((t=>t.onclick=e=>{e.stopPropagation();const s=D.contacts.contacts.find((e=>e.worldbookUid===t.dataset.uid));s&&openInv(s)})),"current"===selectedMapValue){const t=getCurInside();$("side-desc").innerHTML=t?.description?`<div class="local-map-title">📍 ${h(playerLocation)}</div>`+parseLinks(t.description):parseLinks(D.maps?.outdoor?.description||"")}else $("side-desc").innerHTML=parseLinks(D.maps?.outdoor?.description||"");bindLinks($("side-desc")),$("mob-desc").innerHTML="",renderMapSelector(),renderMap()}function renderMap(){const t=D.maps?.outdoor;if(seed=123456789,inner.querySelectorAll(".item").forEach((t=>t.remove())),svg.innerHTML="",nodes=[],lines=[],!t?.nodes?.length)return;t.nodes.forEach(((t,e)=>{const s=dirMap[t.position]||[0,0],o=Math.hypot(s[0],s[1])||1,a=120*(t.distant||1);nodes.push({id:"n"+e,name:t.name,type:t.type||"sub",pos:t.position,distant:t.distant||1,x:s[0]/o*a,y:s[1]/o*a,data:t})}));const e={};nodes.forEach((t=>(e[t.pos]=e[t.pos]||[]).push(t)));for(let t in e){const s=e[t];if(s.length<=1)continue;const o=dirMap[t]||[0,0],a=Math.hypot(o[0],o[1])||1,n=-o[1]/a,r=o[0]/a,i=(s.length-1)/2;s.sort(((t,e)=>t.distant-e.distant)).forEach(((t,e)=>{t.x+=n*(e-i)*50,t.y+=r*(e-i)*50}))}for(let t=0;t<15;t++)for(let t=0;t<nodes.length;t++)for(let e=t+1;e<nodes.length;e++){const s=nodes[t],o=nodes[e],a=s.x-o.x,n=s.y-o.y,r=Math.hypot(a,n);if(r<80&&r>0){const t=(80-r)/r*.5;s.x+=a*t,s.y+=n*t,o.x-=a*t,o.y-=n*t}}nodes.forEach((t=>{const e=document.createElement("div");e.className=`item node-${t.type}`,e.id=t.id,e.textContent="home"===t.type?"🏠 "+t.name:t.name,e.style.cssText=`left:${t.x+2e3}px;top:${t.y+2e3}px`,e.onclick=e=>{e.stopPropagation(),curNode?.id===t.id?hideInfo():showInfo(t)},inner.appendChild(e)}));for(let t in e){const s=e[t].sort(((t,e)=>t.distant-e.distant));for(let t=0;t<s.length-1;t++)lines.push([s[t],s[t+1]])}const s=Object.values(e).map((t=>t.sort(((t,e)=>t.distant-e.distant))[0])).sort(((t,e)=>Math.atan2(t.y,t.x)-Math.atan2(e.y,e.x)));s.forEach(((t,e)=>lines.push([t,s[(e+1)%s.length]])));const o=(t,e)=>lines.some((([s,o])=>s===t&&o===e||s===e&&o===t));for(let t=0,e=0;e<Math.floor(nodes.length/5)&&t<200;t++){const t=nodes[Math.floor(rand()*nodes.length)],s=nodes[Math.floor(rand()*nodes.length)];t===s||o(t,s)||(lines.push([t,s]),e++)}drawLines()}function drawLines(){svg.innerHTML="";const t=mapWrap.getBoundingClientRect();lines.forEach((([e,s])=>{const o=$(e.id),a=$(s.id);if(!o||!a)return;const n=o.getBoundingClientRect(),r=a.getBoundingClientRect(),i=document.createElementNS("http://www.w3.org/2000/svg","line");i.setAttribute("x1",(n.left+n.width/2-t.left)/scale-offX/scale),i.setAttribute("y1",(n.top+n.height/2-t.top)/scale-offY/scale),i.setAttribute("x2",(r.left+r.width/2-t.left)/scale-offX/scale),i.setAttribute("y2",(r.top+r.height/2-t.top)/scale-offY/scale);const c="main"===e.type&&"main"===s.type||"home"===e.type||"home"===s.type;i.setAttribute("stroke",c?"#333":"#aaa"),i.setAttribute("stroke-width",c?"2":"1"),c||i.setAttribute("stroke-dasharray","4 3"),svg.appendChild(i)}))}$("btn-settings").onclick=()=>{reqSet(),$("set-api-url").value=gSet.apiUrl||"",$("set-api-key").value=gSet.apiKey||"",$("set-model").value=gSet.model||"",$("set-model-list").style.display="none",$("test-res").className="set-test-res",$("set-stage").value=D.stage||0,$("set-deviation").value=D.deviationScore||0,$("set-sim-target").value=D.simulationTarget??5,$("set-mode").value=gSet.mode||"story",$("set-history-count").value=commSet.historyCount||50,$("set-use-stream").checked=!!commSet.stream,$("set-npc-position").value=commSet.npcPosition||0,$("set-npc-order").value=commSet.npcOrder||100,renderDataList(),syncSimDueUI(),openM("m-settings")},$("btn-adv-prompts").onclick=()=>advOpen(),$("adv-save-global").onclick=()=>advSaveTo("global"),$("adv-save-char").onclick=()=>advSaveTo("character"),$("adv-reset").onclick=()=>advReset(),$("btn-fetch-models").onclick=()=>{BtnState.load($("btn-fetch-models"),"加载"),post("FETCH_MODELS",{apiUrl:$("set-api-url").value.trim(),apiKey:$("set-api-key").value.trim()})},$("btn-test-conn").onclick=()=>{$("test-res").className="set-test-res",BtnState.load($("btn-test-conn"),"测试"),post("TEST_CONNECTION",{apiUrl:$("set-api-url").value.trim(),apiKey:$("set-api-key").value.trim(),model:$("set-model").value.trim()})},$("set-save").onclick=()=>{gSet={apiUrl:$("set-api-url").value.trim(),apiKey:$("set-api-key").value.trim(),model:$("set-model").value.trim(),mode:$("set-mode").value||"story"},D.stage=Math.max(0,Math.min(10,parseInt($("set-stage").value,10)||0)),D.deviationScore=Math.max(0,Math.min(100,parseInt($("set-deviation").value,10)||0)),D.simulationTarget=parseInt($("set-sim-target").value,10),Number.isNaN(D.simulationTarget)&&(D.simulationTarget=5),commSet={historyCount:Math.max(0,Math.min(200,parseInt($("set-history-count").value,10)||50)),stream:!!$("set-use-stream").checked,npcPosition:parseInt($("set-npc-position").value,10)||0,npcOrder:Math.max(0,Math.min(1e3,parseInt($("set-npc-order").value,10)||100))};const t={};dataKeys.forEach((([e,,,s])=>{dataCk[e]&&(t[e]=s())})),syncSimDueUI(),post("SAVE_SETTINGS",{globalSettings:gSet,commSettings:commSet,stage:D.stage,deviationScore:D.deviationScore,simulationTarget:D.simulationTarget,playerLocation,dataChecked:dataCk,outlineData:t,allData:{meta:D.meta,timeline:D.timeline,world:D.world,outdoor:D.maps.outdoor,indoor:D.maps.indoor,strangers:D.contacts.strangers,contacts:contactsForSave()}}),closeM("m-settings")},$("btn-close").onclick=()=>post("CLOSE_PANEL"),window.addEventListener("message",(t=>{if(t.origin!==PARENT_ORIGIN||t.source!==parent)return;if("LittleWhiteBox"!==t.data?.source)return;const e=t.data,s=e.type;if("LOAD_SETTINGS"===s){if(e.globalSettings&&(gSet=e.globalSettings),void 0!==e.stage&&(D.stage=e.stage),void 0!==e.deviationScore&&(D.deviationScore=e.deviationScore),void 0!==e.simulationTarget&&(D.simulationTarget=e.simulationTarget),e.playerLocation&&(playerLocation=e.playerLocation),e.commSettings&&(commSet={historyCount:e.commSettings.historyCount??50,npcPosition:e.commSettings.npcPosition??0,npcOrder:e.commSettings.npcOrder??100,stream:!!e.commSettings.stream}),e.dataChecked&&(dataCk=e.dataChecked),e.promptConfig&&(promptDefaults=e.promptConfig.defaults||promptDefaults,e.promptConfig.stores?(promptStores.global=e.promptConfig.stores.global||{jsonTemplates:{},promptSources:{}},promptStores.character=e.promptConfig.stores.character||{jsonTemplates:{},promptSources:{}}):(promptStores.global=e.promptConfig.current||{jsonTemplates:{},promptSources:{}},promptStores.character=promptStores.character||{jsonTemplates:{},promptSources:{}})),e.outlineData){const t=e.outlineData;t.meta&&(D.meta=t.meta),t.world&&(D.world=t.world),t.outdoor&&(D.maps.outdoor=t.outdoor),t.indoor&&(D.maps.indoor=t.indoor),t.sceneSetup&&(D.sceneSetup=t.sceneSetup),t.strangers&&(D.contacts.strangers=t.strangers),t.contacts&&(D.contacts.contacts=t.contacts)}{const t=e.characterContactSmsHistory||{};charSmsHistory={messages:Array.isArray(t.messages)?t.messages:[],summarizedCount:t.summarizedCount||0,summaries:t.summaries||{}}}let t=D.contacts.contacts.find((t=>"__CHARACTER_CARD__"===t.worldbookUid));t||(t=D.contacts.contacts.find((t=>!t.worldbookUid&&"炒饭智能"===t.name)),t?(t.worldbookUid="__CHARACTER_CARD__",t.info="角色卡联络人",t.location="在线",t.online=!0):(D.contacts.contacts.unshift({name:e.characterCardName||"{{characterName}}",avatar:"",color:"#555",location:"在线",info:"角色卡联络人",online:!0,worldbookUid:"__CHARACTER_CARD__",messages:[],summarizedCount:0}),t=D.contacts.contacts[0])),t&&e.characterCardName&&(t.name=e.characterCardName,t.avatar=(e.characterCardName||"")[0]||t.avatar||""),render(),syncSimDueUI(),$("m-settings").classList.contains("act")&&($("set-api-url").value=gSet.apiUrl||"",$("set-api-key").value=gSet.apiKey||"",$("set-model").value=gSet.model||"",$("set-stage").value=D.stage,$("set-deviation").value=D.deviationScore,$("set-sim-target").value=D.simulationTarget??5,$("set-mode").value=gSet.mode||"story",$("set-history-count").value=commSet.historyCount,$("set-use-stream").checked=!!commSet.stream,$("set-npc-position").value=commSet.npcPosition,$("set-npc-order").value=commSet.npcOrder,renderDataList())}else if("PROMPT_CONFIG_UPDATED"===s){if(e.promptConfig&&(promptDefaults=e.promptConfig.defaults||promptDefaults,e.promptConfig.stores?(promptStores.global=e.promptConfig.stores.global||{jsonTemplates:{},promptSources:{}},promptStores.character=e.promptConfig.stores.character||{jsonTemplates:{},promptSources:{}}):(promptStores.global=e.promptConfig.current||{jsonTemplates:{},promptSources:{}},promptStores.character=promptStores.character||{jsonTemplates:{},promptSources:{}}),$("m-adv-prompts").classList.contains("act"))){const t=$("adv-key")?.value;t&&advApplyToUI(t,advGetScope())}}else if("FETCH_MODELS_RESULT"===s){BtnState.reset($("btn-fetch-models"),"获取");const t=$("set-model-list");if(e.error)return t.style.display="none",void showTestRes(!1,"获取模型失败: "+e.error);if(!e.models?.length)return t.style.display="none",void showTestRes(!1,"未找到可用模型");t.innerHTML='<option value="">-- 选择模型 --</option>'+e.models.map((t=>`<option value="${t}">${t}</option>`)).join(""),t.style.display="block",t.onchange=()=>{t.value&&($("set-model").value=t.value)},showTestRes(!0,`找到 ${e.models.length} 个模型`)}else if("TEST_CONN_RESULT"===s)BtnState.reset($("btn-test-conn"),"测试连接"),showTestRes(e.success,e.message);else if("CHECK_WORLDBOOK_UID_RESULT"===s){if(BtnState.reset($("btn-check-uid"),'<i class="fa-solid fa-search"></i> 检查'),!Req.match(e.requestId))return;if(e.error)return void showUidErr(e.error);if(!e.primaryKeys?.length)return void showUidErr("该条目没有主要关键字");addCtState.keys=e.primaryKeys;const t=$("add-name");t.innerHTML='<option value="">-- 请选择 --</option>'+e.primaryKeys.map((t=>`<option value="${t}">${t}</option>`)).join(""),t.onchange=()=>{addCtState.name=t.value,$("add-ct-ok").disabled=!t.value},$("name-select-group").style.display="block",1===e.primaryKeys.length&&(addCtState.name=e.primaryKeys[0],t.value=addCtState.name,$("add-ct-ok").disabled=!1)}else if("SMS_RESULT"===s){const t=Req.get("sms");if(!t||t.id!==e.requestId)return;if(Req.clear("sms"),smsGen=!1,$("chat-in").disabled=$("chat-send").disabled=$("chat-back").disabled=!1,!chatTgt)return;chatTgt.messages=chatTgt.messages.filter((t=>!t.typing)),e.error?chatTgt.messages.push({type:"received",text:`[错误] ${e.error}`}):e.reply&&chatTgt.messages.push({type:"received",text:stripXml(e.reply)}),renderMsgs(),saveCt(),chatTgt.worldbookUid&&saveChat(chatTgt)}else if("SMS_STREAM"===s){const t=Req.get("sms");if(!t||t.id!==e.requestId||!chatTgt)return;const s=chatTgt.messages.find((t=>t.typing));s&&e.text&&(s.text=e.text,renderMsgs())}else if("LOAD_SMS_HISTORY_RESULT"===s){if(!chatTgt||chatTgt.worldbookUid!==e.worldbookUid)return;e.messages?.length&&(chatTgt.messages=e.messages,chatTgt.summarizedCount=e.summarizedCount||0,saveCt()),renderMsgs()}else if("COMPRESS_SMS_RESULT"===s){const t=Req.get("compress");if(!t||t.id!==e.requestId)return;if(Req.clear("compress"),smsGen=!1,$("chat-compress").disabled=$("chat-in").disabled=$("chat-send").disabled=$("chat-back").disabled=!1,!chatTgt)return;if(e.error)return void alert(`压缩失败: ${e.error}`);void 0!==e.newSummarizedCount&&(chatTgt.summarizedCount=e.newSummarizedCount,renderMsgs(),saveCt())}else if("CHECK_STRANGER_WORLDBOOK_RESULT"===s){const t=Req.get("stgwb");if(!t||t.id!==e.requestId)return;const{name:s,info:o,btn:a}=t;if(Req.clear("stgwb"),e.found&&e.worldbookUid){BtnState.reset(a,'<i class="fa-solid fa-user-plus"></i> 添加');const t=D.contacts.strangers.findIndex((t=>t.name===s));if(t>-1){const s=D.contacts.strangers.splice(t,1)[0];D.contacts.contacts.push({name:s.name,avatar:s.avatar||s.name[0],color:s.color||"#"+Math.floor(16777215*Math.random()).toString(16).padStart(6,"0"),location:s.location||"未知",info:s.info||"",worldbookUid:e.worldbookUid,messages:[]}),saveCt(),render()}}else{BtnState.load(a,"生成中");const t=Req.create("npcgen");Req.set("npcgen",{name:s,info:o,btn:a}),post("GENERATE_NPC",{requestId:t,strangerName:s,strangerInfo:o})}}else if("GENERATE_NPC_RESULT"===s){const t=Req.get("npcgen");if(!t||t.id!==e.requestId)return;const{name:s,btn:o}=t;if(Req.clear("npcgen"),BtnState.reset(o,'<i class="fa-solid fa-user-plus"></i> 添加'),e.error)return void showResultModal("生成角色失败","生成 NPC 失败",!0,e.error);if(e.success&&e.worldbookUid){const t=D.contacts.strangers.findIndex((t=>t.name===s));if(t>-1){const o=D.contacts.strangers.splice(t,1)[0],a=e.npcData||{};D.contacts.contacts.push({name:a.name||o.name,avatar:(a.name||o.name)[0],color:o.color||"#"+Math.floor(16777215*Math.random()).toString(16).padStart(6,"0"),location:o.location||"未知",info:a.intro||o.info||"",worldbookUid:e.worldbookUid,messages:[]}),saveCt(),render(),showResultModal("生成成功",`NPC ${s} 已生成并添加到联络人`,!1,e.npcData)}}}else if("EXTRACT_STRANGERS_RESULT"===s){const t=Req.get("extract");if(!t||t.id!==e.requestId)return;const{btn:s}=t;if(Req.clear("extract"),BtnState.reset(s,'<i class="fa-solid fa-rotate"></i>'),e.error)return void showResultModal("提取失败","提取陌路人失败",!0,e.error);if(e.success&&Array.isArray(e.strangers)){if(!e.strangers.length)return void showResultModal("提取结果","没有发现新的陌路人");const t=[...D.contacts.contacts.map((t=>t.name)),...D.contacts.strangers.map((t=>t.name))],s=e.strangers.filter((e=>!t.includes(e.name)));if(!s.length)return void showResultModal("提取结果","提取到的角色都已存在");D.contacts.strangers=D.contacts.strangers.concat(s),saveCt(),render(),showResultModal("提取成功",`成功提取 ${s.length} 个新陌路人`,!1,s)}}else if("GENERATE_WORLD_STATUS"===s){if(!Req.match(e.requestId))return;const t=$("world-gen-status");t.style.display="block",t.style.color="#4a9",t.textContent=e.message}else if("GENERATE_WORLD_RESULT"===s){if(!Req.match(e.requestId))return;Req.clear("wgen");const t=$("world-gen-ok"),s=$("world-gen-status");if(BtnState.reset(t,'<i class="fa-solid fa-wand-magic-sparkles"></i> 开始生成'),e.error){if(s.style.display="none",showResultModal("生成失败","世界生成失败",!0,e.error),String(e.error||"").includes("Step 2")){const e=$("res-action");e.style.display="inline-block",e.textContent="重试 Step2",e.onclick=()=>{closeM("m-result");const e=Req.create("wgen");BtnState.load(t,"重试中"),s.style.display="block",s.style.color="#4a9",s.textContent="准备重试 Step 2/2...",post("RETRY_WORLD_GEN_STEP2",{requestId:e})}}return}if(e.success&&e.worldData){s.style.color="#4a9",s.textContent="生成成功!正在应用数据...";const t=e.worldData;if(t.meta&&(D.meta=t.meta),t.world&&(D.world=t.world),t.maps?.outdoor&&(D.maps.outdoor=t.maps.outdoor),D.stage=0,D.deviationScore=0,t.playerLocation)playerLocation=t.playerLocation;else{const t=D.maps?.outdoor?.nodes?.find((t=>"home"===t.type));playerLocation=t?.name||D.maps?.outdoor?.nodes?.[0]?.name||"未知"}t.maps?.inside&&playerLocation&&(D.maps.indoor=D.maps.indoor||{},D.maps.indoor[playerLocation]=t.maps.inside),selectedMapValue="current",saveAll(),render(),setTimeout((()=>{closeM("m-world-gen"),s.style.display="none",$("world-gen-req").value="",showResultModal("生成成功","世界数据生成完成!Stage 和 Deviation 已重置为 0",!1,e.worldData)}),500)}}else if("SIMULATE_WORLD_RESULT"===s){if(!e.isAuto&&!Req.match(e.requestId))return;if(!e.isAuto){Req.clear("wsim");const t=$("world-sim-ok"),s=$("world-sim-status");if(BtnState.reset(t,'<i class="fa-solid fa-rotate"></i> 开始推演'),e.error)return s.style.display="none",void showResultModal("推演失败","世界推演失败",!0,e.error)}if(e.success&&e.simData){if(!e.isAuto){const t=$("world-sim-status");t.style.color="#4a9",t.textContent="推演成功!正在应用数据..."}const t=e.simData;t.meta&&(D.meta=t.meta),t.world&&(D.world=t.world),t.maps?.outdoor&&(D.maps.outdoor=t.maps.outdoor),D.stage=(D.stage||0)+1,saveAll(),render(),e.isAuto||setTimeout((()=>{closeM("m-world-sim"),$("world-sim-status").style.display="none",showResultModal("推演成功",`世界推演完成!Stage 已推进到 ${D.stage}`,!1,e.simData)}),500)}}else if("REFRESH_WORLD_NEWS_RESULT"===s){const t=Req.get("newsrf");if(!t||t.id!==e.requestId)return;const{btn:s}=t;if(Req.clear("newsrf"),s&&(s.disabled=!1,s.innerHTML=s._o??'<i class="fa-solid fa-rotate"></i>'),e.error)return void showResultModal("刷新失败","世界新闻刷新失败",!0,e.error);e.success&&Array.isArray(e.news)&&(D.world=D.world||{},D.world.news=e.news,saveAll(),render(),showResultModal("刷新成功",`已更新世界新闻(${e.news.length} 条)`,!1,e.news))}else if("SCENE_SWITCH_RESULT"===s){const t=Req.get("scene");if(!t||t.id!==e.requestId)return;const{node:s,prev:o}=t;if(Req.clear("scene"),BtnState.reset($("goto-ok"),"确认前往"),closeM("m-goto"),e.error)return void showResultModal("切换失败","场景切换失败",!0,e.error);if(e.success&&e.sceneData){const t=e.sceneData;if("number"==typeof t.newScore&&(D.deviationScore=t.newScore),t.localMap&&(D.maps.indoor=D.maps.indoor||{},D.maps.indoor[s.name]=t.localMap),t.strangers?.length){const e=new Set((D.contacts.strangers||[]).map((t=>t.name))),s=t.strangers.filter((t=>!e.has(t.name)));D.contacts.strangers=[...D.contacts.strangers||[],...s]}finishTravel(s.name,o),0!==t.scoreDelta&&showResultModal("切换成功",`场景切换完成!\n偏差值变化: ${t.scoreDelta>0?"+":""}${t.scoreDelta} (当前: ${t.newScore})`,!1,e.sceneData)}}else if("REFRESH_LOCAL_MAP_RESULT"===s){const t=Req.get("localmaprf");if(!t||t.id!==e.requestId)return;const{btn:s,loc:o}=t;if(Req.clear("localmaprf"),BtnState.reset(s,'<i class="fa-solid fa-rotate-right"></i>刷新'),e.error)return void showResultModal("刷新失败","刷新局部地图失败",!0,e.error);if(e.success&&e.localMapData){const t=e.localMapData,s=t.name||o||playerLocation||"当前位置";D.maps.indoor=D.maps.indoor||{},D.maps.indoor[s]=t,playerLocation=s,selectedMapValue="current",saveAll(),render(),t.description&&($("side-desc").innerHTML=`<div class="local-map-title">📍 ${h(s)}</div>`+parseLinks(t.description),bindLinks($("side-desc"))),showResultModal("刷新成功",`局部地图已刷新!当前位置: ${s}`,!1,e.localMapData)}}else if("GENERATE_LOCAL_SCENE_RESULT"===s){const t=Req.get("localscene");if(!t||t.id!==e.requestId)return;const{btn:s,loc:o}=t;if(Req.clear("localscene"),BtnState.reset(s,'<i class="fa-solid fa-feather-pointed"></i>局部剧情'),e.error)return void showResultModal("生成失败","局部剧情生成失败",!0,e.error);if(e.success&&e.sceneSetup){D.sceneSetup={...D.sceneSetup||{},...e.sceneSetup||{}},saveAll(),render();const t=String(e.introduce||"").replace(/^\s*(?:\/?\s*(?:sendas|as)\s+name\s*=\s*(?:"[^"]*"|'[^']*'|\S+)\s+)/i,"").replace(/\s+/g," ").trim();t&&post("EXECUTE_SLASH_COMMAND",{command:`/sendas name="剧情任务" ${t}`}),showResultModal("生成成功",`局部剧情已生成:${o||playerLocation}`,!1,e.sceneSetup)}}else if("SEND_INVITE_RESULT"===s){const t=Req.get("invite");if(!t||t.id!==e.requestId)return;const{contact:s,loc:o,btn:a}=t;if(Req.clear("invite"),BtnState.reset(a,"发送邀请"),e.error)return void showResultModal("邀请失败","邀请发送失败",!0,e.error);if(e.success&&e.inviteData){const t=e.inviteData,a=canonicalLoc(t.targetLocation||o||""),n=D.contacts.contacts.find((t=>t&&s&&t.worldbookUid&&s.worldbookUid&&t.worldbookUid===s.worldbookUid))||D.contacts.contacts.find((t=>t&&s&&t.name===s.name))||s;if(n.messages=n.messages||[],n.messages.push({type:"sent",text:`我邀请你前往「${a}」`}),n.messages.push({type:"received",text:t.reply}),t.accepted){const e=canonicalLoc(playerLocation);n.location=a,e&&a&&e===a?(delete n.waitingAt,post("EXECUTE_SLASH_COMMAND",{command:`/sendas name="剧情任务" ${n.name}过来了。`})):n.waitingAt=a,showResultModal("邀请成功",`${n.name} 接受了邀请!\n回复: ${t.reply}`,!1,t)}else showResultModal("邀请被拒",`${n.name} 拒绝了邀请。\n回复: ${t.reply}`,!1,t);saveAll(),n.worldbookUid&&saveChat(n),closeM("m-invite"),render(),openChat(n)}}else if("GENERATE_LOCAL_MAP_RESULT"===s){const t=Req.get("localmap");if(!t||t.id!==e.requestId)return;const{btn:s}=t;if(Req.clear("localmap"),BtnState.reset(s,'<i class="fa-solid fa-plus"></i>局部地图'),e.error)return void showResultModal("生成失败","局部地图生成失败",!0,e.error);if(e.success&&e.localMapData){const t=e.localMapData,s=t.name||"当前位置";D.sceneSetup=null,D.maps.indoor=D.maps.indoor||{},D.maps.indoor[s]=t,playerLocation=s,selectedMapValue="current",saveAll(),render(),t.description&&($("side-desc").innerHTML=`<div class="local-map-title">📍 ${h(s)}</div>`+parseLinks(t.description),bindLinks($("side-desc"))),showResultModal("生成成功",`局部地图生成完成!当前位置: ${s}`,!1,t)}}}));const updateTf=()=>{inner.style.transform=`translate(${offX}px,${offY}px) scale(${scale})`,$("zoom-ind").textContent=Math.round(100*scale)+"%",requestAnimationFrame(drawLines)},initPos=()=>{offX=mapWrap.clientWidth/2-2e3,offY=mapWrap.clientHeight/2-2e3,scale=1,updateTf()};function panTo(t,e=350){if(!t||anim)return;if(!$(t.id))return;const s=-(t.x+2e3)*scale+mapWrap.clientWidth/2,o=-(t.y+2e3)*scale+.25*mapWrap.clientHeight,a=offX,n=offY,r=performance.now();anim=!0,function t(i){const c=Math.min((i-r)/e,1),l=1-Math.pow(1-c,3);offX=a+(s-a)*l,offY=n+(o-n)*l,updateTf(),c<1?requestAnimationFrame(t):anim=!1}(r)}function showInfo(t){if(!t?.data)return;curNode=t,inner.querySelectorAll(".item").forEach((t=>t.classList.remove("hl"))),$(t.id)?.classList.add("hl");const e=t.name===playerLocation;$("btn-goto").classList.toggle("show",!e),e||($("goto-t").textContent=`前往 ${t.name}`);const s=D.maps?.indoor?.[t.name];e&&s?.description?($("side-desc").innerHTML=`<div class="local-map-title">📍 ${h(t.name)}</div>`+parseLinks(s.description),bindLinks($("side-desc"))):($("side-desc").innerHTML=parseLinks(D.maps?.outdoor?.description||""),bindLinks($("side-desc"))),isMob()?($("mob-info-t").textContent=t.name,$("mob-info-c").textContent=e?s?.description||t.data.info||"暂无信息...":t.data.info||"暂无信息...",popup.classList.contains("act")||openPop(1)):($("info-t").textContent=t.name,$("info-c").textContent=t.data.info||"暂无信息...",$("tip").classList.add("show"))}const hideInfo=()=>{curNode=null,inner.querySelectorAll(".item").forEach((t=>t.classList.remove("hl"))),$("btn-goto").classList.remove("show"),$("tip").classList.remove("show")};function renderMapSelector(){const t=$("map-lbl-select");t.innerHTML='<option value="overview">🗺️ 大地图</option>';const e=D.maps?.outdoor?.nodes?.findIndex((t=>t.name===playerLocation)),s=D.maps?.indoor&&D.maps.indoor[playerLocation];if((e>=0||s)&&(t.innerHTML+=`<option value="current">📍 ${h(playerLocation)}(你)</option>`),t.innerHTML+="<option disabled>──────────</option>",D.maps?.outdoor?.nodes?.length&&D.maps.outdoor.nodes.forEach(((e,s)=>{e.name!==playerLocation&&(t.innerHTML+=`<option value="node:${s}">${h(e.name)}</option>`)})),D.maps?.indoor){const e=Object.keys(D.maps.indoor).filter((t=>t!==playerLocation&&!D.maps?.outdoor?.nodes?.some((e=>e.name===t))));e.length&&(t.innerHTML+="<option disabled>── 隐藏地图 ──</option>",e.forEach((e=>t.innerHTML+=`<option value="indoor:${h(e)}">🏠 ${h(e)}</option>`)))}t.value=selectedMapValue,updateMapLabel()}function updateMapLabel(){const t=$("map-lbl-select").value;if("overview"===t)$("map-lbl-t").textContent="大地图";else if("current"===t)$("map-lbl-t").textContent=playerLocation+"(你)";else if(t.startsWith("node:")){const e=parseInt(t.split(":")[1]);$("map-lbl-t").textContent=D.maps?.outdoor?.nodes?.[e]?.name||"未知"}else t.startsWith("indoor:")&&($("map-lbl-t").textContent=t.replace("indoor:",""))}function switchMapView(t){if(selectedMapValue=t,hideInfo(),"overview"===t)$("btn-goto").classList.remove("show"),$("side-desc").innerHTML=parseLinks(D.maps?.outdoor?.description||""),bindLinks($("side-desc")),initPos();else if("current"===t){$("btn-goto").classList.remove("show");const t=getCurInside();t?.description?($("side-desc").innerHTML=`<div class="local-map-title">📍 ${h(playerLocation)}</div>`+parseLinks(t.description),bindLinks($("side-desc"))):($("side-desc").innerHTML=parseLinks(D.maps?.outdoor?.description||""),bindLinks($("side-desc")));const e=nodes.find((t=>t.name===playerLocation));e&&(panTo(e),showInfo(e))}else if(t.startsWith("node:")){const e=parseInt(t.split(":")[1]),s=D.maps?.outdoor?.nodes?.[e];$("side-desc").innerHTML=parseLinks(D.maps?.outdoor?.description||""),bindLinks($("side-desc"));const o=nodes.find((t=>t.name===s?.name));o&&(panTo(o),showInfo(o))}else if(t.startsWith("indoor:")){const e=t.replace("indoor:",""),s=D.maps?.indoor?.[e];e!==playerLocation?($("side-desc").innerHTML=parseLinks(D.maps?.outdoor?.description||""),bindLinks($("side-desc")),curNode={name:e,type:"sub",data:{info:stripXml(s?.description||"")},isIndoor:!0},$("btn-goto").classList.add("show"),$("goto-t").textContent=`前往 ${e}`):($("btn-goto").classList.remove("show"),s?.description&&($("side-desc").innerHTML=`<div class="local-map-title">🏠 ${h(e)}</div>`+parseLinks(s.description),bindLinks($("side-desc"))))}updateMapLabel()}$("info-bk").onclick=hideInfo,$("mob-info-bk").onclick=()=>popup.classList.remove("act"),$("map-lbl-select").onchange=t=>switchMapView(t.target.value);let sx,sy,lastDist=0,lastCX=0,lastCY=0;mapWrap.onmousedown=t=>{anim||t.target.closest(".map-act,.map-lbl")||(t.target.classList.contains("item")||hideInfo(),drag=!0,sx=t.clientX,sy=t.clientY,mapWrap.style.cursor="grabbing")},mapWrap.onmousemove=t=>{drag&&(offX+=t.clientX-sx,offY+=t.clientY-sy,sx=t.clientX,sy=t.clientY,updateTf())},mapWrap.onmouseup=mapWrap.onmouseleave=()=>{drag=!1,mapWrap.style.cursor="grab"},mapWrap.onwheel=t=>{if(anim)return;t.preventDefault();const e=Math.max(.3,Math.min(3,scale+(t.deltaY>0?-.1:.1))),s=mapWrap.getBoundingClientRect(),o=t.clientX-s.left,a=t.clientY-s.top,n=e/scale;offX=o-(o-offX)*n,offY=a-(a-offY)*n,scale=e,updateTf()},mapWrap.ontouchstart=t=>{if(anim||t.target.closest(".map-act,.map-lbl"))return;const e=t.target.closest(".item");e?e._ts={x:t.touches[0].clientX,y:t.touches[0].clientY,t:Date.now()}:(hideInfo(),1===t.touches.length?(drag=!0,sx=t.touches[0].clientX,sy=t.touches[0].clientY):2===t.touches.length&&(drag=!1,lastDist=Math.hypot(t.touches[0].clientX-t.touches[1].clientX,t.touches[0].clientY-t.touches[1].clientY),lastCX=(t.touches[0].clientX+t.touches[1].clientX)/2,lastCY=(t.touches[0].clientY+t.touches[1].clientY)/2))},mapWrap.ontouchmove=t=>{const e=t.target.closest(".item");if(e&&e._ts)Math.hypot(t.touches[0].clientX-e._ts.x,t.touches[0].clientY-e._ts.y)>10&&(delete e._ts,drag=!0,sx=t.touches[0].clientX,sy=t.touches[0].clientY);else if(1===t.touches.length&&drag)t.preventDefault(),offX+=t.touches[0].clientX-sx,offY+=t.touches[0].clientY-sy,sx=t.touches[0].clientX,sy=t.touches[0].clientY,updateTf();else if(2===t.touches.length){t.preventDefault();const e=Math.hypot(t.touches[0].clientX-t.touches[1].clientX,t.touches[0].clientY-t.touches[1].clientY),s=(t.touches[0].clientX+t.touches[1].clientX)/2,o=(t.touches[0].clientY+t.touches[1].clientY)/2,a=Math.max(.3,Math.min(3,scale*(e/lastDist))),n=mapWrap.getBoundingClientRect(),r=s-n.left,i=o-n.top,c=a/scale;offX=r-(r-offX)*c,offY=i-(i-offY)*c,offX+=s-lastCX,offY+=o-lastCY,scale=a,lastDist=e,lastCX=s,lastCY=o,updateTf()}},mapWrap.ontouchend=t=>{const e=t.target.closest(".item");if(e&&e._ts){const s=Date.now()-e._ts.t;if(delete e._ts,s<300){const s=nodes.find((t=>t.id===e.id));s&&(t.preventDefault(),curNode?.id===s.id?hideInfo():showInfo(s))}}drag=!1},$$(".nav-i").forEach((t=>t.onclick=()=>{$$(".nav-i").forEach((t=>t.classList.remove("act"))),$$(".page").forEach((t=>t.classList.remove("act"))),t.classList.add("act"),$(`page-${t.dataset.p}`).classList.add("act");const e="map"===t.dataset.p;sidePop.classList.toggle("show",e),isMob()&&(e?openPop(1):popup.classList.remove("act")),e&&setTimeout((()=>{initPos(),drawLines()}),50)})),$$(".comm-tab").forEach((t=>t.onclick=()=>{$$(".comm-tab").forEach((t=>t.classList.remove("act"))),$$(".comm-sec").forEach((t=>t.classList.remove("act"))),t.classList.add("act"),$(`sec-${t.dataset.t}`).classList.add("act")})),$("btn-goto").onclick=t=>{t.stopPropagation(),curNode&&($("goto-d").textContent=`目的地:${curNode.name}`,$("goto-task").value="",openM("m-goto"))},addEventListener("resize",(()=>requestAnimationFrame(drawLines))),window.clickTab=(t,e)=>{const s=t.closest(".settings-modal");if(!s)return;s.querySelectorAll(".set-nav-item").forEach((t=>t.classList.remove("act"))),t.classList.add("act"),s.querySelectorAll(".set-tab-page").forEach((t=>t.classList.remove("act")));const o=document.getElementById(e);o&&(o.classList.add("act"),o.style.animation="none",o.offsetHeight,o.style.animation=null)},document.addEventListener("DOMContentLoaded",(()=>{render(),initPos(),sidePop.classList.add("show"),sidePop.classList.add("act"),isMob()&&openPop(1),post("FRAME_READY"),setTimeout((()=>{"current"===selectedMapValue&&switchMapView("current")}),100)}));
|
||
</script></script>
|
||
</body>
|
||
|
||
</html>
|