protocol-proxy 2.8.3 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/config-store.js +45 -2
- package/lib/conversation-store.js +108 -0
- package/lib/mcp-client.js +423 -0
- package/lib/prompt-builder.js +94 -0
- package/lib/proxy-server.js +44 -5
- package/lib/skill-store.js +150 -0
- package/package.json +2 -1
- package/public/app.js +1102 -106
- package/public/index.html +250 -8
- package/public/style.css +458 -2
- package/server.js +1774 -191
package/public/index.html
CHANGED
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
|
|
25
25
|
<span>总览</span>
|
|
26
26
|
</a>
|
|
27
|
+
<a href="#" class="nav-item" data-page="assistant">
|
|
28
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
|
|
29
|
+
<span>智控助手</span>
|
|
30
|
+
<span class="nav-badge" style="background:var(--accent);color:#fff">AI</span>
|
|
31
|
+
</a>
|
|
27
32
|
<a href="#" class="nav-item" data-page="proxies">
|
|
28
33
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
|
|
29
34
|
<span>代理管理</span>
|
|
@@ -46,10 +51,15 @@
|
|
|
46
51
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
|
47
52
|
<span>系统日志</span>
|
|
48
53
|
</a>
|
|
49
|
-
<a href="#" class="nav-item" data-page="
|
|
50
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="
|
|
51
|
-
<span
|
|
52
|
-
<span class="nav-badge"
|
|
54
|
+
<a href="#" class="nav-item" data-page="skills">
|
|
55
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg>
|
|
56
|
+
<span>技能管理</span>
|
|
57
|
+
<span class="nav-badge" id="nav-skill-count">0</span>
|
|
58
|
+
</a>
|
|
59
|
+
<a href="#" class="nav-item" data-page="mcp-servers">
|
|
60
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path><path d="M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12z"></path><path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"></path></svg>
|
|
61
|
+
<span>MCP 服务</span>
|
|
62
|
+
<span class="nav-badge" id="nav-mcp-count">0</span>
|
|
53
63
|
</a>
|
|
54
64
|
<a href="#" class="nav-item" data-page="settings">
|
|
55
65
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 5 15.34a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 20.39 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
@@ -61,7 +71,7 @@
|
|
|
61
71
|
<span id="theme-icon">☾</span>
|
|
62
72
|
<span id="theme-label">深色</span>
|
|
63
73
|
</button>
|
|
64
|
-
<div class="version" id="app-version"
|
|
74
|
+
<div class="version" id="app-version"></div>
|
|
65
75
|
</div>
|
|
66
76
|
</aside>
|
|
67
77
|
|
|
@@ -328,8 +338,27 @@
|
|
|
328
338
|
<option value="">选择后端代理...</option>
|
|
329
339
|
</select>
|
|
330
340
|
</div>
|
|
341
|
+
<div class="assistant-model-select">
|
|
342
|
+
<label>供应商</label>
|
|
343
|
+
<select id="assistant-provider-select" onchange="onAssistantProviderChange(this.value)">
|
|
344
|
+
<option value="">自动(跟随代理)</option>
|
|
345
|
+
</select>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="assistant-model-select">
|
|
348
|
+
<label>模型</label>
|
|
349
|
+
<select id="assistant-model-select">
|
|
350
|
+
<option value="">自动(跟随代理)</option>
|
|
351
|
+
</select>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="assistant-model-select">
|
|
354
|
+
<label>会话</label>
|
|
355
|
+
<div class="conversation-dropdown" id="conversation-dropdown">
|
|
356
|
+
<button class="conversation-dropdown-trigger" id="conversation-dropdown-trigger" onclick="toggleConversationDropdown()">新会话</button>
|
|
357
|
+
<div class="conversation-dropdown-menu" id="conversation-dropdown-menu"></div>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
331
360
|
<div class="toolbar-actions">
|
|
332
|
-
<button class="btn btn-sm" onclick="clearAssistantChat()"
|
|
361
|
+
<button class="btn btn-sm" onclick="clearAssistantChat()">新会话</button>
|
|
333
362
|
</div>
|
|
334
363
|
</div>
|
|
335
364
|
<div class="assistant-chat" id="assistant-chat">
|
|
@@ -348,14 +377,47 @@
|
|
|
348
377
|
<p class="assistant-hint">请先选择一个运行中的代理作为对话后端</p>
|
|
349
378
|
</div>
|
|
350
379
|
</div>
|
|
351
|
-
<div class="assistant-
|
|
352
|
-
<
|
|
380
|
+
<div class="assistant-context-bar" id="assistant-context-bar" style="display:none">
|
|
381
|
+
<div class="context-bar-track">
|
|
382
|
+
<div class="context-bar-fill" id="context-bar-fill" style="width:0%"></div>
|
|
383
|
+
</div>
|
|
384
|
+
<div class="context-bar-info">
|
|
385
|
+
<span id="context-bar-tokens">0</span> / <span id="context-bar-max">200K</span> tokens
|
|
386
|
+
(<span id="context-bar-percent">0</span>%)
|
|
387
|
+
· <span id="context-bar-messages">0</span> 条消息
|
|
388
|
+
</div>
|
|
389
|
+
<button class="btn btn-sm context-compress-btn" id="context-compress-btn" onclick="compressAssistantContext()" style="display:none">压缩</button>
|
|
390
|
+
</div>
|
|
391
|
+
<div class="skill-panel" id="skill-panel" style="display:none">
|
|
392
|
+
<div class="skill-panel-header"><span>可用技能</span><button class="btn btn-sm" onclick="toggleSkillPanel()">收起</button></div>
|
|
393
|
+
<div class="skill-panel-list" id="skill-panel-list"></div>
|
|
394
|
+
</div>
|
|
395
|
+
<div class="assistant-input-area" style="position:relative">
|
|
396
|
+
<div class="skill-autocomplete" id="skill-autocomplete"></div>
|
|
397
|
+
<textarea id="assistant-input" placeholder="输入问题,例如:帮我看看哪个供应商最慢,最近有什么异常? 输入 / 触发技能" rows="1"></textarea>
|
|
398
|
+
<button class="btn" onclick="toggleSkillPanel()" title="查看可用技能" style="padding:0 10px;font-size:16px">⚡</button>
|
|
353
399
|
<button class="btn btn-primary" id="assistant-send-btn" onclick="sendAssistantMessage()" disabled>发送</button>
|
|
354
400
|
</div>
|
|
355
401
|
</div>
|
|
356
402
|
</div>
|
|
357
403
|
|
|
358
404
|
<!-- ==================== Settings Page ==================== -->
|
|
405
|
+
<div class="page" id="page-skills">
|
|
406
|
+
<div class="page-header" style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
407
|
+
<h2 style="margin:0">技能管理</h2>
|
|
408
|
+
<button class="btn btn-primary" onclick="showSkillModal()">上传技能</button>
|
|
409
|
+
</div>
|
|
410
|
+
<div id="skills-container"></div>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<div class="page" id="page-mcp-servers">
|
|
414
|
+
<div class="page-header" style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
415
|
+
<h2 style="margin:0">MCP 服务管理</h2>
|
|
416
|
+
<button class="btn btn-primary" onclick="showMcpModal()">添加 MCP 服务</button>
|
|
417
|
+
</div>
|
|
418
|
+
<div id="mcp-servers-container"></div>
|
|
419
|
+
</div>
|
|
420
|
+
|
|
359
421
|
<div class="page" id="page-settings">
|
|
360
422
|
<div class="settings-grid">
|
|
361
423
|
<div class="panel">
|
|
@@ -409,6 +471,37 @@
|
|
|
409
471
|
</div>
|
|
410
472
|
</div>
|
|
411
473
|
</div>
|
|
474
|
+
<div class="panel">
|
|
475
|
+
<div class="panel-header">
|
|
476
|
+
<h3>智控助手</h3>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="settings-body">
|
|
479
|
+
<div class="setting-item">
|
|
480
|
+
<div class="setting-info">
|
|
481
|
+
<div class="setting-name">模型上下文大小</div>
|
|
482
|
+
<div class="setting-desc">设置所用模型的上下文窗口大小(Token),影响压缩触发阈值</div>
|
|
483
|
+
</div>
|
|
484
|
+
<input type="number" id="settings-max-context" value="200000" min="10000" step="10000"
|
|
485
|
+
style="width:120px" onchange="updateMaxContext(this.value)">
|
|
486
|
+
</div>
|
|
487
|
+
<div class="setting-item">
|
|
488
|
+
<div class="setting-info">
|
|
489
|
+
<div class="setting-name">最大工具调用轮次</div>
|
|
490
|
+
<div class="setting-desc">单次对话中模型可连续调用工具的最大轮数,达到上限后模型会自动总结回复</div>
|
|
491
|
+
</div>
|
|
492
|
+
<input type="number" id="settings-max-rounds" value="10" min="1" max="100"
|
|
493
|
+
style="width:120px" onchange="updateMaxRounds(this.value)">
|
|
494
|
+
</div>
|
|
495
|
+
<div class="setting-item">
|
|
496
|
+
<div class="setting-info">
|
|
497
|
+
<div class="setting-name">最大会话数</div>
|
|
498
|
+
<div class="setting-desc">保存的历史会话数量上限,超出时自动删除最早的会话,0 表示不限制</div>
|
|
499
|
+
</div>
|
|
500
|
+
<input type="number" id="settings-max-conversations" value="0" min="0"
|
|
501
|
+
style="width:120px" onchange="updateMaxConversations(this.value)">
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
412
505
|
</div>
|
|
413
506
|
</div>
|
|
414
507
|
</main>
|
|
@@ -637,9 +730,158 @@
|
|
|
637
730
|
</div>
|
|
638
731
|
</div>
|
|
639
732
|
|
|
733
|
+
<!-- Skill Modal -->
|
|
734
|
+
<div class="modal-overlay" id="skill-modal">
|
|
735
|
+
<div class="modal" style="max-width:600px">
|
|
736
|
+
<div class="modal-header">
|
|
737
|
+
<h3 id="skill-modal-title">新建技能</h3>
|
|
738
|
+
<button class="modal-close" onclick="closeSkillModal()">×</button>
|
|
739
|
+
</div>
|
|
740
|
+
<div class="modal-body">
|
|
741
|
+
<!-- 上传新技能 -->
|
|
742
|
+
<div id="skill-upload-section">
|
|
743
|
+
<div class="form-group">
|
|
744
|
+
<label>选择技能文件夹 <span style="color:var(--text-muted)">(包含 SKILL.md)</span></label>
|
|
745
|
+
<input type="file" id="skill-file-input" webkitdirectory>
|
|
746
|
+
<div id="skill-file-preview" style="margin-top:12px"></div>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
<!-- 编辑已有技能 -->
|
|
750
|
+
<div id="skill-edit-section" style="display:none">
|
|
751
|
+
<div class="form-group">
|
|
752
|
+
<label>名称 <span style="color:var(--text-muted)">(英文、数字、连字符)</span></label>
|
|
753
|
+
<input type="text" id="skill-name" placeholder="my-skill" pattern="[a-zA-Z0-9_-]+">
|
|
754
|
+
</div>
|
|
755
|
+
<div class="form-group">
|
|
756
|
+
<label>描述</label>
|
|
757
|
+
<input type="text" id="skill-description" placeholder="简短描述这个技能的用途">
|
|
758
|
+
</div>
|
|
759
|
+
<div class="form-group">
|
|
760
|
+
<label>指令内容 <span style="color:var(--text-muted)">(Markdown)</span></label>
|
|
761
|
+
<textarea id="skill-content" rows="10" placeholder="# 技能标题 在此编写技能指令..."></textarea>
|
|
762
|
+
</div>
|
|
763
|
+
<div class="form-group" id="skill-files-section">
|
|
764
|
+
<label>附属文件</label>
|
|
765
|
+
<div class="skill-file-upload-row">
|
|
766
|
+
<select id="skill-upload-dir"><option value="scripts">scripts/</option><option value="reference">reference/</option></select>
|
|
767
|
+
<input type="file" id="skill-upload-input" multiple>
|
|
768
|
+
<button class="btn btn-sm" onclick="uploadSkillFiles()">上传</button>
|
|
769
|
+
</div>
|
|
770
|
+
<div id="skill-existing-files" style="margin-top:8px;font-size:12px"></div>
|
|
771
|
+
</div>
|
|
772
|
+
</div>
|
|
773
|
+
</div>
|
|
774
|
+
<div class="modal-footer">
|
|
775
|
+
<button class="btn" onclick="closeSkillModal()">取消</button>
|
|
776
|
+
<button class="btn btn-primary" id="skill-save-btn" onclick="saveSkill()">保存</button>
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
|
|
781
|
+
<!-- Skill View Modal -->
|
|
782
|
+
<div class="modal-overlay" id="skill-view-modal">
|
|
783
|
+
<div class="modal" style="max-width:700px;max-height:80vh;display:flex;flex-direction:column">
|
|
784
|
+
<div class="modal-header">
|
|
785
|
+
<h3 id="skill-view-title">查看技能</h3>
|
|
786
|
+
<button class="modal-close" onclick="closeSkillViewModal()">×</button>
|
|
787
|
+
</div>
|
|
788
|
+
<div class="modal-body" style="overflow-y:auto;flex:1">
|
|
789
|
+
<div style="margin-bottom:12px">
|
|
790
|
+
<span class="skill-badge" id="skill-view-badge" style="font-size:12px;padding:2px 8px;border-radius:4px"></span>
|
|
791
|
+
<span style="color:var(--text-muted);font-size:13px;margin-left:8px" id="skill-view-desc"></span>
|
|
792
|
+
</div>
|
|
793
|
+
<div id="skill-view-files" style="margin-bottom:12px"></div>
|
|
794
|
+
<div class="skill-view-content" id="skill-view-content"></div>
|
|
795
|
+
</div>
|
|
796
|
+
<div class="modal-footer">
|
|
797
|
+
<button class="btn" onclick="closeSkillViewModal()">关闭</button>
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
</div>
|
|
801
|
+
|
|
802
|
+
<!-- MCP Server Modal -->
|
|
803
|
+
<div class="modal-overlay" id="mcp-modal">
|
|
804
|
+
<div class="modal" style="max-width:550px">
|
|
805
|
+
<div class="modal-header">
|
|
806
|
+
<h3 id="mcp-modal-title">添加 MCP 服务</h3>
|
|
807
|
+
<button class="modal-close" onclick="closeMcpModal()">×</button>
|
|
808
|
+
</div>
|
|
809
|
+
<div class="modal-body">
|
|
810
|
+
<div class="form-group">
|
|
811
|
+
<label>服务名称 <span style="color:var(--text-muted)">(英文、数字、连字符)</span></label>
|
|
812
|
+
<input type="text" id="mcp-name" placeholder="my-mcp-server" pattern="[a-zA-Z0-9_-]+">
|
|
813
|
+
</div>
|
|
814
|
+
<div class="form-group">
|
|
815
|
+
<label>传输方式</label>
|
|
816
|
+
<div style="display:flex;gap:16px;margin-top:4px">
|
|
817
|
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px">
|
|
818
|
+
<input type="radio" name="mcp-transport" value="stdio" checked onchange="toggleMcpTransport()"> 本地进程 (stdio)
|
|
819
|
+
</label>
|
|
820
|
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px">
|
|
821
|
+
<input type="radio" name="mcp-transport" value="http" onchange="toggleMcpTransport()"> 远程 HTTP
|
|
822
|
+
</label>
|
|
823
|
+
</div>
|
|
824
|
+
</div>
|
|
825
|
+
<div id="mcp-stdio-fields">
|
|
826
|
+
<div class="form-group">
|
|
827
|
+
<label>命令</label>
|
|
828
|
+
<input type="text" id="mcp-command" placeholder="npx">
|
|
829
|
+
</div>
|
|
830
|
+
<div class="form-group">
|
|
831
|
+
<label>参数 <span style="color:var(--text-muted)">(空格分隔)</span></label>
|
|
832
|
+
<input type="text" id="mcp-args" placeholder="-y @modelcontextprotocol/server-filesystem /tmp">
|
|
833
|
+
</div>
|
|
834
|
+
<div class="form-group">
|
|
835
|
+
<label>环境变量 <span style="color:var(--text-muted)">(可选)</span></label>
|
|
836
|
+
<div id="mcp-env-editor"></div>
|
|
837
|
+
<button class="btn btn-sm" style="margin-top:6px" onclick="addMcpEnvRow()">+ 添加</button>
|
|
838
|
+
</div>
|
|
839
|
+
</div>
|
|
840
|
+
<div id="mcp-http-fields" style="display:none">
|
|
841
|
+
<div class="form-group">
|
|
842
|
+
<label>URL</label>
|
|
843
|
+
<input type="text" id="mcp-url" placeholder="https://example.com/mcp">
|
|
844
|
+
</div>
|
|
845
|
+
<div class="form-group">
|
|
846
|
+
<label>请求头 <span style="color:var(--text-muted)">(可选)</span></label>
|
|
847
|
+
<div id="mcp-headers-editor"></div>
|
|
848
|
+
<button class="btn btn-sm" style="margin-top:6px" onclick="addMcpHeaderRow()">+ 添加</button>
|
|
849
|
+
</div>
|
|
850
|
+
</div>
|
|
851
|
+
<div class="form-group">
|
|
852
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
853
|
+
<input type="checkbox" id="mcp-enabled" checked> 启用
|
|
854
|
+
</label>
|
|
855
|
+
</div>
|
|
856
|
+
</div>
|
|
857
|
+
<div class="modal-footer">
|
|
858
|
+
<button class="btn" onclick="closeMcpModal()">取消</button>
|
|
859
|
+
<button class="btn btn-primary" onclick="saveMcpServer()">保存</button>
|
|
860
|
+
</div>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
<!-- MCP Tools Modal -->
|
|
865
|
+
<div class="modal-overlay" id="mcp-tools-modal">
|
|
866
|
+
<div class="modal" style="max-width:600px;max-height:70vh;display:flex;flex-direction:column">
|
|
867
|
+
<div class="modal-header">
|
|
868
|
+
<h3 id="mcp-tools-title">MCP 工具列表</h3>
|
|
869
|
+
<button class="modal-close" onclick="closeMcpToolsModal()">×</button>
|
|
870
|
+
</div>
|
|
871
|
+
<div class="modal-body" style="overflow-y:auto;flex:1">
|
|
872
|
+
<div id="mcp-tools-list"></div>
|
|
873
|
+
</div>
|
|
874
|
+
<div class="modal-footer">
|
|
875
|
+
<button class="btn" onclick="closeMcpToolsModal()">关闭</button>
|
|
876
|
+
</div>
|
|
877
|
+
</div>
|
|
878
|
+
</div>
|
|
879
|
+
|
|
640
880
|
<!-- Toast -->
|
|
641
881
|
<div class="toast" id="toast" style="display:none"></div>
|
|
642
882
|
|
|
883
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
884
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
|
|
643
885
|
<script src="app.js"></script>
|
|
644
886
|
</body>
|
|
645
887
|
</html>
|