local-knowledge-graph 1.5.0

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.
@@ -0,0 +1,238 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>本地知识图谱整合器</title>
7
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='10' cy='12' r='5' fill='%2358a6ff'/%3E%3Ccircle cx='23' cy='9' r='4' fill='%237ee787'/%3E%3Ccircle cx='19' cy='23' r='4.5' fill='%23d2a8ff'/%3E%3Cpath d='M14 15l6-4M13 16l4 4M23 13v6' stroke='%238fa3c0' stroke-width='1.6'/%3E%3C/svg%3E">
8
+ <link rel="stylesheet" href="/style.css">
9
+ </head>
10
+ <body>
11
+ <div id="app">
12
+ <header>
13
+ <h1>本地知识图谱整合器</h1>
14
+ <span class="badge" id="stat-badge">实体 0 / 关系 0</span>
15
+ <span class="badge" id="agent-badge">OpenCode 检测中</span>
16
+ <div class="spacer"></div>
17
+ <button id="m-left" class="m-btn" title="打开图谱面板">图谱</button>
18
+ <button id="m-right" class="m-btn" title="打开AI对话">AI</button>
19
+ <button id="btn-savepoint" title="将当前合规数据提交为Git保存点(仅存差异)">打保存点</button>
20
+ <button id="btn-undo" title="撤销最近一次操作(按快照还原,快捷键Ctrl+Z)">撤销</button>
21
+ <button id="btn-export" title="导出标准RDF(Turtle)格式文件">导出RDF</button>
22
+ <button id="btn-relayout" title="重新进行力导向布局">重新布局</button>
23
+ <button id="btn-resetview" title="重置相机视角">重置视角</button>
24
+ <button id="btn-style" title="自定义实体/关系的颜色、大小与线型">视图设置</button>
25
+ <button id="btn-help" title="帮助:查看版本现状、检查更新与使用指南">帮助</button>
26
+ <button id="btn-inference" title="叠加显示推理得到的隐性关系(虚线,OWL传递/对称/逆规则),点击推理边可查看推导依据">推理</button>
27
+ <button id="m-file" class="m-btn" title="文件:打开/保存/另存">文件</button>
28
+ </header>
29
+
30
+ <main>
31
+ <div class="panel" id="panel-left">
32
+ <div class="tabs" id="tabs">
33
+ <div data-tab="entity" class="active">实体</div>
34
+ <div data-tab="relation">关系</div>
35
+ <div data-tab="search">检索</div>
36
+ <div data-tab="log">日志</div>
37
+ <div data-tab="version">版本</div>
38
+ </div>
39
+
40
+ <div class="tabbody active" id="tab-entity">
41
+ <div class="card">
42
+ <h3 id="entity-form-title">新增实体</h3>
43
+ <label>名称</label>
44
+ <input id="e-name" placeholder="例如:长江">
45
+ <label>大类(对应3D样式)</label>
46
+ <select id="e-category"></select>
47
+ <label>属性 JSON(扁平键值对,三元组结构)</label>
48
+ <textarea id="e-attrs" placeholder='{"键": "值"}'></textarea>
49
+ <div class="row">
50
+ <button class="primary" id="e-submit">添加</button>
51
+ <button class="ghost" id="e-cancel" style="display:none">取消编辑</button>
52
+ </div>
53
+ </div>
54
+ <div class="card"><h3>实体列表</h3><div id="e-list"></div></div>
55
+ </div>
56
+
57
+ <div class="tabbody" id="tab-relation">
58
+ <div class="card">
59
+ <h3>新增关系</h3>
60
+ <label>起点实体</label>
61
+ <select id="r-source"></select>
62
+ <label>关系名称</label>
63
+ <input id="r-name" placeholder="例如:位于">
64
+ <label>大类(对应线型/颜色)</label>
65
+ <select id="r-category"></select>
66
+ <label>终点实体</label>
67
+ <select id="r-target"></select>
68
+ <div class="row"><button class="primary" id="r-submit">添加</button></div>
69
+ </div>
70
+ <div class="card"><h3>关系列表</h3><div id="r-list"></div></div>
71
+ </div>
72
+
73
+ <div class="tabbody" id="tab-search">
74
+ <div class="card">
75
+ <h3>智能提问(自然语言关系查找)</h3>
76
+ <label>用一句话描述要找的关系</label>
77
+ <input id="a-question" placeholder="例如:郑和下西洋涉及哪些人物和事件">
78
+ <div class="row">
79
+ <button class="primary" id="a-submit">提问</button>
80
+ <label class="ck" style="display:flex;align-items:center;gap:4px;font-size:11px;color:#8fa3c0;cursor:pointer"><input type="checkbox" id="a-synth" checked>生成综述</label>
81
+ <span id="a-status" style="font-size:11px;color:#8fa3c0;align-self:center"></span>
82
+ </div>
83
+ <div id="a-result"></div>
84
+ </div>
85
+ <div class="card">
86
+ <h3>语义检索配置(硅基流动默认)</h3>
87
+ <div class="row">
88
+ <div style="flex:1"><label>API Key</label><input id="s-key" type="password" placeholder="仅存本地 data/settings.json"></div>
89
+ <div style="width:110px"><label>模型</label><input id="s-model"></div>
90
+ </div>
91
+ <div class="row"><button class="primary" id="s-save">保存配置</button><button id="s-build">构建全量向量</button><span id="s-status" style="font-size:11px;color:#8fa3c0;align-self:center"></span></div>
92
+ </div>
93
+ <div class="card">
94
+ <h3>混合检索(语义+关键词 RRF 融合)</h3>
95
+ <label>查询</label>
96
+ <input id="s-query" placeholder="例如:明朝的航海人物">
97
+ <div class="row"><button class="primary" id="s-run">检索</button><span id="s-mode" style="font-size:11px;color:#8fa3c0;align-self:center"></span></div>
98
+ <div id="s-results"></div>
99
+ </div>
100
+ <div class="card">
101
+ <h3>关系查询(迷你Cypher,只读)</h3>
102
+ <textarea id="c-query" placeholder="MATCH (a)-[r:互动]->(b) WHERE a.name contains 郑和 RETURN a.name, r.name, b.name LIMIT 10"></textarea>
103
+ <div class="row"><button class="primary" id="c-run">执行</button><span id="c-hint" style="font-size:11px;color:#8fa3c0;align-self:center">支持 contains / = 与 类型:互动|归属</span></div>
104
+ <div id="c-results"></div>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="tabbody" id="tab-log">
109
+ <div class="card">
110
+ <h3>操作日志(手工与OpenCode共用)</h3>
111
+ <div class="log-filter">
112
+ <select id="log-type">
113
+ <option value="">全部类型</option>
114
+ <option value="ADD_ENTITY">新增实体</option>
115
+ <option value="UPDATE_ENTITY">更新实体</option>
116
+ <option value="DELETE_ENTITY">删除实体</option>
117
+ <option value="ADD_RELATION">新增关系</option>
118
+ <option value="UPDATE_RELATION">更新关系</option>
119
+ <option value="DELETE_RELATION">删除关系</option>
120
+ <option value="RESTORE">版本回溯</option>
121
+ <option value="UNDO">撤销</option>
122
+ </select>
123
+ <input id="log-kw" placeholder="筛选关键字(名称/来源)">
124
+ </div>
125
+ <div id="log-list"></div>
126
+ </div>
127
+ </div>
128
+
129
+ <div class="tabbody" id="tab-version">
130
+ <div class="card">
131
+ <h3>Git保存点</h3>
132
+ <label>保存点备注(可选)</label>
133
+ <input id="v-message" placeholder="例如:补全中国地理实体">
134
+ <div class="row"><button class="primary" id="v-save">打保存点</button></div>
135
+ <div class="hint-text">数据文件 data/kg.db 由本地Git仓库托管,仅存差异版本;恢复前会自动备份当前状态。</div>
136
+ </div>
137
+ <div class="card"><h3>提交历史(与操作日志双向绑定)</h3><div id="v-list"></div></div>
138
+ </div>
139
+ </div>
140
+
141
+ <div id="canvas-wrap">
142
+ <div class="legend" id="legend"></div>
143
+ <button id="m-legend" class="m-btn">图例</button>
144
+ <div id="ego-bar">
145
+ <span>中心:</span><span class="ego-name" id="ego-name"></span>
146
+ <label>层数</label><input type="number" id="ego-depth" min="1" step="1" placeholder="全部">
147
+ <button id="ego-rebuild">重新布局</button>
148
+ <button id="ego-exit">退出中心</button>
149
+ <div class="ego-hint">清空层数输入框 = 显示全部层级;层级光圈颜色随跳数递进</div>
150
+ </div>
151
+ <div id="info-card"></div>
152
+ <div id="style-panel"></div>
153
+ <div id="help-panel">
154
+ <div class="hp-head"><span>帮助</span><span id="help-close" title="关闭">×</span></div>
155
+ <div class="hp-body">
156
+ <h3>关于</h3>
157
+ <p class="hp-p">本地知识图谱整合器 —— 把文档与联网信息整理成可导航的 3D 知识图谱。全流程本地运行,数据存于本机 data/ 目录(独立 Git 仓库托管,可回溯)。</p>
158
+ <h3>版本与更新</h3>
159
+ <div id="help-upd"></div>
160
+ <div class="hp-line">详细帮助文档:项目目录下 <b>HELP.md</b></div>
161
+ <div class="hp-line">发布记录:<a href="https://github.com/iamsamyiok/local-knowledge-graph/releases" target="_blank" rel="noopener">GitHub Releases</a></div>
162
+ <h3>快速上手</h3>
163
+ <ul class="hp-list">
164
+ <li><b>AI 对话建图</b>:右侧输入自然语言(如"联网查一下珠穆朗玛峰的海拔并建入图谱"),或上传文档(md/txt/pdf/docx)批量抽取</li>
165
+ <li><b>检索页签</b>:智能提问(一句话找关系)、语义/关键词混合检索</li>
166
+ <li><b>最短路径</b>:搜索或列表选中实体后,用"路径"功能查两实体关联链路</li>
167
+ <li><b>撤销</b>:按钮或 Ctrl+Z,可连续撤销;删除实体仅恢复实体本身,关联关系可在日志页看到级联记录</li>
168
+ <li><b>推理</b>:开关叠加虚线显示的隐性关系(传递/对称/逆规则),点击可见推导依据</li>
169
+ <li><b>保存点与回溯</b>:版本页签打保存点,随时回滚到任一历史状态</li>
170
+ <li><b>图片绑定</b>:实体信息卡上传本地图片,自动生成缩略图</li>
171
+ <li><b>导出</b>:RDF(Turtle)、整库 .db、单文件只读网页版</li>
172
+ </ul>
173
+ <h3>键盘快捷键</h3>
174
+ <ul class="hp-list">
175
+ <li><b>Ctrl+F</b> 聚焦搜索框 <b>Ctrl+Z</b> 撤销 <b>Delete</b> 删除选中 <b>Esc</b> 逐层关闭面板/取消选中</li>
176
+ </ul>
177
+ <h3>数据安全</h3>
178
+ <p class="hp-p">图谱数据、图片、API Key 全部保存在本机数据目录(npm 安装在 ~/.local-knowledge-graph/,Git 克隆在项目内 data/)。语义检索的 API Key 仅存本地,服务端永不回传真实值。</p>
179
+ </div>
180
+ </div>
181
+ <div id="empty-hint" style="display:none">暂无数据<br>在左侧添加实体,或在右侧让 OpenCode 联网补全</div>
182
+ <div id="tooltip3d" style="display:none"></div>
183
+ <div id="path-panel" style="display:none">
184
+ <div class="p-head"><span id="path-title">最短路径</span><span id="path-close" title="关闭">×</span></div>
185
+ <div id="path-body"></div>
186
+ </div>
187
+ </div>
188
+
189
+ <div class="panel right" id="panel-right">
190
+ <div class="tabs"><div class="active" style="cursor:default">OpenCode 自然语言指令</div></div>
191
+ <div class="messages" id="messages">
192
+ <div class="msg bot"><div class="who">系统</div><div class="bubble">输入自然语言指令(如"帮我联网查一下珠穆朗玛峰的海拔并建入图谱")。OpenCode 将校验信息后提交操作,所有写入均需通过RDF规范校验。</div></div>
193
+ </div>
194
+ <div class="chat-input">
195
+ <div id="file-chip" style="display:none">
196
+ <span id="file-chip-name"></span>
197
+ <button id="file-chip-del" title="移除附件">移除</button>
198
+ </div>
199
+ <textarea id="agent-input" placeholder="输入指令,Enter发送,Shift+Enter换行;可附文档让OpenCode抽取三元组"></textarea>
200
+ <div class="row">
201
+ <button class="ghost" id="agent-file-btn" title="附上文档,由kg-triples技能抽取三元组">附文档</button>
202
+ <button class="primary" id="agent-send">发送</button>
203
+ </div>
204
+ <input type="file" id="agent-file" accept=".txt,.md,.markdown,.csv,.json,.html,.htm,.xml,.pdf,.docx,.doc" style="display:none">
205
+ <div class="hint-text">OpenCode 进程由后端默认拉起;仅它可联网补全公开信息,本地图谱数据不对外传输。文档经 kg-triples 技能抽取并校验后融合入库。</div>
206
+ </div>
207
+ </div>
208
+ </main>
209
+ </div>
210
+ <div id="backdrop"></div>
211
+ <div id="toast"></div>
212
+ <div id="file-menu">
213
+ <button id="fm-open">打开图谱</button>
214
+ <button id="fm-save">保存图谱</button>
215
+ <button id="fm-saveas">另存为图谱</button>
216
+ <button id="fm-savehtml">另存为图谱网页</button>
217
+ <button id="fm-rdf">导出 RDF</button>
218
+ </div>
219
+ <input type="file" id="db-file-input" accept=".db,.sqlite,.sqlite3" style="display:none">
220
+ <div id="lightbox">
221
+ <button class="lb-close" id="lb-close" title="关闭">x</button>
222
+ <figure>
223
+ <img id="lb-img" alt="">
224
+ <figcaption id="lb-cap"></figcaption>
225
+ <div class="lb-btns">
226
+ <button id="lb-prev">上一张</button>
227
+ <span id="lb-pos" style="color:#8fa3c0;font-size:11.5px"></span>
228
+ <button id="lb-next">下一张</button>
229
+ <button class="danger" onclick="delImage()">删除绑定</button>
230
+ </div>
231
+ </figure>
232
+ </div>
233
+
234
+ <script src="/vendor/three.min.js"></script>
235
+ <script src="/vendor/OrbitControls.js"></script>
236
+ <script src="/app.js"></script>
237
+ </body>
238
+ </html>
@@ -0,0 +1,374 @@
1
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2
+ html, body { height: 100%; overflow: hidden; }
3
+ body {
4
+ font-family: "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, sans-serif;
5
+ background: #0b0f1a; color: #d7dee9; font-size: 13px;
6
+ overscroll-behavior: none;
7
+ }
8
+ button, input, select, textarea { touch-action: manipulation; }
9
+ #app { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }
10
+
11
+ header {
12
+ height: 50px; display: flex; align-items: center; gap: 10px; padding: 0 14px;
13
+ background: #101728; border-bottom: 1px solid #1e2a44; flex-shrink: 0;
14
+ }
15
+ header h1 { font-size: 15px; font-weight: 600; color: #7fd1ff; letter-spacing: 1px; }
16
+ header .spacer { flex: 1; }
17
+ header .badge { font-size: 11px; color: #8fa3c0; border: 1px solid #2a3a5c; border-radius: 10px; padding: 2px 9px; }
18
+ header .badge.ok { color: #7ee787; border-color: #2ea04366; }
19
+ header .badge.off { color: #f0883e; border-color: #f0883e66; }
20
+
21
+ button {
22
+ background: #1b2b4d; color: #cfe2ff; border: 1px solid #2f4a7a; border-radius: 5px;
23
+ padding: 5px 12px; cursor: pointer; font-size: 12px; font-family: inherit;
24
+ }
25
+ button:hover { background: #24386a; }
26
+ button:disabled { opacity: .45; cursor: not-allowed; }
27
+ button.primary { background: #1f6feb; border-color: #388bfd; }
28
+ button.primary:hover { background: #2b7bff; }
29
+ button.danger { background: #3a1620; border-color: #7a2a3a; color: #ffb3c0; }
30
+ button.danger:hover { background: #4d1c29; }
31
+ button.ghost { background: transparent; }
32
+ .m-btn { display: none; }
33
+
34
+ #backdrop {
35
+ position: fixed; inset: 0; background: rgba(4,8,16,.5); z-index: 30; display: none;
36
+ }
37
+ #backdrop.show { display: block; }
38
+
39
+ main { display: flex; flex: 1; min-height: 0; }
40
+
41
+ .panel {
42
+ width: 300px; background: #0e1524; border-right: 1px solid #1e2a44;
43
+ display: flex; flex-direction: column; flex-shrink: 0;
44
+ }
45
+ .panel.right { border-right: none; border-left: 1px solid #1e2a44; width: 330px; }
46
+
47
+ .tabs { display: flex; border-bottom: 1px solid #1e2a44; flex-shrink: 0; }
48
+ .tabs div {
49
+ flex: 1; text-align: center; padding: 9px 0; cursor: pointer; color: #8fa3c0; font-size: 12px;
50
+ border-bottom: 2px solid transparent;
51
+ }
52
+ .tabs div.active { color: #7fd1ff; border-bottom-color: #1f6feb; background: #101a30; }
53
+
54
+ #btn-inference.active { background: #3b2d52; border-color: #c792ea; color: #c792ea; }
55
+
56
+ .tabbody { flex: 1; overflow-y: auto; padding: 12px; display: none; }
57
+ .tabbody.active { display: block; }
58
+ .tabbody::-webkit-scrollbar, .messages::-webkit-scrollbar { width: 8px; }
59
+ .tabbody::-webkit-scrollbar-thumb, .messages::-webkit-scrollbar-thumb { background: #24304d; border-radius: 4px; }
60
+
61
+ .card { background: #101a30; border: 1px solid #1e2a44; border-radius: 8px; padding: 10px; margin-bottom: 12px; }
62
+ .card h3 { font-size: 12px; color: #9fb6d9; margin-bottom: 8px; font-weight: 600; }
63
+ label { display: block; font-size: 11px; color: #8fa3c0; margin: 8px 0 3px; }
64
+ input, select, textarea {
65
+ width: 100%; background: #0b1220; border: 1px solid #24304d; border-radius: 5px;
66
+ color: #d7dee9; padding: 6px 8px; font-size: 12px; font-family: inherit;
67
+ }
68
+ textarea { resize: vertical; min-height: 56px; font-family: ui-monospace, Consolas, monospace; }
69
+ input:focus, select:focus, textarea:focus { outline: none; border-color: #1f6feb; }
70
+ .row { display: flex; gap: 8px; margin-top: 10px; }
71
+ .row > * { flex: 1; }
72
+
73
+ .list-item {
74
+ background: #0b1220; border: 1px solid #1e2a44; border-radius: 6px;
75
+ padding: 7px 9px; margin-bottom: 6px; display: flex; align-items: center; gap: 8px;
76
+ }
77
+ .list-item .main { flex: 1; min-width: 0; }
78
+ .list-item .name { font-size: 12.5px; color: #e2eaf7; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
79
+ .list-item .sub { font-size: 10.5px; color: #7a8db0; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
80
+ .list-item button { padding: 3px 8px; font-size: 11px; flex-shrink: 0; }
81
+ .tag {
82
+ display: inline-block; font-size: 10px; padding: 1px 7px; border-radius: 9px;
83
+ border: 1px solid; margin-left: 4px; vertical-align: 1px;
84
+ }
85
+
86
+ #canvas-wrap { flex: 1; position: relative; min-width: 0; background: radial-gradient(ellipse at center, #0d1424 0%, #070a12 100%); }
87
+ #canvas-wrap canvas { display: block; touch-action: none; }
88
+ .legend {
89
+ position: absolute; left: 12px; bottom: 12px; background: rgba(13,20,36,.85);
90
+ border: 1px solid #1e2a44; border-radius: 8px; padding: 9px 12px; font-size: 11px; color: #a9bbd8;
91
+ pointer-events: none; line-height: 1.9;
92
+ }
93
+ .legend b { color: #d7dee9; font-weight: 600; }
94
+ .sw { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; vertical-align: -1px; }
95
+ .ln { display: inline-block; width: 22px; height: 0; border-top: 2.5px solid; margin-right: 6px; vertical-align: 3px; }
96
+ .ln.dash { border-top-style: dashed; }
97
+
98
+ #info-card {
99
+ position: absolute; top: 12px; right: 12px; width: 240px; display: none;
100
+ background: rgba(13,20,36,.92); border: 1px solid #2a3a5c; border-radius: 8px; padding: 10px 12px;
101
+ }
102
+ #info-card h4 { color: #7fd1ff; font-size: 13px; margin-bottom: 6px; }
103
+ #info-card .kv { font-size: 11px; color: #a9bbd8; margin: 2px 0; word-break: break-all; }
104
+ #info-card .kv b { color: #d7dee9; }
105
+ #info-card .btns { margin-top: 8px; display: flex; gap: 6px; }
106
+ #empty-hint {
107
+ position: absolute; top: 45%; left: 50%; transform: translate(-50%,-50%);
108
+ color: #4a5b7d; font-size: 14px; pointer-events: none; text-align: center; line-height: 2;
109
+ }
110
+
111
+ /* ================= 中心层级视图 ================= */
112
+ #ego-bar {
113
+ position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
114
+ display: none; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center;
115
+ background: rgba(13,20,36,.92); border: 1px solid #2a3a5c; border-radius: 8px;
116
+ padding: 7px 12px; z-index: 12; max-width: calc(100% - 24px); font-size: 12px;
117
+ }
118
+ #ego-bar.show { display: flex; }
119
+ #ego-bar .ego-name { color: #ffd75f; font-weight: 600; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
120
+ #ego-bar label { margin: 0; color: #8fa3c0; }
121
+ #ego-bar input { width: 76px; }
122
+ #ego-bar .ego-hint { color: #5c6f92; font-size: 10.5px; width: 100%; text-align: center; }
123
+ @media (max-width: 768px) {
124
+ #ego-bar { top: 8px; padding: 6px 8px; gap: 6px; }
125
+ #ego-bar .ego-name { max-width: 110px; }
126
+ #ego-bar button { min-height: 30px; padding: 5px 9px; font-size: 11px; }
127
+ }
128
+
129
+ /* ================= 实体图片 ================= */
130
+ #img-section { margin-top: 8px; }
131
+ #img-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
132
+ #img-grid img {
133
+ width: 52px; height: 52px; object-fit: cover; border-radius: 6px; cursor: zoom-in;
134
+ border: 1px solid #2a3a5c; background: #0b1220;
135
+ }
136
+ #img-grid img:hover { border-color: #1f6feb; }
137
+ .img-tag { color: #7fd1ff; }
138
+ #lightbox {
139
+ position: fixed; inset: 0; z-index: 60; display: none;
140
+ background: rgba(4,8,16,.92); align-items: center; justify-content: center;
141
+ }
142
+ #lightbox.show { display: flex; }
143
+ #lightbox figure { max-width: 92vw; max-height: 88vh; display: flex; flex-direction: column; gap: 10px; align-items: center; margin: 0; }
144
+ #lightbox img { max-width: 92vw; max-height: 76vh; object-fit: contain; border-radius: 8px; background: #0b1220; }
145
+ #lightbox figcaption { color: #cfe2ff; font-size: 12.5px; text-align: center; word-break: break-all; }
146
+ #lightbox .lb-btns { display: flex; gap: 10px; align-items: center; }
147
+ #lightbox .lb-close {
148
+ position: absolute; top: 12px; right: 14px; width: 40px; height: 40px; border-radius: 50%;
149
+ background: rgba(13,20,36,.9); font-size: 18px; line-height: 1;
150
+ }
151
+
152
+ .messages { flex: 1; overflow-y: auto; padding: 12px; }
153
+ .msg { margin-bottom: 10px; }
154
+ .msg .who { font-size: 10.5px; color: #7a8db0; margin-bottom: 3px; }
155
+ .msg .bubble {
156
+ background: #101a30; border: 1px solid #1e2a44; border-radius: 8px; padding: 8px 10px;
157
+ font-size: 12px; line-height: 1.7; white-space: pre-wrap; word-break: break-word;
158
+ }
159
+ .msg.user .bubble { background: #16233f; border-color: #2a3f6b; }
160
+ .msg .ops { margin-top: 6px; }
161
+ .chip {
162
+ display: inline-block; font-size: 10.5px; background: #0f2e1c; color: #7ee787;
163
+ border: 1px solid #2ea04355; border-radius: 9px; padding: 1px 8px; margin: 2px 3px 0 0;
164
+ }
165
+ .chip.warn { background: #331b0d; color: #f0b37e; border-color: #f0883e55; }
166
+ .chat-input { padding: 10px; border-top: 1px solid #1e2a44; flex-shrink: 0; }
167
+ .chat-input textarea { min-height: 62px; }
168
+ .hint-text { font-size: 10.5px; color: #5c6f92; margin-top: 5px; line-height: 1.6; }
169
+ #file-chip {
170
+ display: flex; align-items: center; gap: 8px; background: #16233f;
171
+ border: 1px dashed #2f4a7a; border-radius: 6px; padding: 5px 9px; margin-bottom: 7px;
172
+ }
173
+ #file-chip-name { flex: 1; font-size: 11.5px; color: #7fd1ff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
174
+ #file-chip button { padding: 2px 8px; font-size: 10.5px; }
175
+ .report { font-size: 11px; color: #a9bbd8; margin-top: 6px; line-height: 1.8; background: #0d1628; border: 1px solid #1e2a44; border-radius: 6px; padding: 7px 9px; }
176
+ .report b { color: #7ee787; }
177
+ .report .rerr { color: #f0b37e; }
178
+
179
+ #toast {
180
+ position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%);
181
+ background: #16233f; border: 1px solid #2f4a7a; color: #cfe2ff; border-radius: 7px;
182
+ padding: 9px 18px; font-size: 12.5px; display: none; z-index: 99; max-width: 70vw;
183
+ }
184
+ #toast.err { border-color: #7a2a3a; color: #ffb3c0; background: #241318; }
185
+ .log-item { font-size: 11px; border-bottom: 1px solid #16213a; padding: 7px 2px; }
186
+ .log-item .l1 { display: flex; justify-content: space-between; color: #9fb6d9; }
187
+ .log-item .l2 { color: #67799c; margin-top: 3px; word-break: break-all; }
188
+ .ver-item { background: #0b1220; border: 1px solid #1e2a44; border-radius: 6px; padding: 8px 10px; margin-bottom: 7px; }
189
+ .ver-item .v1 { display: flex; align-items: center; gap: 7px; }
190
+ .ver-item code { color: #7fd1ff; font-size: 11px; }
191
+ .ver-item .v2 { font-size: 11px; color: #b6c6e2; margin: 4px 0; }
192
+ .ver-item .v3 { font-size: 10px; color: #67799c; }
193
+
194
+ /* ================= 视图样式自定义面板(桌面) ================= */
195
+ #btn-style { display: inline-block; }
196
+ #style-panel {
197
+ position: absolute; top: 12px; right: 12px; width: 288px; max-height: calc(100% - 24px);
198
+ overflow-y: auto; display: none; z-index: 22;
199
+ background: rgba(13,20,36,.96); border: 1px solid #2a3a5c; border-radius: 8px; padding: 12px;
200
+ }
201
+ #style-panel.show { display: block; }
202
+ #style-panel h4 { color: #7fd1ff; font-size: 13px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; }
203
+ #style-panel h4 span { cursor: pointer; color: #8fa3c0; font-size: 15px; }
204
+ .style-sec { font-size: 11px; color: #9fb6d9; margin: 10px 0 4px; font-weight: 600; }
205
+ .style-row { display: flex; align-items: center; gap: 6px; margin: 5px 0; font-size: 11px; }
206
+ .style-row .sname { width: 58px; color: #d7dee9; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
207
+ .style-row input[type="color"] { width: 34px; padding: 1px; height: 26px; flex: none; }
208
+ .style-row input[type="number"], .style-row input[type="range"] { width: 58px; flex: none; padding: 3px 5px; }
209
+ .style-row input[type="range"] { width: 64px; }
210
+ .style-row label.ck { display: flex; align-items: center; gap: 3px; color: #8fa3c0; margin: 0; cursor: pointer; }
211
+ .style-row label.ck input { width: auto; }
212
+ #style-panel .hint-text { margin-top: 8px; }
213
+
214
+ /* ================= 悬浮提示 / 路径面板 / 日志筛选 ================= */
215
+ #tooltip3d {
216
+ position: fixed; display: none; z-index: 40; pointer-events: none;
217
+ max-width: 220px; padding: 7px 10px; font-size: 12px; line-height: 1.5;
218
+ background: rgba(13,20,36,.94); border: 1px solid #2a3a5c; border-radius: 7px; color: #d7dee9;
219
+ box-shadow: 0 4px 14px rgba(0,0,0,.4);
220
+ }
221
+ #tooltip3d b { color: #7fd1ff; }
222
+ #tooltip3d .tt-cat { color: #8fa3c0; font-size: 10.5px; margin-left: 6px; }
223
+ #tooltip3d .tt-attr { color: #9fb6d9; font-size: 11px; margin-top: 2px; word-break: break-all; }
224
+
225
+ #path-panel {
226
+ position: absolute; top: 52px; left: 50%; transform: translateX(-50%);
227
+ min-width: 240px; max-width: min(420px, calc(100vw - 24px)); max-height: 60%;
228
+ overflow-y: auto; z-index: 30; display: none;
229
+ background: rgba(13,20,36,.96); border: 1px solid #2a3a5c; border-radius: 9px; padding: 10px 12px;
230
+ box-shadow: 0 6px 20px rgba(0,0,0,.45);
231
+ }
232
+ #path-panel .p-head { display: flex; justify-content: space-between; align-items: center; color: #7fd1ff; font-size: 12.5px; font-weight: 600; margin-bottom: 8px; }
233
+ #path-panel .p-head #path-close { cursor: pointer; color: #8fa3c0; font-size: 15px; font-weight: 400; }
234
+ #path-panel .p-ent { display: inline-block; margin: 2px 0; padding: 3px 8px; border-radius: 6px; background: #16233f; border: 1px solid #2a3a5c; cursor: pointer; font-size: 12px; color: #d7dee9; }
235
+ #path-panel .p-ent:hover { border-color: #7fd1ff; }
236
+ #path-panel .p-ent .tag { color: #8fa3c0; font-size: 10px; margin-left: 4px; }
237
+ #path-panel .p-rel { display: block; color: #6f88b3; font-size: 11px; margin: 1px 0 1px 14px; }
238
+
239
+ .log-filter { display: flex; gap: 6px; margin-bottom: 8px; }
240
+ .log-filter select, .log-filter input {
241
+ background: #101a30; border: 1px solid #263454; border-radius: 6px; color: #d7dee9;
242
+ font-size: 11.5px; padding: 5px 7px; min-width: 0;
243
+ }
244
+ .log-filter select { flex: none; }
245
+ .log-filter input { flex: 1; }
246
+
247
+ /* ================= 文件菜单(手机端) ================= */
248
+ #file-menu {
249
+ position: fixed; top: 46px; right: 8px; z-index: 45; display: none;
250
+ background: #101a30; border: 1px solid #2a3a5c; border-radius: 8px;
251
+ box-shadow: 0 8px 24px rgba(0,0,0,.5); min-width: 172px; overflow: hidden;
252
+ }
253
+ #file-menu.show { display: block; }
254
+ #file-menu button {
255
+ display: block; width: 100%; text-align: left; background: transparent;
256
+ border: none; border-bottom: 1px solid #16213a; border-radius: 0;
257
+ padding: 11px 14px; font-size: 13px; color: #cfe2ff; min-height: 42px;
258
+ }
259
+ #file-menu button:last-child { border-bottom: none; }
260
+ #file-menu button:active { background: #1b2b4d; }
261
+
262
+ /* ================= 平板/小屏桌面适配(769px-1100px) ================= */
263
+ @media (min-width: 769px) and (max-width: 1100px) {
264
+ header { padding: 0 10px; gap: 8px; }
265
+ header h1 { font-size: 13.5px; }
266
+ header button { padding: 4px 9px; font-size: 11.5px; }
267
+ .panel { width: 264px; }
268
+ .panel.right { width: 296px; }
269
+ #info-card { width: 216px; }
270
+ .legend { font-size: 10px; padding: 7px 9px; line-height: 1.7; }
271
+ }
272
+
273
+ /* ================= 手机端适配 ================= */
274
+ @media (max-width: 768px) {
275
+ .m-btn { display: inline-block; background: #16233f; }
276
+ #btn-inference { display: none; }
277
+ /* 主题设置移动端可达:按钮保留,面板收窄为屏幕内浮层 */
278
+ #style-panel { max-width: calc(100vw - 16px); width: 288px; top: 8px; right: 8px; }
279
+
280
+ /* 隐藏大标题,所有功能按钮单行排布,最大化画布显示范围 */
281
+ header {
282
+ flex-wrap: nowrap; height: 44px; padding: 0 6px; gap: 4px;
283
+ overflow: hidden;
284
+ }
285
+ header h1 { display: none; }
286
+ header button { padding: 5px 7px; font-size: 11px; flex-shrink: 0; min-width: 0; }
287
+ #btn-export { display: none; } /* 导出RDF移入文件菜单 */
288
+ #stat-badge, #agent-badge { display: none; }
289
+
290
+ /* 左右面板变为抽屉 */
291
+ .panel {
292
+ position: fixed; top: 0; bottom: 0; left: 0; width: min(86vw, 320px);
293
+ z-index: 40; transform: translateX(-105%);
294
+ transition: transform .25s ease; box-shadow: 0 0 30px rgba(0,0,0,.55);
295
+ }
296
+ .panel.open { transform: translateX(0); }
297
+ .panel.right {
298
+ left: auto; right: 0; width: min(92vw, 360px);
299
+ transform: translateX(105%);
300
+ }
301
+ .panel.right.open { transform: translateX(0); }
302
+ .tabbody, .messages { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
303
+ .chat-input { padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
304
+
305
+ /* 触屏表单16px,避免iOS聚焦自动放大 */
306
+ input, select, textarea { font-size: 16px; padding: 8px 10px; }
307
+ button { min-height: 30px; }
308
+ .list-item button { min-height: 26px; padding: 4px 10px; }
309
+
310
+ /* 信息卡变为底部卡片 */
311
+ #info-card {
312
+ left: 8px; right: 8px; top: auto; bottom: 58px; width: auto;
313
+ max-height: 42vh; overflow-y: auto; z-index: 25;
314
+ }
315
+ .info-btns button { min-height: 34px; }
316
+
317
+ /* 图例默认收起,由浮动按钮开关 */
318
+ .legend { bottom: 56px; font-size: 10px; padding: 7px 9px; line-height: 1.7; }
319
+ .legend.hidden { display: none; }
320
+ #m-legend {
321
+ display: block; position: absolute; left: 10px; bottom: 12px; z-index: 15;
322
+ border-radius: 16px; background: rgba(13,20,36,.92);
323
+ }
324
+ #empty-hint { width: 80vw; }
325
+ }
326
+ @media (max-width: 430px) {
327
+ #stat-badge { display: none; }
328
+ }
329
+
330
+ /* ---------- 智能提问(smart-retrieval) ---------- */
331
+ .ask-steps { display: flex; flex-wrap: wrap; gap: 5px; margin: 8px 0; }
332
+ .ask-chip {
333
+ display: inline-block; padding: 2px 8px; font-size: 10.5px; border-radius: 9px;
334
+ border: 1px solid #3a4a66; color: #a8b8d4; background: rgba(58, 74, 102, 0.25);
335
+ }
336
+ .ask-chip.ok { border-color: #3f7f5c; color: #7fd8a4; background: rgba(63, 127, 92, 0.15); }
337
+ .ask-chip.timeout { border-color: #b08a3e; color: #e0c068; background: rgba(176, 138, 62, 0.15); }
338
+ .ask-chip.err, .ask-chip.degraded { border-color: #8a4a4a; color: #e08a8a; background: rgba(138, 74, 74, 0.15); }
339
+ .ask-sec { font-size: 11.5px; color: #8fa3c0; margin: 10px 0 4px; border-bottom: 1px solid #26334c; padding-bottom: 3px; }
340
+ .ask-synth {
341
+ margin: 10px 0; padding: 9px 11px; font-size: 12px; line-height: 1.7; color: #cfe0f5;
342
+ background: rgba(58, 74, 102, 0.2); border: 1px solid #33436166; border-radius: 8px;
343
+ }
344
+ .syn-ref { color: #7fd1ff; cursor: pointer; border-bottom: 1px dashed #7fd1ff88; }
345
+ .syn-ref:hover { color: #aee4ff; }
346
+
347
+ /* ---------- 帮助面板 ---------- */
348
+ #help-panel {
349
+ position: absolute; top: 12px; right: 12px; width: 340px; max-height: calc(100% - 24px);
350
+ overflow-y: auto; display: none; z-index: 24;
351
+ background: rgba(13, 20, 36, 0.97); border: 1px solid #2a3a5c; border-radius: 8px; padding: 12px;
352
+ }
353
+ #help-panel.show { display: block; }
354
+ .hp-head {
355
+ display: flex; justify-content: space-between; align-items: center;
356
+ color: #7fd1ff; font-size: 14px; font-weight: 600; margin-bottom: 8px;
357
+ }
358
+ .hp-head span:last-child { cursor: pointer; color: #8fa3c0; font-size: 17px; padding: 0 4px; }
359
+ .hp-head span:last-child:hover { color: #d7dee9; }
360
+ .hp-body h3 { color: #7fd1ff; font-size: 12.5px; margin: 12px 0 5px; border-bottom: 1px solid #26334c; padding-bottom: 3px; }
361
+ .hp-p { font-size: 11.5px; color: #b9c8e0; line-height: 1.65; margin: 4px 0; }
362
+ .hp-line { font-size: 11.5px; color: #8fa3c0; margin: 4px 0; line-height: 1.6; }
363
+ .hp-line a, .upd-status a { color: #7fd1ff; text-decoration: underline; }
364
+ .hp-list { margin: 4px 0; padding-left: 16px; font-size: 11.5px; color: #b9c8e0; line-height: 1.75; }
365
+ .hp-list b { color: #d7dee9; }
366
+
367
+ /* ---------- 版本与更新 ---------- */
368
+ .upd-box { border: 1px solid #26334c; border-radius: 8px; padding: 9px 10px; margin-top: 4px; background: rgba(38, 51, 76, 0.2); }
369
+ .upd-ver { font-size: 12px; color: #d7dee9; margin-bottom: 4px; }
370
+ .upd-ver b { color: #7fd1ff; }
371
+ .upd-status { font-size: 11px; color: #8fa3c0; line-height: 1.6; min-height: 15px; word-break: break-all; }
372
+ @media (max-width: 768px) {
373
+ #help-panel { width: calc(100vw - 24px); right: 12px; }
374
+ }