sdtk-wiki-kit 0.1.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.
- package/README.md +262 -0
- package/assets/atlas/build_atlas.py +1110 -0
- package/assets/atlas/doc_atlas_viewer_template.html +3796 -0
- package/assets/atlas/vendor/mermaid.min.js +2029 -0
- package/assets/keys/sdtk-entitlement-public.pem +11 -0
- package/bin/sdtk-wiki.js +14 -0
- package/package.json +45 -0
- package/src/commands/ask.js +139 -0
- package/src/commands/atlas.js +339 -0
- package/src/commands/deferred.js +14 -0
- package/src/commands/help.js +67 -0
- package/src/commands/init.js +91 -0
- package/src/commands/lint.js +48 -0
- package/src/commands/wiki.js +251 -0
- package/src/index.js +65 -0
- package/src/lib/args.js +68 -0
- package/src/lib/browser-open.js +32 -0
- package/src/lib/errors.js +29 -0
- package/src/lib/wiki-ask.js +175 -0
- package/src/lib/wiki-compile.js +287 -0
- package/src/lib/wiki-config.js +180 -0
- package/src/lib/wiki-discover.js +271 -0
- package/src/lib/wiki-flags.js +89 -0
- package/src/lib/wiki-ingest.js +198 -0
- package/src/lib/wiki-lint.js +468 -0
- package/src/lib/wiki-paths.js +169 -0
- package/src/lib/wiki-premium-loader.js +364 -0
- package/src/lib/wiki-prune.js +334 -0
- package/src/lib/wiki-query-history.js +111 -0
- package/src/lib/wiki-runner.js +373 -0
- package/src/lib/wiki-workspace.js +144 -0
|
@@ -0,0 +1,3796 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>SDTK-WIKI Dashboard</title>
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='16' fill='%235f89ff'/%3E%3Ctext x='32' y='42' text-anchor='middle' font-family='Segoe UI,Arial,sans-serif' font-size='28' font-weight='700' fill='white'%3EA%3C/text%3E%3C/svg%3E">
|
|
8
|
+
<style>
|
|
9
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
10
|
+
:root{
|
|
11
|
+
--bg:#0d1117;--surface:#161b22;--surface2:#21262d;--border:#30363d;
|
|
12
|
+
--text:#e6edf3;--text2:#8b949e;--accent:#58a6ff;--red:#f85149;
|
|
13
|
+
--graph-bg:#f8f6f1;--graph-surface:#ffffff;--graph-surface2:#f4efe6;--graph-surface3:#fbfaf7;
|
|
14
|
+
--graph-border:#e4ddd1;--graph-border-strong:#cfc3b2;--graph-text:#1f2a37;--graph-muted:#66758a;
|
|
15
|
+
--graph-shadow:0 28px 60px rgba(15,23,42,0.12);--graph-grid:rgba(126,140,158,0.16);
|
|
16
|
+
--atlas-panel-width:min(560px,calc(100% - 32px));--atlas-panel-expanded-width:min(672px,calc(100% - 32px));
|
|
17
|
+
}
|
|
18
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
19
|
+
background:var(--bg);color:var(--text);font-size:14px;line-height:1.6}
|
|
20
|
+
a{color:var(--accent);text-decoration:none}
|
|
21
|
+
.nav{position:fixed;bottom:0;left:0;right:0;display:flex;background:var(--surface);
|
|
22
|
+
border-top:1px solid var(--border);z-index:100}
|
|
23
|
+
.nav button{flex:1;padding:10px 4px 8px;background:none;border:none;
|
|
24
|
+
color:var(--text2);font-size:11px;cursor:pointer;
|
|
25
|
+
display:flex;flex-direction:column;align-items:center;gap:2px}
|
|
26
|
+
.nav button.active{color:var(--accent)}
|
|
27
|
+
.nav button svg{width:20px;height:20px}
|
|
28
|
+
.panel{display:none;padding:16px;padding-bottom:80px;min-height:100vh}
|
|
29
|
+
.panel.active{display:block}
|
|
30
|
+
.header{padding:20px 0 12px;margin-bottom:8px}
|
|
31
|
+
.header h1{font-size:22px;font-weight:700}
|
|
32
|
+
.header p{color:var(--text2);font-size:12px;margin-top:4px}
|
|
33
|
+
.dash-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:16px}
|
|
34
|
+
.dash-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center}
|
|
35
|
+
.dash-card .num{font-size:28px;font-weight:700;display:block}
|
|
36
|
+
.dash-card .label{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:1px}
|
|
37
|
+
.stat-row{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:12px 14px;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center}
|
|
38
|
+
.stat-row .label{color:var(--text2);font-size:12px}
|
|
39
|
+
.stat-row .val{font-weight:600;font-size:13px}
|
|
40
|
+
.search-box{width:100%;padding:10px 14px;background:var(--surface);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;margin-bottom:12px;outline:none}
|
|
41
|
+
.search-box:focus{border-color:var(--accent)}
|
|
42
|
+
.search-box::placeholder{color:var(--text2)}
|
|
43
|
+
.filter-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}
|
|
44
|
+
.filter-btn{font-size:11px;padding:4px 10px;border-radius:16px;border:1px solid var(--border);background:var(--surface);color:var(--text2);cursor:pointer}
|
|
45
|
+
.filter-btn.active{border-color:var(--accent);color:var(--accent)}
|
|
46
|
+
.cat-header{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:1.5px;margin:16px 0 6px;padding-left:2px}
|
|
47
|
+
.doc-item{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px 14px;margin-bottom:5px;cursor:pointer;transition:border-color .15s}
|
|
48
|
+
.doc-item:hover{border-color:var(--accent);background:var(--surface2)}
|
|
49
|
+
.doc-item .title{font-weight:600;font-size:13px;margin-bottom:2px}
|
|
50
|
+
.doc-item .meta{font-size:11px;color:var(--text2)}
|
|
51
|
+
.tag{display:inline-block;font-size:10px;padding:1px 7px;border-radius:16px;font-weight:600;margin-right:4px}
|
|
52
|
+
.detail{display:none;position:fixed;inset:0;background:var(--bg);z-index:200;overflow-y:auto;padding:16px;padding-bottom:80px}
|
|
53
|
+
.detail.open{display:block}
|
|
54
|
+
.back-btn{background:var(--surface);border:1px solid var(--border);color:var(--accent);padding:7px 14px;border-radius:7px;font-size:13px;cursor:pointer;margin-bottom:14px;display:inline-flex;align-items:center;gap:5px}
|
|
55
|
+
.detail-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:18px;margin-bottom:12px}
|
|
56
|
+
.detail-card h2{font-size:18px;margin-bottom:6px}
|
|
57
|
+
.detail-card h3{font-size:13px;color:var(--accent);margin:14px 0 6px;border-bottom:1px solid var(--border);padding-bottom:3px}
|
|
58
|
+
.detail-card p{margin-bottom:8px;font-size:13px}
|
|
59
|
+
.detail-card ul{margin:4px 0 10px 18px}
|
|
60
|
+
.detail-card li{font-size:12px;margin-bottom:3px}
|
|
61
|
+
.detail-card code{background:var(--surface2);padding:1px 5px;border-radius:3px;font-size:12px;font-family:'SF Mono',Consolas,monospace}
|
|
62
|
+
.wiki-link{color:var(--accent);cursor:pointer;text-decoration:underline dotted}
|
|
63
|
+
.dot{width:9px;height:9px;border-radius:50%;display:inline-block}
|
|
64
|
+
.graph-shell{background:linear-gradient(180deg,#fdfcf9 0%,#f8f4ec 100%);border:1px solid var(--graph-border);border-radius:28px;padding:18px;box-shadow:var(--graph-shadow);color:var(--graph-text)}
|
|
65
|
+
.graph-main-header{margin-bottom:12px}
|
|
66
|
+
.graph-main-header h1{font-size:24px;color:var(--graph-text);font-weight:700}
|
|
67
|
+
.graph-main-header p{color:var(--graph-muted);font-size:12px;margin-top:4px}
|
|
68
|
+
.graph-toolbar{position:relative;background:rgba(255,255,255,0.84);border:1px solid var(--graph-border);border-radius:20px;padding:14px 14px 12px;margin-bottom:14px;box-shadow:0 12px 28px rgba(15,23,42,0.08)}
|
|
69
|
+
.graph-shell .search-box{background:var(--graph-surface3);border:1px solid var(--graph-border);color:var(--graph-text);margin-bottom:10px}
|
|
70
|
+
.graph-shell .search-box:focus{border-color:#5f89ff;box-shadow:0 0 0 3px rgba(95,137,255,0.12)}
|
|
71
|
+
.graph-shell .search-box::placeholder{color:#8a97a8}
|
|
72
|
+
.graph-shell .filter-row{gap:8px;margin-bottom:10px}
|
|
73
|
+
.graph-shell .filter-btn{background:var(--graph-surface3);border:1px solid var(--graph-border);color:var(--graph-muted);padding:6px 12px;font-weight:600}
|
|
74
|
+
.graph-shell .filter-btn.active{background:#ffffff;border-color:#5f89ff;color:#3156c9;box-shadow:0 6px 16px rgba(95,137,255,0.14)}
|
|
75
|
+
.toggle-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}
|
|
76
|
+
.toggle-btn{font-size:11px;padding:6px 10px;border-radius:16px;border:1px solid var(--graph-border);background:var(--graph-surface2);color:var(--graph-muted);cursor:pointer;font-weight:600}
|
|
77
|
+
.toggle-btn.active{background:#ffffff;border-color:#5f89ff;color:#3156c9}
|
|
78
|
+
.zoom-badge{display:inline-flex;align-items:center;justify-content:center;min-width:74px;padding:6px 12px;border-radius:999px;background:#ffffff;border:1px solid var(--graph-border-strong);color:#3156c9;font-size:11px;font-weight:700;box-shadow:0 6px 14px rgba(49,86,201,0.08)}
|
|
79
|
+
.graph-scope-row{display:none;flex-wrap:wrap;align-items:center;gap:8px;margin-top:4px}
|
|
80
|
+
.graph-scope-row.active{display:flex}
|
|
81
|
+
.graph-scope-pill{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;background:#ffffff;border:1px solid var(--graph-border-strong);font-size:11px;font-weight:700;color:#3156c9;box-shadow:0 6px 14px rgba(49,86,201,0.08)}
|
|
82
|
+
.graph-scope-clear{border:none;background:transparent;color:#66758a;font-size:11px;font-weight:700;cursor:pointer;padding:4px 2px}
|
|
83
|
+
.graph-results{display:none;position:absolute;left:14px;right:14px;top:54px;z-index:20;background:rgba(255,255,255,0.98);border:1px solid var(--graph-border);border-radius:14px;box-shadow:0 18px 40px rgba(15,23,42,0.14);padding:8px}
|
|
84
|
+
.graph-results.open{display:block}
|
|
85
|
+
.result-item{background:transparent;border:1px solid transparent;border-radius:10px;padding:9px 10px;margin-bottom:4px;cursor:pointer}
|
|
86
|
+
.result-item:hover,.result-item.active{background:#f7f4ee;border-color:#d9cfbf}
|
|
87
|
+
.result-item strong{display:block;font-size:12px;color:var(--graph-text)}
|
|
88
|
+
.result-item span{font-size:11px;color:var(--graph-muted)}
|
|
89
|
+
.graph-hint{font-size:11px;color:var(--graph-muted);margin-top:2px}
|
|
90
|
+
.graph-stage{position:relative;min-height:720px}
|
|
91
|
+
.graph-canvas-shell{position:relative;background:radial-gradient(circle at 18px 18px, var(--graph-grid) 1.1px, transparent 1.2px) 0 0/36px 36px,linear-gradient(180deg,#ffffff 0%,#fbfaf7 100%);border:1px solid var(--graph-border);border-radius:24px;padding:14px;overflow:hidden;min-height:720px}
|
|
92
|
+
.graph-legend{position:absolute;left:18px;bottom:18px;display:flex;flex-wrap:wrap;gap:8px;max-width:calc(100% - 40px);z-index:2}
|
|
93
|
+
.legend-label{display:inline-flex;align-items:center;padding:6px 8px;font-size:10px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;color:#7a8698}
|
|
94
|
+
.legend-pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:rgba(255,255,255,0.88);border:1px solid var(--graph-border);font-size:11px;color:var(--graph-muted);font-weight:600;backdrop-filter:blur(8px);cursor:pointer}
|
|
95
|
+
.legend-pill.active{background:#ffffff;border-color:#5f89ff;color:#3156c9;box-shadow:0 6px 16px rgba(95,137,255,0.14)}
|
|
96
|
+
.legend-pill.edge{color:#52606f}
|
|
97
|
+
.legend-pill.edge.active{border-color:#3156c9;color:#2349c0}
|
|
98
|
+
#graph-canvas{width:100%;display:block;border-radius:18px;background:transparent;cursor:grab;min-height:684px}
|
|
99
|
+
#graph-canvas.dragging{cursor:grabbing}
|
|
100
|
+
.graph-hover-card{position:absolute;min-width:220px;max-width:320px;padding:12px 14px;border-radius:18px;background:rgba(255,255,255,0.97);border:1px solid var(--graph-border);box-shadow:0 18px 34px rgba(15,23,42,0.14);pointer-events:none;z-index:6;display:none}
|
|
101
|
+
.graph-hover-card.open{display:block}
|
|
102
|
+
.graph-hover-title{font-size:13px;font-weight:700;color:var(--graph-text)}
|
|
103
|
+
.graph-hover-meta{font-size:11px;color:var(--graph-muted);margin-top:4px}
|
|
104
|
+
.graph-hover-body{font-size:12px;color:#41526a;margin-top:8px;line-height:1.55}
|
|
105
|
+
.graph-focus-overlay{position:absolute;top:18px;right:18px;bottom:18px;width:min(430px,34vw);display:flex;align-items:stretch;pointer-events:none;z-index:8}
|
|
106
|
+
.graph-focus-overlay.compact{width:var(--atlas-panel-width)}
|
|
107
|
+
.graph-focus-overlay.expanded{width:var(--atlas-panel-expanded-width)}
|
|
108
|
+
.graph-focus-panel{pointer-events:auto;display:flex;flex-direction:column;width:100%;background:rgba(255,255,255,0.96);border:1px solid var(--graph-border);border-radius:24px;box-shadow:0 18px 34px rgba(15,23,42,0.14);overflow:hidden;backdrop-filter:blur(18px)}
|
|
109
|
+
.graph-focus-header{display:flex;align-items:center;gap:14px;padding:18px 20px;border-bottom:1px solid var(--graph-border)}
|
|
110
|
+
.graph-focus-icon{width:46px;height:46px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;box-shadow:0 10px 24px rgba(15,23,42,0.14)}
|
|
111
|
+
.graph-focus-meta{flex:1;min-width:0}
|
|
112
|
+
.graph-focus-kicker{display:block;font-size:11px;letter-spacing:0.12em;text-transform:uppercase;color:#61748b;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
113
|
+
.graph-focus-doc-title{font-size:15px;font-weight:700;color:var(--graph-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
|
|
114
|
+
.graph-focus-subtle{display:block;font-size:11px;color:#8a97a8;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
115
|
+
.graph-focus-actions{display:flex;align-items:center;gap:8px}
|
|
116
|
+
.graph-history-btn,.graph-overlay-btn,.graph-focus-close{height:34px;border-radius:999px;border:1px solid var(--graph-border);background:#fff;color:#62748a;cursor:pointer}
|
|
117
|
+
.graph-history-btn{min-width:34px;padding:0 10px;font-size:12px;font-weight:700}
|
|
118
|
+
.graph-history-btn:disabled{opacity:.45;cursor:default}
|
|
119
|
+
.graph-overlay-btn{padding:0 12px;font-size:11px;font-weight:700;color:#3156c9}
|
|
120
|
+
.graph-focus-close{width:34px;font-size:20px;line-height:1}
|
|
121
|
+
.graph-detail-card{flex:1;overflow:auto;padding:20px 22px 24px;color:var(--graph-text)}
|
|
122
|
+
.graph-detail-card h2{font-size:18px;margin-bottom:8px;color:var(--graph-text)}
|
|
123
|
+
.graph-detail-card h3{font-size:13px;margin:16px 0 8px;color:#3156c9;border-bottom:1px solid var(--graph-border);padding-bottom:4px}
|
|
124
|
+
.graph-detail-card h4{font-size:12px;margin:12px 0 6px;color:#41526a}
|
|
125
|
+
.graph-detail-card p{margin-bottom:10px;font-size:13px;color:var(--graph-text)}
|
|
126
|
+
.graph-detail-card ul{margin:4px 0 12px 18px}
|
|
127
|
+
.graph-detail-card li{font-size:12px;margin-bottom:5px;color:var(--graph-text)}
|
|
128
|
+
.graph-detail-card code{background:#f3efe7;border:1px solid #e6ded1;padding:1px 5px;border-radius:5px;font-size:12px;font-family:'SF Mono',Consolas,monospace;color:#354153}
|
|
129
|
+
.graph-detail-card .muted{color:var(--graph-muted);font-size:12px}
|
|
130
|
+
.graph-detail-card .subtle{font-size:11px;color:#8893a1;margin-top:-2px}
|
|
131
|
+
.graph-detail-card .full-mode-title{font-size:11px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;color:#7a8698;margin-bottom:10px}
|
|
132
|
+
.graph-note-empty{display:flex;flex-direction:column;justify-content:center;min-height:320px;color:var(--graph-muted)}
|
|
133
|
+
.graph-note-prose{display:flex;flex-direction:column;gap:12px}
|
|
134
|
+
.graph-note-prose p{margin:0;font-size:14px;line-height:1.7;color:#243142}
|
|
135
|
+
.graph-note-prose h3{margin:6px 0 0;font-size:22px;border:none;color:#162132;padding:0}
|
|
136
|
+
.graph-note-prose h4{margin:8px 0 0;font-size:16px;color:#243142}
|
|
137
|
+
.graph-note-prose ul,.graph-note-prose ol{margin:0 0 0 20px}
|
|
138
|
+
.graph-note-prose blockquote{margin:0;padding:10px 14px;border-left:3px solid #b9c7de;background:#f5f8fd;color:#41526a;border-radius:10px}
|
|
139
|
+
.graph-note-prose pre{margin:0;overflow:auto;padding:14px 16px;background:#f3efe7;border:1px solid #e6ded1;border-radius:12px}
|
|
140
|
+
.graph-note-prose pre code{background:none;border:none;padding:0;font-size:12px;display:block;white-space:pre-wrap}
|
|
141
|
+
.graph-note-prose hr{border:none;border-top:1px solid #e6ded1;margin:6px 0}
|
|
142
|
+
.graph-note-mermaid-shell{display:flex;flex-direction:column;gap:8px;padding:14px 16px;border:1px solid #e6ded1;border-radius:12px;background:#fff;box-shadow:0 8px 18px rgba(15,23,42,0.05)}
|
|
143
|
+
.graph-note-mermaid{display:flex;align-items:center;justify-content:center;overflow:auto;min-height:120px}
|
|
144
|
+
.graph-note-mermaid svg{display:block;max-width:100%;height:auto}
|
|
145
|
+
.graph-note-mermaid-status{font-size:11px;color:#66758a}
|
|
146
|
+
.graph-note-mermaid-status.error{color:#b54747;font-weight:600}
|
|
147
|
+
.graph-focus-footer{position:sticky;bottom:0;display:flex;gap:10px;justify-content:space-between;padding:14px 20px 18px;border-top:1px solid var(--graph-border);background:linear-gradient(180deg,rgba(255,255,255,0.78) 0%,rgba(255,255,255,0.98) 45%)}
|
|
148
|
+
.inline-btn{background:#ffffff;border:1px solid var(--graph-border-strong);color:#3156c9;padding:9px 14px;border-radius:12px;font-size:12px;font-weight:700;cursor:pointer;box-shadow:0 6px 14px rgba(49,86,201,0.10)}
|
|
149
|
+
.inline-btn:hover{background:#f6f8ff}
|
|
150
|
+
.graph-badge-row{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px 0}
|
|
151
|
+
.graph-pill{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;background:#f5f1e8;border:1px solid #e6dccd;font-size:11px;font-weight:600;color:#52606f}
|
|
152
|
+
.graph-history-strip{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}
|
|
153
|
+
.graph-history-chip{display:inline-flex;align-items:center;gap:6px;max-width:100%;padding:6px 11px;border-radius:999px;border:1px solid #d9cfbf;background:#fff;color:#52606f;font-size:11px;font-weight:700;cursor:pointer}
|
|
154
|
+
.graph-history-chip.active{background:#eef3ff;border-color:#6f8fff;color:#2349c0}
|
|
155
|
+
.graph-history-chip span{display:inline-block;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
156
|
+
.graph-chip-section{display:flex;flex-direction:column;gap:8px;margin-top:14px}
|
|
157
|
+
.graph-chip-group{display:flex;flex-wrap:wrap;gap:8px}
|
|
158
|
+
.graph-chip-label{font-size:10px;font-weight:700;letter-spacing:0.12em;text-transform:uppercase;color:#7a8698}
|
|
159
|
+
.graph-chip{display:inline-flex;align-items:center;gap:6px;max-width:100%;padding:6px 11px;border-radius:999px;border:1px solid #d9cfbf;background:#fff;color:#3156c9;font-size:11px;font-weight:700;cursor:pointer;box-shadow:0 6px 14px rgba(49,86,201,0.07)}
|
|
160
|
+
.graph-chip:hover{background:#f6f8ff}
|
|
161
|
+
.graph-chip.active{background:#eef3ff;border-color:#6f8fff;color:#2349c0}
|
|
162
|
+
.graph-chip span{display:inline-block;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
163
|
+
.graph-chip small{font-size:10px;color:#6f7f92}
|
|
164
|
+
@media (max-width: 1180px){
|
|
165
|
+
.graph-stage{display:flex;flex-direction:column;gap:16px}
|
|
166
|
+
.graph-focus-overlay{position:relative;top:auto;right:auto;bottom:auto;width:100%;pointer-events:auto}
|
|
167
|
+
.graph-canvas-shell{min-height:560px}
|
|
168
|
+
.graph-chip span{max-width:none}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:linear-gradient(180deg,#eef3f9 0%,#f7f3ec 100%);color:var(--graph-text);font-size:14px;line-height:1.6;height:100vh;overflow:hidden;--nav-width:0px}
|
|
172
|
+
body.nav-open{--nav-width:84px}
|
|
173
|
+
body.embedded-atlas{background:#ffffff;--nav-width:0px}
|
|
174
|
+
body.embedded-atlas .nav{display:none !important}
|
|
175
|
+
body.embedded-atlas .panel{inset:0 !important;left:0 !important;right:0 !important;top:0 !important;bottom:0 !important;padding:0 !important}
|
|
176
|
+
body.embedded-atlas .graph-shell{padding:0;border:none;border-radius:0;box-shadow:none;background:#ffffff}
|
|
177
|
+
body.embedded-atlas .graph-main-header{display:none}
|
|
178
|
+
body.embedded-atlas .graph-stage{height:100%}
|
|
179
|
+
body.embedded-atlas .graph-canvas-shell{min-height:0;height:100%;border:none;border-radius:0;padding:0}
|
|
180
|
+
body.embedded-atlas #graph-canvas{border-radius:0}
|
|
181
|
+
body.embedded-atlas .graph-settings-corner{top:14px;right:14px}
|
|
182
|
+
body.embedded-atlas .graph-footer-status{bottom:0}
|
|
183
|
+
body.embedded-atlas .graph-focus-overlay{top:14px;right:14px;bottom:14px}
|
|
184
|
+
.panel{border-radius:30px;overflow:hidden}
|
|
185
|
+
a{color:#3156c9;text-decoration:none}
|
|
186
|
+
.panel{display:none;position:fixed;top:16px;right:16px;bottom:16px;left:calc(var(--nav-width) + 16px);padding:0;min-height:0;overflow:hidden}
|
|
187
|
+
.panel.active{display:flex;flex-direction:column}
|
|
188
|
+
.nav{position:fixed;top:16px;bottom:16px;left:16px;right:auto;width:72px;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:12px 8px;background:linear-gradient(180deg,rgba(252,250,245,0.97) 0%,rgba(245,239,229,0.98) 100%);border:1px solid #ddcfba;border-radius:28px;box-shadow:0 24px 48px rgba(94,83,63,0.12);z-index:100;gap:12px;overflow:hidden;opacity:0;pointer-events:none;transform:translateX(-18px);transition:opacity .18s ease,transform .18s ease}
|
|
189
|
+
body.nav-open .nav{opacity:1;pointer-events:auto;transform:translateX(0)}
|
|
190
|
+
.nav button{flex:0 0 auto;width:54px;height:54px;padding:0;background:transparent;border:1px solid transparent;color:#6d7d95;font-size:11px;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;border-radius:20px;min-height:54px;transition:all .18s ease}
|
|
191
|
+
.nav button.active{background:linear-gradient(180deg,rgba(255,255,255,0.94) 0%,rgba(247,243,237,0.98) 100%);border-color:#d9cfbf;color:#24344d;box-shadow:0 14px 28px rgba(94,83,63,0.12)}
|
|
192
|
+
.nav button:hover{color:#24344d;background:rgba(255,255,255,0.74)}
|
|
193
|
+
.nav button svg{width:22px;height:22px}
|
|
194
|
+
.nav button .label{display:none}
|
|
195
|
+
.nav-toggle-rail{display:none}
|
|
196
|
+
body.nav-open .nav-toggle-rail{display:flex}
|
|
197
|
+
.screen-shell,.graph-shell{position:relative;height:100%;display:flex;flex-direction:column;min-height:0;background:linear-gradient(180deg,#fdfcf9 0%,#f8f4ec 100%);border:1px solid var(--graph-border);border-radius:30px;padding:18px;box-shadow:var(--graph-shadow);color:var(--graph-text);overflow:hidden}
|
|
198
|
+
.header{padding:4px 2px 14px;margin-bottom:6px;flex:0 0 auto}
|
|
199
|
+
.header-top{display:flex;align-items:center;gap:12px}
|
|
200
|
+
.page-nav-toggle{width:42px;height:42px;border-radius:14px;border:1px solid #d9cfbf;background:rgba(255,255,255,0.92);color:#4a5c76;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 10px 22px rgba(49,86,201,0.08);flex:0 0 auto}
|
|
201
|
+
.page-nav-toggle:hover{background:#ffffff;color:#3156c9}
|
|
202
|
+
.page-nav-toggle svg{width:20px;height:20px}
|
|
203
|
+
body.nav-open .page-nav-toggle{display:none}
|
|
204
|
+
.header h1{font-size:26px;font-weight:750;color:var(--graph-text)}
|
|
205
|
+
.header p{color:var(--graph-muted);font-size:12px;margin-top:4px}
|
|
206
|
+
.screen-toolbar{position:relative;background:rgba(255,255,255,0.84);border:1px solid var(--graph-border);border-radius:20px;padding:14px 14px 12px;margin-bottom:14px;box-shadow:0 12px 28px rgba(15,23,42,0.08);flex:0 0 auto}
|
|
207
|
+
.screen-scroll{flex:1;min-height:0;overflow:auto;padding-right:4px}
|
|
208
|
+
.dash-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px}
|
|
209
|
+
.dash-card{background:rgba(255,255,255,0.9);border:1px solid var(--graph-border);border-radius:18px;padding:16px;text-align:left;box-shadow:0 10px 20px rgba(15,23,42,0.05)}
|
|
210
|
+
.dash-card .num{font-size:30px;font-weight:800;display:block;margin-bottom:8px}
|
|
211
|
+
.dash-card .label{font-size:11px;color:var(--graph-muted);text-transform:uppercase;letter-spacing:0.12em}
|
|
212
|
+
.stat-row{background:rgba(255,255,255,0.88);border:1px solid var(--graph-border);border-radius:16px;padding:14px 16px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center;box-shadow:0 8px 18px rgba(15,23,42,0.05)}
|
|
213
|
+
.stat-row .label{color:var(--graph-muted);font-size:12px}
|
|
214
|
+
.stat-row .val{font-weight:700;font-size:13px}
|
|
215
|
+
#panel-docs .search-box{background:var(--graph-surface3);border:1px solid var(--graph-border);color:var(--graph-text);margin-bottom:10px}
|
|
216
|
+
#panel-docs .search-box:focus{border-color:#5f89ff;box-shadow:0 0 0 3px rgba(95,137,255,0.12)}
|
|
217
|
+
#panel-docs .search-box::placeholder{color:#8a97a8}
|
|
218
|
+
#panel-docs .filter-row{gap:8px;margin-bottom:0}
|
|
219
|
+
#panel-docs .filter-btn{background:var(--graph-surface3);border:1px solid var(--graph-border);color:var(--graph-muted);padding:6px 12px;font-weight:600}
|
|
220
|
+
#panel-docs .filter-btn.active{background:#ffffff;border-color:#5f89ff;color:#3156c9;box-shadow:0 6px 16px rgba(95,137,255,0.14)}
|
|
221
|
+
.doc-item{background:rgba(255,255,255,0.88);border:1px solid var(--graph-border);border-radius:16px;padding:12px 14px;margin-bottom:8px;cursor:pointer;transition:border-color .15s,transform .15s,box-shadow .15s;color:var(--graph-text);box-shadow:0 8px 16px rgba(15,23,42,0.04)}
|
|
222
|
+
.doc-item:hover{border-color:#5f89ff;background:#ffffff;transform:translateY(-1px);box-shadow:0 12px 22px rgba(49,86,201,0.10)}
|
|
223
|
+
.doc-item .title{font-weight:700;font-size:13px;margin-bottom:4px}
|
|
224
|
+
.doc-item .meta{font-size:11px;color:var(--graph-muted)}
|
|
225
|
+
.detail{display:none;position:absolute;inset:10%;z-index:80;background:rgba(255,255,255,0.96);border:1px solid var(--graph-border);border-radius:26px;box-shadow:0 30px 70px rgba(15,23,42,0.22);backdrop-filter:blur(18px);overflow:hidden;color:var(--graph-text)}
|
|
226
|
+
.detail.open{display:flex;flex-direction:column}
|
|
227
|
+
.detail-header{display:flex;align-items:center;gap:14px;padding:22px 34px 18px;border-bottom:1px solid var(--graph-border);background:rgba(255,255,255,0.94);flex:0 0 auto}
|
|
228
|
+
.detail-icon{width:46px;height:46px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;box-shadow:0 10px 24px rgba(15,23,42,0.14);flex:0 0 auto}
|
|
229
|
+
.detail-meta{flex:1;min-width:0}
|
|
230
|
+
.detail-kicker{display:block;font-size:11px;letter-spacing:0.12em;text-transform:uppercase;color:#61748b;font-weight:800;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
231
|
+
.detail-title{font-size:16px;font-weight:800;color:var(--graph-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
|
|
232
|
+
.detail-subtitle{display:block;font-size:11px;color:#8a97a8;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
233
|
+
.detail-actions{display:flex;align-items:center;gap:8px;flex:0 0 auto}
|
|
234
|
+
.detail-nav-btn,.detail-close{height:36px;border-radius:999px;border:1px solid var(--graph-border);background:#fff;color:#62748a;cursor:pointer;box-shadow:0 8px 18px rgba(15,23,42,0.05)}
|
|
235
|
+
.detail-nav-btn{min-width:36px;padding:0 11px;font-size:13px;font-weight:800}
|
|
236
|
+
.detail-nav-btn:disabled{opacity:.42;cursor:default}
|
|
237
|
+
.detail-close{width:36px;font-size:22px;line-height:1}
|
|
238
|
+
.detail-card{flex:1;min-height:0;overflow:auto;box-sizing:border-box;align-self:center;width:min(1180px,calc(100% - 72px));margin:20px auto 24px;padding:24px 28px 28px;background:linear-gradient(180deg,#fdfcf9 0%,#f8f4ec 100%);border:1px solid var(--graph-border);border-radius:22px;box-shadow:0 10px 22px rgba(15,23,42,0.08);color:var(--graph-text)}
|
|
239
|
+
.detail-card h2{font-size:22px;margin-bottom:8px}
|
|
240
|
+
.detail-card h3{font-size:13px;color:#3156c9;margin:16px 0 8px;border-bottom:1px solid var(--graph-border);padding-bottom:4px}
|
|
241
|
+
.detail-card p,.detail-card li{color:var(--graph-text)}
|
|
242
|
+
.detail-card code{background:#f3efe7;border:1px solid #e6ded1;padding:1px 5px;border-radius:5px;font-size:12px;font-family:'SF Mono',Consolas,monospace;color:#354153}
|
|
243
|
+
.graph-shell{padding:18px}
|
|
244
|
+
.graph-main-header{margin-bottom:10px;flex:0 0 auto}
|
|
245
|
+
.graph-main-header h1{font-size:26px}
|
|
246
|
+
.graph-toolbar{flex:0 0 auto;padding:12px 14px 10px;margin-bottom:12px}
|
|
247
|
+
.graph-hint{font-size:11px;color:var(--graph-muted);margin-top:4px}
|
|
248
|
+
.graph-stage{position:relative;display:flex;flex:1;min-height:0}
|
|
249
|
+
.graph-canvas-shell{position:relative;flex:1;min-height:0;height:100%;background:radial-gradient(circle at 18px 18px, var(--graph-grid) 1.1px, transparent 1.2px) 0 0/36px 36px,linear-gradient(180deg,#ffffff 0%,#fbfaf7 100%);border:1px solid var(--graph-border);border-radius:24px;padding:14px;overflow:hidden}
|
|
250
|
+
#graph-canvas{width:100%;height:100%;display:block;border-radius:18px;background:transparent;cursor:grab;min-height:0}
|
|
251
|
+
.graph-focus-overlay{position:absolute;top:14px;right:14px;bottom:14px;width:min(430px,34vw);display:flex;align-items:stretch;pointer-events:none;z-index:8}
|
|
252
|
+
.graph-focus-overlay.compact{width:var(--atlas-panel-width)}
|
|
253
|
+
.graph-focus-overlay.expanded{width:var(--atlas-panel-expanded-width)}
|
|
254
|
+
.graph-legend{left:16px;bottom:16px;max-width:calc(100% - 32px)}
|
|
255
|
+
@media (max-width: 1440px){
|
|
256
|
+
.dash-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
257
|
+
}
|
|
258
|
+
@media (max-width: 1180px){
|
|
259
|
+
body{overflow:auto;height:auto;min-height:100vh}
|
|
260
|
+
.panel{position:relative;inset:auto;left:auto;right:auto;top:auto;bottom:auto;height:auto;min-height:calc(100vh - 32px);padding-left:0}
|
|
261
|
+
body.nav-open{--nav-width:0px}
|
|
262
|
+
.nav{position:relative;top:auto;bottom:auto;left:auto;width:100%;height:auto;flex-direction:row;justify-content:flex-start;overflow:auto;padding:10px;opacity:1;pointer-events:auto;transform:none;margin-bottom:12px}
|
|
263
|
+
.nav button{min-height:54px;min-width:54px}
|
|
264
|
+
.nav-toggle-rail{display:none !important}
|
|
265
|
+
body.nav-open .page-nav-toggle{display:flex}
|
|
266
|
+
.panel.active{min-height:calc(100vh - 132px)}
|
|
267
|
+
.graph-stage{display:flex;flex-direction:column;gap:14px}
|
|
268
|
+
.graph-focus-overlay{position:relative;top:auto;right:auto;bottom:auto;width:100%;pointer-events:auto}
|
|
269
|
+
.graph-canvas-shell{min-height:420px}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.graph-stage{position:relative;display:flex;flex:1;min-height:0}
|
|
273
|
+
.graph-canvas-shell{position:relative;flex:1;min-height:0;height:100%;padding:14px;overflow:hidden}
|
|
274
|
+
.graph-toolbar-overlay{position:absolute;top:14px;left:14px;z-index:9;display:flex;flex-direction:column;width:var(--atlas-panel-width);max-width:calc(100% - 32px);max-height:calc(100% - 28px);background:rgba(255,255,255,0.94);border:1px solid var(--graph-border);border-radius:24px;box-shadow:0 18px 34px rgba(15,23,42,0.12);backdrop-filter:blur(18px);padding:12px 12px 10px;overflow:visible;pointer-events:auto;transition:width .18s ease,box-shadow .18s ease}
|
|
275
|
+
.graph-toolbar-overlay.compact{width:var(--atlas-panel-width)}
|
|
276
|
+
.graph-toolbar-overlay.expanded{width:var(--atlas-panel-expanded-width)}
|
|
277
|
+
.graph-toolbar-overlay.minimized{width:auto;max-width:none;padding:0;background:transparent;border:none;box-shadow:none;backdrop-filter:none;overflow:visible}
|
|
278
|
+
.graph-toolbar-overlay.minimized .graph-toolbar-drag-bar,.graph-toolbar-overlay.minimized .graph-toolbar-top,.graph-toolbar-overlay.minimized .graph-results{display:none}
|
|
279
|
+
.graph-toolbar-peek{display:none;align-items:center;gap:10px;padding:10px 14px;border-radius:18px;border:1px solid var(--graph-border);background:rgba(255,255,255,0.96);box-shadow:0 18px 34px rgba(15,23,42,0.14);color:#3156c9;font-size:12px;font-weight:800;cursor:pointer;user-select:none}
|
|
280
|
+
.graph-toolbar-overlay.minimized .graph-toolbar-peek{display:inline-flex}
|
|
281
|
+
.graph-toolbar-peek-glyph{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#4f78ff 0%,#7f95ff 100%);color:#fff;font-weight:800;box-shadow:0 10px 24px rgba(15,23,42,0.14)}
|
|
282
|
+
.graph-toolbar-drag-bar{display:flex;align-items:center;justify-content:center;gap:5px;padding:4px 0 6px;cursor:move;user-select:none}
|
|
283
|
+
.graph-toolbar-drag-bar span{width:28px;height:4px;border-radius:999px;background:#d8dfe8}
|
|
284
|
+
.graph-toolbar-top{display:flex;align-items:center;gap:8px}
|
|
285
|
+
.graph-toolbar-top .search-box{margin:0;flex:1;min-width:0}
|
|
286
|
+
.graph-toolbar-toggle{width:42px;height:42px;border-radius:14px;border:1px solid var(--graph-border-strong);background:#ffffff;color:#3156c9;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;box-shadow:0 10px 20px rgba(49,86,201,0.10)}
|
|
287
|
+
.graph-toolbar-toggle:hover{background:#f6f8ff}
|
|
288
|
+
.graph-results{box-sizing:border-box;min-height:180px;max-height:min(420px,calc(100dvh - 240px),52vh);overflow:auto;overscroll-behavior:contain;padding:10px 10px 12px;scrollbar-gutter:stable both-edges;border:1px solid var(--graph-border);border-radius:18px;background:rgba(255,255,255,0.98);box-shadow:0 18px 34px rgba(15,23,42,0.14)}
|
|
289
|
+
.graph-results.open{display:flex;flex-direction:column;gap:4px}
|
|
290
|
+
.graph-results-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:4px 2px 8px}
|
|
291
|
+
.graph-results-filter-row{display:flex;flex-wrap:wrap;gap:6px}
|
|
292
|
+
.graph-results-type-btn{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;border:1px solid var(--graph-border-strong);background:#fff;color:#5d6b7e;font-size:11px;font-weight:800;cursor:pointer;box-shadow:0 6px 14px rgba(15,23,42,0.04)}
|
|
293
|
+
.graph-results-type-btn.active{color:#3156c9;border-color:#8aa8ff;background:#f6f8ff}
|
|
294
|
+
.graph-results-meta{font-size:11px;color:#728198}
|
|
295
|
+
.result-kind-tag{display:inline-flex;align-items:center;justify-content:center;min-width:42px;padding:4px 8px;border-radius:999px;border:1px solid #d9cfbf;background:#fff;font-size:10px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#6d7d95}
|
|
296
|
+
.result-kind-tag.ask{border-color:#cad7ff;background:#f6f8ff;color:#3156c9}
|
|
297
|
+
.result-kind-tag.doc{border-color:#d9cfbf;background:#fff;color:#6d7d95}
|
|
298
|
+
.graph-stage{overflow:hidden;border-radius:26px}
|
|
299
|
+
.graph-canvas-shell{border-radius:26px}
|
|
300
|
+
.graph-note-table-wrap{overflow:auto;border:1px solid #e6ded1;border-radius:12px;background:#ffffff;box-shadow:0 8px 18px rgba(15,23,42,0.05)}
|
|
301
|
+
.graph-note-table{width:100%;border-collapse:collapse;min-width:460px}
|
|
302
|
+
.graph-note-table th,.graph-note-table td{padding:10px 12px;border-bottom:1px solid #ece4d8;font-size:12px;line-height:1.55;text-align:left;vertical-align:top}
|
|
303
|
+
.graph-note-table th{background:#f6f1e8;color:#304258;font-size:11px;font-weight:800;letter-spacing:0.04em;text-transform:uppercase}
|
|
304
|
+
.graph-note-table tr:last-child td{border-bottom:none}
|
|
305
|
+
.graph-note-figure{display:flex;flex-direction:column;gap:8px;margin:0;padding:12px;border:1px solid #e6ded1;border-radius:12px;background:#ffffff;box-shadow:0 8px 18px rgba(15,23,42,0.05)}
|
|
306
|
+
.graph-note-image{display:block;max-width:100%;height:auto;border-radius:10px;border:1px solid #ece4d8;background:#fff}
|
|
307
|
+
.graph-note-figure figcaption{font-size:11px;color:#66758a}
|
|
308
|
+
.graph-note-image-stack{display:flex;flex-direction:column;gap:12px}
|
|
309
|
+
.nav button.active svg,.nav button:hover svg,.page-nav-toggle:hover svg{color:#3156c9}
|
|
310
|
+
.graph-toolbar-overlay.compact .graph-results{top:68px}
|
|
311
|
+
.graph-toolbar-overlay .graph-results{left:12px;right:12px;top:70px}
|
|
312
|
+
.graph-toolbar-overlay .graph-hint{margin-top:2px}
|
|
313
|
+
.atlas-ask-launch{cursor:pointer;color:#3156c9}
|
|
314
|
+
.atlas-ask-dock{position:absolute;left:14px;top:auto;bottom:88px;z-index:8;display:flex;flex-direction:column;align-items:flex-start;gap:10px;width:auto;max-width:calc(100% - 32px);pointer-events:auto}
|
|
315
|
+
.atlas-ask-dock.open{width:var(--atlas-panel-width)}
|
|
316
|
+
.atlas-ask-dock:not(.open){width:auto}
|
|
317
|
+
.atlas-ask-peek{display:inline-flex;align-items:center;gap:10px;padding:10px 14px;border-radius:18px;border:1px solid var(--graph-border);background:rgba(255,255,255,0.96);box-shadow:0 18px 34px rgba(15,23,42,0.14);color:#3156c9;font-size:12px;font-weight:800;cursor:pointer;user-select:none}
|
|
318
|
+
.atlas-ask-peek-glyph{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#4f78ff 0%,#7f95ff 100%);color:#fff;font-weight:800;box-shadow:0 10px 24px rgba(15,23,42,0.14)}
|
|
319
|
+
.atlas-ask-overlay{position:relative;display:none;flex-direction:column;width:100%;height:min(620px,calc(100% - 28px));max-height:calc(100% - 28px);min-height:0;background:rgba(255,255,255,0.96);border:1px solid var(--graph-border);border-radius:24px;box-shadow:0 18px 34px rgba(15,23,42,0.12);backdrop-filter:blur(18px);overflow:hidden}
|
|
320
|
+
.atlas-ask-dock.open .atlas-ask-overlay{display:flex}
|
|
321
|
+
.atlas-ask-dock.open .atlas-ask-peek{display:none}
|
|
322
|
+
.atlas-ask-body{display:flex;flex:1 1 auto;flex-direction:column;gap:10px;padding:12px;min-height:0;height:100%;overflow:hidden}
|
|
323
|
+
.atlas-ask-drag-bar{display:flex;align-items:center;justify-content:center;gap:5px;padding:4px 0 2px;cursor:move;user-select:none}
|
|
324
|
+
.atlas-ask-drag-bar span{width:28px;height:4px;border-radius:999px;background:#d8dfe8}
|
|
325
|
+
.atlas-ask-composer{position:relative;display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:22px;border:1px solid #ddcfba;background:linear-gradient(180deg,rgba(255,255,255,0.98) 0%,rgba(247,243,237,0.98) 100%);box-shadow:0 18px 30px rgba(94,83,63,0.12);flex:0 0 auto}
|
|
326
|
+
.atlas-ask-collapse{position:absolute;top:12px;right:12px;z-index:2}
|
|
327
|
+
.atlas-ask-attachments{display:flex;flex-wrap:wrap;gap:8px;padding-right:52px}
|
|
328
|
+
.atlas-ask-attachments:empty{display:none}
|
|
329
|
+
.atlas-ask-attachment-chip{display:inline-flex;align-items:center;gap:10px;max-width:100%;padding:8px 10px;border-radius:14px;border:1px solid #d9cfbf;background:#ffffff;color:#24344d;box-shadow:0 8px 18px rgba(15,23,42,0.06)}
|
|
330
|
+
.atlas-ask-attachment-icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;color:#3156c9;flex:0 0 auto}
|
|
331
|
+
.atlas-ask-attachment-copy{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
332
|
+
.atlas-ask-attachment-copy strong{font-size:12px;font-weight:700;color:#24344d;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px}
|
|
333
|
+
.atlas-ask-attachment-copy span{font-size:11px;color:#6d7d95;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
|
|
334
|
+
.atlas-ask-attachment-remove{flex:0 0 auto;width:20px;height:20px;border-radius:999px;border:1px solid #d9cfbf;background:#fff;color:#6d7d95;cursor:pointer;font-size:12px;line-height:1}
|
|
335
|
+
.atlas-ask-attachment-remove:hover{color:#3156c9;border-color:#aebfe7;background:#f6f8ff}
|
|
336
|
+
.atlas-ask-input-wrap{position:relative}
|
|
337
|
+
.atlas-ask-input{width:100%;min-height:62px;max-height:132px;resize:none;padding:0;border:0;background:transparent;color:#24344d;font:inherit;line-height:1.6;box-sizing:border-box}
|
|
338
|
+
.atlas-ask-input:focus{outline:none;box-shadow:none}
|
|
339
|
+
.atlas-ask-input::placeholder{color:#7b8798}
|
|
340
|
+
.atlas-ask-mention-results{position:absolute;left:0;right:0;top:calc(100% + 8px);display:none;flex-direction:column;gap:0;max-height:220px;overflow:auto;padding:8px;background:rgba(255,255,255,0.98);border:1px solid #d9cfbf;border-radius:18px;box-shadow:0 18px 30px rgba(94,83,63,0.16);z-index:3}
|
|
341
|
+
.atlas-ask-mention-results.open{display:flex}
|
|
342
|
+
.atlas-ask-mention-item{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:10px 12px;border-radius:14px;cursor:pointer;color:#24344d}
|
|
343
|
+
.atlas-ask-mention-item.active,.atlas-ask-mention-item:hover{background:#f7f9ff}
|
|
344
|
+
.atlas-ask-mention-copy{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}
|
|
345
|
+
.atlas-ask-mention-copy strong{font-size:12px;color:#24344d}
|
|
346
|
+
.atlas-ask-mention-copy span{font-size:11px;color:#6d7d95;line-height:1.45;word-break:break-word}
|
|
347
|
+
.atlas-ask-mention-tag{flex:0 0 auto;font-size:10px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:#3156c9}
|
|
348
|
+
.atlas-ask-source-summary{font-size:12px;line-height:1.5;color:#6d7d95}
|
|
349
|
+
.atlas-ask-source-summary strong{color:#24344d}
|
|
350
|
+
.atlas-ask-footer{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:nowrap}
|
|
351
|
+
.atlas-ask-controls{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;flex:1;min-width:0}
|
|
352
|
+
.atlas-ask-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:999px;border:1px solid #d9cfbf;background:#fff;color:#62748a;cursor:pointer;font-size:16px;line-height:1;box-shadow:0 8px 18px rgba(15,23,42,0.06)}
|
|
353
|
+
.atlas-ask-icon-btn:hover{border-color:#aebfe7;color:#3156c9;background:#f6f8ff}
|
|
354
|
+
.atlas-ask-icon-btn:disabled{opacity:.45;cursor:not-allowed}
|
|
355
|
+
.atlas-ask-mode-select{height:36px;min-width:0;padding:0 34px 0 12px;border-radius:999px;border:1px solid #d9cfbf;background-color:#fff;color:#24344d;font-size:12px;font-weight:700;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:linear-gradient(45deg,transparent 50%,#7d8fa8 50%),linear-gradient(135deg,#7d8fa8 50%,transparent 50%);background-position:calc(100% - 18px) calc(50% - 2px),calc(100% - 13px) calc(50% - 2px);background-size:5px 5px,5px 5px;background-repeat:no-repeat;cursor:pointer;box-shadow:0 8px 18px rgba(15,23,42,0.04)}
|
|
356
|
+
.atlas-ask-mode-select::-ms-expand{display:none}
|
|
357
|
+
.atlas-ask-mode-select:focus{outline:none;border-color:#5f89ff;box-shadow:0 0 0 3px rgba(95,137,255,0.12)}
|
|
358
|
+
#atlas-ask-runtime-select{flex:0 0 92px;width:92px}
|
|
359
|
+
#atlas-ask-model-select{flex:0 0 136px;width:136px}
|
|
360
|
+
#atlas-ask-mode-select{flex:1 1 140px;width:140px}
|
|
361
|
+
#atlas-ask-info-toggle{flex:0 0 36px}
|
|
362
|
+
.atlas-ask-send{display:inline-flex;align-items:center;justify-content:center;flex:0 0 42px;width:42px;height:42px;border-radius:999px;border:0;background:linear-gradient(135deg,#4f78ff 0%,#3156c9 100%);color:#ffffff;cursor:pointer;font-size:20px;font-weight:900;box-shadow:0 14px 24px rgba(49,86,201,0.26)}
|
|
363
|
+
.atlas-ask-send:hover{filter:brightness(1.03)}
|
|
364
|
+
.atlas-ask-send:disabled{opacity:.5;cursor:not-allowed}
|
|
365
|
+
.atlas-ask-status{font-size:12px;line-height:1.45;color:#6d7d95}
|
|
366
|
+
.atlas-ask-status.error{color:#c94f5c}
|
|
367
|
+
.atlas-ask-status.busy{color:#3156c9}
|
|
368
|
+
.atlas-ask-typing{display:inline-flex;align-items:center;gap:8px;color:#3156c9;font-weight:700}
|
|
369
|
+
.atlas-ask-typing-text{font-size:12px;line-height:1.4}
|
|
370
|
+
.atlas-ask-typing-dots{display:inline-flex;align-items:center;gap:4px}
|
|
371
|
+
.atlas-ask-typing-dot{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.25;animation:wikiAskTypingPulse 1.15s ease-in-out infinite}
|
|
372
|
+
.atlas-ask-typing-dot:nth-child(2){animation-delay:.18s}
|
|
373
|
+
.atlas-ask-typing-dot:nth-child(3){animation-delay:.36s}
|
|
374
|
+
@keyframes wikiAskTypingPulse{
|
|
375
|
+
0%,80%,100%{opacity:.25;transform:translateY(0)}
|
|
376
|
+
40%{opacity:1;transform:translateY(-2px)}
|
|
377
|
+
}
|
|
378
|
+
.atlas-ask-info-popover{display:none;position:absolute;right:12px;bottom:60px;z-index:4;width:min(300px,calc(100% - 24px));max-height:min(260px,calc(100% - 84px));overflow:auto;scrollbar-gutter:stable}
|
|
379
|
+
.atlas-ask-info-popover.open{display:block}
|
|
380
|
+
.atlas-ask-info-card{padding:12px 14px;border-radius:18px;border:1px solid #d9cfbf;background:rgba(255,255,255,0.98);box-shadow:0 18px 30px rgba(94,83,63,0.16)}
|
|
381
|
+
.atlas-ask-info-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}
|
|
382
|
+
.atlas-ask-info-title{font-size:11px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#61748b}
|
|
383
|
+
.atlas-ask-info-close{width:26px;height:26px;border-radius:999px;border:1px solid #d9cfbf;background:#fff;color:#62748a;cursor:pointer;font-size:16px;line-height:1}
|
|
384
|
+
.atlas-ask-info-close:hover{background:#f6f8ff;color:#3156c9}
|
|
385
|
+
.atlas-ask-advanced-summary{font-size:11px;line-height:1.45;color:#52606f;margin-bottom:10px}
|
|
386
|
+
.atlas-ask-advanced-body{display:flex;flex-direction:column;gap:7px}
|
|
387
|
+
.atlas-ask-meta-item{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;min-width:0}
|
|
388
|
+
.atlas-ask-meta-label{font-size:10px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:#7b8798;flex:0 0 auto}
|
|
389
|
+
.atlas-ask-meta-value{font-size:11px;line-height:1.4;color:#24344d;word-break:break-word;text-align:right;max-width:170px}
|
|
390
|
+
.atlas-ask-answer{display:flex;flex-direction:column;gap:12px;flex:1 1 0;min-height:0;max-height:100%;overflow:auto;padding:0 6px 8px 0;scrollbar-gutter:stable;overscroll-behavior:contain}
|
|
391
|
+
.atlas-ask-thread{display:flex;flex-direction:column;gap:12px;min-height:0}
|
|
392
|
+
.atlas-ask-message{display:flex;flex-direction:column;gap:6px}
|
|
393
|
+
.atlas-ask-message.user{align-items:flex-end}
|
|
394
|
+
.atlas-ask-message-label{font-size:10px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#7b8798}
|
|
395
|
+
.atlas-ask-message-bubble{max-width:88%;padding:12px 14px;border-radius:18px;border:1px solid #d9cfbf;background:#ffffff;color:#24344d;box-shadow:0 10px 20px rgba(15,23,42,0.05);line-height:1.55;white-space:pre-wrap}
|
|
396
|
+
.atlas-ask-message.user .atlas-ask-message-bubble{background:#f6f8ff;border-color:#cad7ff}
|
|
397
|
+
.atlas-ask-answer-card{border:1px solid #e6ded1;border-radius:18px;background:rgba(255,255,255,0.88);box-shadow:0 10px 22px rgba(15,23,42,0.06);padding:14px;max-width:100%;overflow:auto}
|
|
398
|
+
.atlas-ask-answer-card.busy{display:flex;align-items:center;min-height:76px}
|
|
399
|
+
.atlas-ask-answer-card h3{margin:0 0 10px;font-size:14px;color:#24344d}
|
|
400
|
+
.atlas-ask-answer-card .muted{display:block;margin-top:8px}
|
|
401
|
+
.atlas-ask-answer-card ul{margin:8px 0 0 18px;padding:0}
|
|
402
|
+
.atlas-ask-answer-card li{margin:0 0 8px}
|
|
403
|
+
.atlas-ask-empty{padding:12px 14px;border-radius:18px;border:1px dashed #d9cfbf;background:rgba(255,255,255,0.82);color:#6d7d95;font-size:13px;line-height:1.55}
|
|
404
|
+
.atlas-ask-history-list{display:flex;flex-direction:column;gap:8px}
|
|
405
|
+
.atlas-ask-history-item{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border-radius:14px;border:1px solid #e6ded1;background:rgba(255,255,255,0.88);cursor:pointer;text-align:left}
|
|
406
|
+
.atlas-ask-history-item:hover{background:#f7f9ff;border-color:#cad7ff}
|
|
407
|
+
.atlas-ask-history-item strong{font-size:12px;color:#24344d}
|
|
408
|
+
.atlas-ask-history-meta{font-size:11px;color:#6d7d95;line-height:1.4}
|
|
409
|
+
.atlas-ask-history-preview{font-size:12px;color:#52606f;line-height:1.45}
|
|
410
|
+
@media (max-width: 1180px){
|
|
411
|
+
.atlas-ask-dock{position:relative;left:auto;top:auto;bottom:auto;width:100%;max-width:none;margin-top:14px}
|
|
412
|
+
.atlas-ask-dock:not(.open){width:100%}
|
|
413
|
+
.atlas-ask-dock.open{width:100%}
|
|
414
|
+
.atlas-ask-overlay{height:auto;max-height:none}
|
|
415
|
+
.atlas-ask-footer{align-items:stretch;flex-wrap:wrap}
|
|
416
|
+
.atlas-ask-controls{width:100%}
|
|
417
|
+
.atlas-ask-mode-select{min-width:0;flex:1 1 160px}
|
|
418
|
+
.atlas-ask-info-popover{right:14px;left:14px;bottom:64px;width:auto;max-height:min(240px,calc(100% - 84px))}
|
|
419
|
+
.atlas-ask-drag-bar{display:none}
|
|
420
|
+
.graph-settings-dock.graph-settings-corner{position:relative;top:auto;right:auto;margin-left:auto;margin-bottom:12px}
|
|
421
|
+
.graph-settings-dock.graph-settings-corner.shifted,.graph-settings-dock.graph-settings-corner.shifted.expanded{right:auto}
|
|
422
|
+
}
|
|
423
|
+
.graph-footer-status{position:absolute;left:16px;right:16px;bottom:16px;z-index:5;display:block;pointer-events:none}
|
|
424
|
+
.graph-footer-status .zoom-badge{pointer-events:auto}
|
|
425
|
+
.graph-footer-status .graph-legend{position:static;left:auto;bottom:auto;display:flex;flex-direction:column;align-items:flex-start;gap:8px;width:100%;max-width:100%;pointer-events:none}
|
|
426
|
+
.graph-footer-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;max-width:100%;pointer-events:auto}
|
|
427
|
+
.graph-footer-row-groups{width:100%;box-sizing:border-box;flex-wrap:nowrap;overflow:hidden}
|
|
428
|
+
.graph-footer-inline-tools{display:flex;align-items:center;gap:8px;min-width:0;width:100%;max-width:100%;flex:1 1 auto;overflow:hidden;pointer-events:auto}
|
|
429
|
+
.graph-footer-row-controls{display:none;align-items:center;gap:8px}
|
|
430
|
+
.graph-footer-control-group{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
431
|
+
.graph-footer-control-group .legend-label{padding:6px 4px}
|
|
432
|
+
.graph-footer-filter-row{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;margin:0;min-width:0;overflow:hidden;flex:0 1 auto}
|
|
433
|
+
#graph-family-filters{flex:1 1 auto}
|
|
434
|
+
.graph-footer-filter-row .filter-btn,.graph-footer-control-group .toggle-btn{margin:0;white-space:nowrap}
|
|
435
|
+
.graph-footer-row-controls .graph-scope-row{margin:0}
|
|
436
|
+
.graph-footer-row-controls.active{display:flex}
|
|
437
|
+
.graph-footer-row-controls .zoom-badge{margin-left:2px}
|
|
438
|
+
.graph-family-more-wrap{position:relative;display:inline-flex;align-items:center;align-self:center;flex:0 0 auto;pointer-events:auto}
|
|
439
|
+
.graph-family-more-panel{display:none;position:absolute;left:0;bottom:calc(100% + 10px);z-index:12;min-width:230px;max-width:min(420px,calc(100vw - 80px));max-height:280px;overflow:auto;flex-direction:column;gap:6px;padding:10px;border:1px solid var(--graph-border);border-radius:18px;background:rgba(255,255,255,0.98);box-shadow:0 18px 34px rgba(15,23,42,0.16);backdrop-filter:blur(18px)}
|
|
440
|
+
.graph-family-more-panel.open{display:flex}
|
|
441
|
+
.graph-family-more-panel .legend-pill{justify-content:flex-start;width:100%;margin:0}
|
|
442
|
+
.graph-settings-dock{position:relative;display:inline-flex;align-items:center;gap:8px;pointer-events:auto}
|
|
443
|
+
.graph-settings-dock.graph-settings-corner{position:absolute;top:14px;right:14px;z-index:10}
|
|
444
|
+
.graph-settings-dock.graph-settings-corner.shifted{right:calc(var(--atlas-panel-width) + 28px)}
|
|
445
|
+
.graph-settings-dock.graph-settings-corner.shifted.expanded{right:calc(var(--atlas-panel-expanded-width) + 28px)}
|
|
446
|
+
.graph-settings-toggle{display:inline-flex;align-items:center;justify-content:center;gap:6px;margin:0;line-height:1;white-space:nowrap}
|
|
447
|
+
.graph-settings-panel{display:none;position:absolute;left:0;bottom:calc(100% + 10px);z-index:12;width:min(320px,calc(100vw - 80px));max-height:min(420px,calc(100vh - 160px));overflow:auto;flex-direction:column;gap:12px;padding:14px;border:1px solid var(--graph-border);border-radius:20px;background:rgba(255,255,255,0.98);box-shadow:0 18px 34px rgba(15,23,42,0.16);backdrop-filter:blur(18px)}
|
|
448
|
+
.graph-settings-dock.graph-settings-corner .graph-settings-panel{left:auto;right:0;top:calc(100% + 10px);bottom:auto}
|
|
449
|
+
.graph-settings-panel.open{display:flex}
|
|
450
|
+
.graph-settings-section{display:flex;flex-direction:column;gap:8px;padding-bottom:10px;border-bottom:1px solid #e6ded1}
|
|
451
|
+
.graph-settings-section:last-child{border-bottom:none;padding-bottom:0}
|
|
452
|
+
.graph-settings-title{font-size:10px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:#7a8698}
|
|
453
|
+
.graph-settings-panel .graph-footer-filter-row,.graph-settings-panel .graph-footer-control-group{display:flex;flex-wrap:wrap;gap:8px;overflow:visible}
|
|
454
|
+
.graph-settings-panel .zoom-badge{align-self:flex-start}
|
|
455
|
+
.graph-focus-overlay.hidden{display:none}
|
|
456
|
+
.graph-focus-overlay{position:absolute;top:14px;right:14px;bottom:auto;width:var(--atlas-panel-width);height:min(680px,calc(100% - 28px));max-height:calc(100% - 28px);display:flex;align-items:stretch;pointer-events:none;z-index:8;transition:width .18s ease}
|
|
457
|
+
.graph-focus-overlay.compact{width:var(--atlas-panel-width)}
|
|
458
|
+
.graph-focus-overlay.expanded{width:var(--atlas-panel-expanded-width)}
|
|
459
|
+
.graph-focus-overlay.minimized{width:auto;height:auto;max-height:none;bottom:auto;align-items:flex-start;pointer-events:auto}
|
|
460
|
+
.graph-focus-overlay.collapsed{bottom:auto;align-items:flex-start}
|
|
461
|
+
.graph-focus-panel{height:100%;max-height:100%;min-height:0}
|
|
462
|
+
.graph-focus-panel.collapsed{height:auto;max-height:min(220px,calc(100vh - 120px))}
|
|
463
|
+
.graph-focus-drag-bar{display:flex;align-items:center;justify-content:center;gap:5px;padding:6px 0 0;cursor:move;user-select:none}
|
|
464
|
+
.graph-focus-drag-bar span{width:28px;height:4px;border-radius:999px;background:#d8dfe8}
|
|
465
|
+
.graph-focus-panel.collapsed .graph-focus-header{padding:12px 14px;gap:10px;align-items:center}
|
|
466
|
+
.graph-focus-panel.collapsed .graph-focus-icon{width:36px;height:36px;flex:0 0 36px;font-size:13px}
|
|
467
|
+
.graph-focus-panel.collapsed .graph-focus-meta{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
468
|
+
.graph-focus-panel.collapsed .graph-focus-kicker{font-size:10px;line-height:1.1;margin:0}
|
|
469
|
+
.graph-focus-panel.collapsed .graph-focus-doc-title{font-size:13px;line-height:1.15;margin-top:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
470
|
+
.graph-focus-panel.collapsed .graph-focus-subtle{display:none}
|
|
471
|
+
.graph-focus-panel.collapsed .graph-focus-actions{gap:6px;flex-wrap:nowrap}
|
|
472
|
+
.graph-focus-panel.collapsed .graph-history-btn,.graph-focus-panel.collapsed .graph-overlay-btn,.graph-focus-panel.collapsed .graph-focus-toggle,.graph-focus-panel.collapsed .graph-focus-minimize,.graph-focus-panel.collapsed .graph-focus-close{height:30px;min-width:30px;padding:0 8px;font-size:11px}
|
|
473
|
+
.graph-focus-panel.minimized{background:transparent;border:none;box-shadow:none;backdrop-filter:none;overflow:visible}
|
|
474
|
+
.graph-focus-peek{display:inline-flex;align-items:center;gap:10px;padding:10px 14px;border-radius:18px;border:1px solid var(--graph-border);background:rgba(255,255,255,0.96);box-shadow:0 18px 34px rgba(15,23,42,0.14);color:#3156c9;font-size:12px;font-weight:800;cursor:move;user-select:none}
|
|
475
|
+
.graph-focus-peek-glyph{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;box-shadow:0 10px 24px rgba(15,23,42,0.14)}
|
|
476
|
+
.graph-focus-toggle,.graph-focus-minimize{width:34px;height:34px;border-radius:999px;border:1px solid var(--graph-border);background:#fff;color:#62748a;cursor:pointer;font-size:18px;line-height:1}
|
|
477
|
+
.graph-focus-toggle{font-size:15px;font-weight:800}
|
|
478
|
+
.graph-note-empty{justify-content:flex-start;min-height:0;padding-top:6px}
|
|
479
|
+
.graph-note-empty h2{margin-bottom:8px}
|
|
480
|
+
.graph-note-empty .muted{max-width:320px}
|
|
481
|
+
@media (max-width: 1180px){
|
|
482
|
+
.graph-toolbar-overlay,.graph-toolbar-overlay.compact,.graph-toolbar-overlay.minimized{position:relative;top:auto;left:auto;width:100%;max-width:none;margin-bottom:14px}
|
|
483
|
+
.graph-focus-overlay,.graph-focus-overlay.minimized{position:relative;top:auto;right:auto;bottom:auto;width:100%;height:auto;max-height:none;pointer-events:auto}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
</style>
|
|
487
|
+
</head>
|
|
488
|
+
<body>
|
|
489
|
+
|
|
490
|
+
<div id="panel-dash" class="panel active">
|
|
491
|
+
<div class="screen-shell">
|
|
492
|
+
<div class="header">
|
|
493
|
+
<div class="header-top">
|
|
494
|
+
<button class="page-nav-toggle" onclick="toggleSidebar()" aria-label="Toggle SDTK-WIKI navigation" title="Toggle SDTK-WIKI navigation">
|
|
495
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
496
|
+
<line x1="5" y1="7" x2="19" y2="7"/>
|
|
497
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
498
|
+
<line x1="5" y1="17" x2="19" y2="17"/>
|
|
499
|
+
</svg>
|
|
500
|
+
</button>
|
|
501
|
+
<h1>SDTK-WIKI Dashboard</h1>
|
|
502
|
+
</div>
|
|
503
|
+
<p>Project-local wiki and knowledge graph - generated __ATLAS_GENERATED__</p>
|
|
504
|
+
</div>
|
|
505
|
+
<div class="screen-scroll">
|
|
506
|
+
<div class="dash-grid">
|
|
507
|
+
<div class="dash-card" style="border-color:#58a6ff">
|
|
508
|
+
<span class="num" style="color:#58a6ff" id="stat-total">-</span>
|
|
509
|
+
<span class="label">Total Docs</span>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="dash-card" style="border-color:#3fb950">
|
|
512
|
+
<span class="num" style="color:#3fb950" id="stat-families">-</span>
|
|
513
|
+
<span class="label">Families</span>
|
|
514
|
+
</div>
|
|
515
|
+
<div class="dash-card" style="border-color:#f0883e">
|
|
516
|
+
<span class="num" style="color:#f0883e" id="stat-edges">-</span>
|
|
517
|
+
<span class="label">Graph Edges</span>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="dash-card" style="border-color:#d2a8ff">
|
|
520
|
+
<span class="num" style="color:#d2a8ff" id="stat-issues">-</span>
|
|
521
|
+
<span class="label">BK Issues</span>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
<div id="dash-family-rows"></div>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
<div id="panel-docs" class="panel">
|
|
530
|
+
<div class="screen-shell">
|
|
531
|
+
<div class="header">
|
|
532
|
+
<div class="header-top">
|
|
533
|
+
<button class="page-nav-toggle" onclick="toggleSidebar()" aria-label="Toggle SDTK-WIKI navigation" title="Toggle SDTK-WIKI navigation">
|
|
534
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
535
|
+
<line x1="5" y1="7" x2="19" y2="7"/>
|
|
536
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
537
|
+
<line x1="5" y1="17" x2="19" y2="17"/>
|
|
538
|
+
</svg>
|
|
539
|
+
</button>
|
|
540
|
+
<h1>SDTK-WIKI Docs View</h1>
|
|
541
|
+
</div>
|
|
542
|
+
<p>Search indexed markdown docs, then open structured detail without leaving the SDTK-WIKI workspace.</p>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="screen-toolbar">
|
|
545
|
+
<input class="search-box" placeholder="Search docs..." id="search" autocomplete="off">
|
|
546
|
+
<div class="filter-row" id="family-filters"></div>
|
|
547
|
+
</div>
|
|
548
|
+
<div class="screen-scroll">
|
|
549
|
+
<div id="doc-list"></div>
|
|
550
|
+
</div>
|
|
551
|
+
<div class="detail" id="detail-panel" role="dialog" aria-modal="true" aria-labelledby="detail-title">
|
|
552
|
+
<div class="detail-header">
|
|
553
|
+
<div class="detail-icon" id="detail-icon">D</div>
|
|
554
|
+
<div class="detail-meta">
|
|
555
|
+
<span class="detail-kicker" id="detail-kicker">Document</span>
|
|
556
|
+
<div class="detail-title" id="detail-title">Document detail</div>
|
|
557
|
+
<span class="detail-subtitle" id="detail-subtitle"></span>
|
|
558
|
+
</div>
|
|
559
|
+
<div class="detail-actions">
|
|
560
|
+
<button class="detail-nav-btn" id="detail-prev" onclick="navigateDetail(-1)" aria-label="Previous document" title="Previous document">←</button>
|
|
561
|
+
<button class="detail-nav-btn" id="detail-next" onclick="navigateDetail(1)" aria-label="Next document" title="Next document">→</button>
|
|
562
|
+
<button class="detail-close" onclick="closeDetail()" aria-label="Close document detail" title="Close document detail">×</button>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
<div class="detail-card" id="detail-content"></div>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
<div id="panel-graph" class="panel">
|
|
571
|
+
<div class="graph-shell">
|
|
572
|
+
<div class="graph-main-header">
|
|
573
|
+
<div class="header-top">
|
|
574
|
+
<button class="page-nav-toggle" onclick="toggleSidebar()" aria-label="Toggle SDTK-WIKI navigation" title="Toggle SDTK-WIKI navigation">
|
|
575
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
576
|
+
<line x1="5" y1="7" x2="19" y2="7"/>
|
|
577
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
578
|
+
<line x1="5" y1="17" x2="19" y2="17"/>
|
|
579
|
+
</svg>
|
|
580
|
+
</button>
|
|
581
|
+
<h1>SDTK-WIKI Knowledge Graph</h1>
|
|
582
|
+
</div>
|
|
583
|
+
<p>Explore the live SDTK document graph, surface feature context fast, and trace specs, reviews, skills, and knowledge objects in one visual workspace.</p>
|
|
584
|
+
</div>
|
|
585
|
+
<div class="graph-stage">
|
|
586
|
+
<div class="graph-canvas-shell">
|
|
587
|
+
<div class="graph-toolbar-overlay minimized" id="graph-toolbar-overlay">
|
|
588
|
+
<div class="graph-toolbar-drag-bar" id="graph-toolbar-drag-bar" title="Drag search and filter overlay">
|
|
589
|
+
<span></span>
|
|
590
|
+
</div>
|
|
591
|
+
<button class="graph-toolbar-peek" id="graph-toolbar-peek" onclick="openGraphToolbarPeek(event)" title="Open SDTK-WIKI Search">
|
|
592
|
+
<span class="graph-toolbar-peek-glyph">S</span>
|
|
593
|
+
<span>Search SDTK-WIKI</span>
|
|
594
|
+
</button>
|
|
595
|
+
<div class="graph-toolbar-top">
|
|
596
|
+
<input class="search-box" placeholder="Search docs and saved asks..." id="graph-search" autocomplete="off">
|
|
597
|
+
<button class="graph-toolbar-toggle" id="graph-toolbar-toggle" onclick="toggleGraphToolbarMinimized(true)" aria-label="Close SDTK-WIKI Search" title="Close SDTK-WIKI Search">
|
|
598
|
+
<span id="graph-toolbar-toggle-icon">▾</span>
|
|
599
|
+
</button>
|
|
600
|
+
</div>
|
|
601
|
+
<div class="graph-results" id="graph-search-results"></div>
|
|
602
|
+
</div>
|
|
603
|
+
<div class="atlas-ask-dock" id="atlas-ask-dock">
|
|
604
|
+
<button class="atlas-ask-peek" id="atlas-ask-launch" title="Open Ask SDTK-WIKI">
|
|
605
|
+
<span class="atlas-ask-peek-glyph">A</span>
|
|
606
|
+
<span>Ask SDTK-WIKI</span>
|
|
607
|
+
</button>
|
|
608
|
+
<div class="atlas-ask-overlay" id="atlas-ask-overlay">
|
|
609
|
+
<div class="atlas-ask-body">
|
|
610
|
+
<div class="atlas-ask-drag-bar" id="atlas-ask-drag-bar" title="Drag Ask SDTK-WIKI panel">
|
|
611
|
+
<span></span>
|
|
612
|
+
</div>
|
|
613
|
+
<div class="atlas-ask-composer">
|
|
614
|
+
<button class="graph-focus-toggle atlas-ask-collapse" id="atlas-ask-toggle" title="Hide Ask SDTK-WIKI" onclick="toggleWikiAsk(false)">▾</button>
|
|
615
|
+
<div class="atlas-ask-attachments" id="atlas-ask-attachments"></div>
|
|
616
|
+
<div class="atlas-ask-input-wrap">
|
|
617
|
+
<textarea class="atlas-ask-input" id="atlas-ask-input" placeholder="Ask about the visible graph group, current focus, or attached docs..."></textarea>
|
|
618
|
+
<div class="atlas-ask-mention-results" id="atlas-ask-mention-results"></div>
|
|
619
|
+
</div>
|
|
620
|
+
<div class="atlas-ask-source-summary" id="atlas-ask-source-summary"></div>
|
|
621
|
+
<div class="atlas-ask-footer">
|
|
622
|
+
<div class="atlas-ask-controls">
|
|
623
|
+
<select class="atlas-ask-mode-select" id="atlas-ask-runtime-select" onchange="setWikiAskRuntimeAgent(this.value)" aria-label="Select runtime agent"></select>
|
|
624
|
+
<select class="atlas-ask-mode-select" id="atlas-ask-model-select" onchange="setWikiAskModel(this.value)" aria-label="Select model"></select>
|
|
625
|
+
<select class="atlas-ask-mode-select" id="atlas-ask-mode-select" onchange="setWikiAskMode(this.value)" aria-label="Select grounding mode">
|
|
626
|
+
<option value="visible-group">Visible Group</option>
|
|
627
|
+
<option value="current-focus">Current Focus</option>
|
|
628
|
+
<option value="selected-docs">Selected Docs</option>
|
|
629
|
+
</select>
|
|
630
|
+
<button class="atlas-ask-icon-btn" id="atlas-ask-info-toggle" title="Show grounding info" onclick="toggleWikiAskAdvanced()">i</button>
|
|
631
|
+
</div>
|
|
632
|
+
<button class="atlas-ask-send" id="atlas-ask-submit" onclick="submitWikiAsk()" aria-label="Submit SDTK-WIKI Ask" title="Submit SDTK-WIKI Ask">↑</button>
|
|
633
|
+
</div>
|
|
634
|
+
<div class="atlas-ask-info-popover" id="atlas-ask-info-popover"></div>
|
|
635
|
+
<div class="atlas-ask-status" id="atlas-ask-status">Enter sends. Shift+Enter adds a newline. Answers stay grounded to the SDTK-WIKI source pack you selected.</div>
|
|
636
|
+
</div>
|
|
637
|
+
<div class="atlas-ask-answer" id="atlas-ask-answer"></div>
|
|
638
|
+
</div>
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
<div class="graph-settings-dock graph-settings-corner" id="graph-settings-dock">
|
|
642
|
+
<button class="filter-btn legend-pill graph-settings-toggle" id="graph-settings-toggle" onclick="toggleGraphSettings()">Settings</button>
|
|
643
|
+
<div class="graph-settings-panel" id="graph-settings-panel">
|
|
644
|
+
<div class="graph-settings-section">
|
|
645
|
+
<span class="graph-settings-title">Links</span>
|
|
646
|
+
<div class="filter-row graph-footer-filter-row" id="graph-edge-filters"></div>
|
|
647
|
+
</div>
|
|
648
|
+
<div class="graph-settings-section">
|
|
649
|
+
<span class="graph-settings-title">Cluster</span>
|
|
650
|
+
<div class="graph-footer-control-group">
|
|
651
|
+
<button class="toggle-btn active" id="graph-group-family">Family</button>
|
|
652
|
+
<button class="toggle-btn" id="graph-group-folder">Folder</button>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
<div class="graph-settings-section">
|
|
656
|
+
<span class="graph-settings-title">Camera</span>
|
|
657
|
+
<div class="graph-footer-control-group">
|
|
658
|
+
<button class="toggle-btn" id="graph-zoom-in">Zoom In</button>
|
|
659
|
+
<button class="toggle-btn" id="graph-zoom-out">Zoom Out</button>
|
|
660
|
+
<button class="toggle-btn" id="graph-reset-view">Reset View</button>
|
|
661
|
+
<button class="toggle-btn" id="graph-fit-visible">Fit Visible</button>
|
|
662
|
+
<button class="toggle-btn" id="graph-clear-focus">Clear Focus</button>
|
|
663
|
+
<span class="zoom-badge" id="graph-zoom-badge">100% zoom</span>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
<canvas id="graph-canvas"></canvas>
|
|
669
|
+
<div class="graph-hover-card" id="graph-hover-card"></div>
|
|
670
|
+
<div class="graph-footer-status" id="graph-footer-status">
|
|
671
|
+
<div class="graph-legend" id="graph-legend">
|
|
672
|
+
<div class="graph-footer-row graph-footer-row-groups">
|
|
673
|
+
<div class="graph-footer-inline-tools">
|
|
674
|
+
<div class="filter-row graph-footer-filter-row" id="graph-family-filters"></div>
|
|
675
|
+
<div class="graph-family-more-wrap" id="graph-family-more-wrap"></div>
|
|
676
|
+
</div>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="graph-footer-row graph-footer-row-controls">
|
|
679
|
+
<div class="graph-scope-row" id="graph-scope-row"></div>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
</div>
|
|
683
|
+
</div>
|
|
684
|
+
<aside class="graph-focus-overlay compact" id="graph-focus-overlay">
|
|
685
|
+
<div class="graph-focus-panel" id="graph-focus-panel"></div>
|
|
686
|
+
</aside>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
<nav class="nav" id="atlas-nav">
|
|
693
|
+
<button class="nav-toggle-rail" id="nav-toggle-rail" onclick="toggleSidebar()" aria-label="Hide SDTK-WIKI navigation" title="Hide SDTK-WIKI navigation">
|
|
694
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
695
|
+
<line x1="5" y1="7" x2="19" y2="7"/>
|
|
696
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
697
|
+
<line x1="5" y1="17" x2="19" y2="17"/>
|
|
698
|
+
</svg>
|
|
699
|
+
</button>
|
|
700
|
+
<button class="nav-panel-btn active" data-panel="dash" onclick="showPanel('dash',this)" title="SDTK-WIKI Dashboard" aria-label="SDTK-WIKI Dashboard">
|
|
701
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
702
|
+
<rect x="3" y="3" width="7" height="7" rx="1"/>
|
|
703
|
+
<rect x="14" y="3" width="7" height="7" rx="1"/>
|
|
704
|
+
<rect x="3" y="14" width="7" height="7" rx="1"/>
|
|
705
|
+
<rect x="14" y="14" width="7" height="7" rx="1"/>
|
|
706
|
+
</svg>
|
|
707
|
+
<span class="label">Dashboard</span>
|
|
708
|
+
</button>
|
|
709
|
+
<button class="nav-panel-btn" data-panel="docs" onclick="showPanel('docs',this)" title="SDTK-WIKI Docs View" aria-label="SDTK-WIKI Docs View">
|
|
710
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
711
|
+
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/>
|
|
712
|
+
<polyline points="14,2 14,8 20,8"/>
|
|
713
|
+
</svg>
|
|
714
|
+
<span class="label">Docs</span>
|
|
715
|
+
</button>
|
|
716
|
+
<button class="nav-panel-btn" data-panel="graph" onclick="showPanel('graph',this)" title="SDTK-WIKI Knowledge Graph" aria-label="SDTK-WIKI Knowledge Graph">
|
|
717
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
718
|
+
<circle cx="6" cy="6" r="3"/><circle cx="18" cy="18" r="3"/>
|
|
719
|
+
<circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/>
|
|
720
|
+
<line x1="9" y1="6" x2="15" y2="6"/>
|
|
721
|
+
<line x1="9" y1="18" x2="15" y2="18"/>
|
|
722
|
+
<line x1="6" y1="9" x2="6" y2="15"/>
|
|
723
|
+
<line x1="18" y1="9" x2="18" y2="15"/>
|
|
724
|
+
</svg>
|
|
725
|
+
<span class="label">Graph</span>
|
|
726
|
+
</button>
|
|
727
|
+
</nav>
|
|
728
|
+
|
|
729
|
+
<script src="./mermaid.min.js"></script>
|
|
730
|
+
<script>
|
|
731
|
+
const INDEX = __ATLAS_INDEX_JSON__;
|
|
732
|
+
const GRAPH = __ATLAS_GRAPH_JSON__;
|
|
733
|
+
const FAMILY_COLORS = __ATLAS_FAMILY_COLORS_JSON__;
|
|
734
|
+
const FAMILY_GLYPHS = {
|
|
735
|
+
'governance': 'G', 'product': 'P', 'spec': 'S', 'architecture': 'A', 'database': 'D',
|
|
736
|
+
'api': 'I', 'qa': 'Q', 'design': 'L', 'dev': 'V', 'skill': 'K', 'template': 'T',
|
|
737
|
+
'governance-core': 'G', 'backlog': 'B', 'review-artifact': 'R', 'knowledge-object': 'K',
|
|
738
|
+
'toolkit-skill': 'S', 'toolkit-template': 'T', 'toolkit-guide': 'I', 'docs-site-guide': 'D', 'guide': 'D',
|
|
739
|
+
'root-readme': 'H', 'other-markdown': 'M',
|
|
740
|
+
};
|
|
741
|
+
const GRAPH_EDGE_COLORS = { references_path: 'rgba(95,137,255,0.38)', references_wiki_link: 'rgba(46,181,125,0.44)' };
|
|
742
|
+
const GRAPH_FRAME_MS = 40;
|
|
743
|
+
(function initDash() {
|
|
744
|
+
const docs = INDEX.documents;
|
|
745
|
+
const families = new Set(docs.map(d => d.family));
|
|
746
|
+
const issues = new Set(docs.flatMap(d => d.issues));
|
|
747
|
+
const edges = GRAPH.edges.filter(e => e.type !== 'same_family');
|
|
748
|
+
document.getElementById('stat-total').textContent = docs.length;
|
|
749
|
+
document.getElementById('stat-families').textContent = families.size;
|
|
750
|
+
document.getElementById('stat-edges').textContent = edges.length;
|
|
751
|
+
document.getElementById('stat-issues').textContent = issues.size;
|
|
752
|
+
const famCounts = {};
|
|
753
|
+
docs.forEach(d => { famCounts[d.family] = (famCounts[d.family] || 0) + 1; });
|
|
754
|
+
const rows = document.getElementById('dash-family-rows');
|
|
755
|
+
Object.entries(famCounts).sort((a,b)=>b[1]-a[1]).forEach(([fam,cnt]) => {
|
|
756
|
+
const color = FAMILY_COLORS[fam] || '#8b949e';
|
|
757
|
+
const row = document.createElement('div');
|
|
758
|
+
row.className = 'stat-row';
|
|
759
|
+
row.innerHTML = `<span class="label"><span class="dot" style="background:${color}"></span> ${fam}</span><span class="val" style="color:${color}">${cnt}</span>`;
|
|
760
|
+
rows.appendChild(row);
|
|
761
|
+
});
|
|
762
|
+
})();
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
function applySidebarState(collapsed) {
|
|
766
|
+
document.body.classList.toggle('nav-open', !!collapsed);
|
|
767
|
+
document.querySelectorAll('.page-nav-toggle').forEach(toggle => {
|
|
768
|
+
toggle.setAttribute('aria-label', collapsed ? 'Hide SDTK-WIKI navigation' : 'Show SDTK-WIKI navigation');
|
|
769
|
+
toggle.title = collapsed ? 'Hide SDTK-WIKI navigation' : 'Show SDTK-WIKI navigation';
|
|
770
|
+
});
|
|
771
|
+
if (document.getElementById('panel-graph').classList.contains('active')) {
|
|
772
|
+
graphState.layoutKey = null;
|
|
773
|
+
drawGraph();
|
|
774
|
+
syncWikiAskPosition();
|
|
775
|
+
syncGraphOverlayPositions();
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function toggleSidebar() {
|
|
779
|
+
const next = !document.body.classList.contains('nav-open');
|
|
780
|
+
applySidebarState(next);
|
|
781
|
+
try { localStorage.setItem(ATLAS_NAV_STORAGE_KEY, next ? '1' : '0'); } catch (error) {}
|
|
782
|
+
}
|
|
783
|
+
(function initSidebarState() {
|
|
784
|
+
let collapsed = true;
|
|
785
|
+
try {
|
|
786
|
+
const stored = localStorage.getItem(ATLAS_NAV_STORAGE_KEY);
|
|
787
|
+
collapsed = stored === null ? true : stored === '1';
|
|
788
|
+
} catch (error) {}
|
|
789
|
+
applySidebarState(collapsed);
|
|
790
|
+
})();
|
|
791
|
+
|
|
792
|
+
function showPanel(name, btn) {
|
|
793
|
+
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
|
794
|
+
document.getElementById('panel-' + name).classList.add('active');
|
|
795
|
+
document.querySelectorAll('.nav-panel-btn').forEach(b => b.classList.remove('active'));
|
|
796
|
+
if (btn) btn.classList.add('active');
|
|
797
|
+
document.title = PANEL_TITLES[name] || PANEL_TITLES.dash;
|
|
798
|
+
if (name === 'graph') {
|
|
799
|
+
setTimeout(() => {
|
|
800
|
+
drawGraph();
|
|
801
|
+
syncWikiAskPosition();
|
|
802
|
+
syncGraphOverlayPositions();
|
|
803
|
+
startGraphAnimation();
|
|
804
|
+
}, 80);
|
|
805
|
+
} else {
|
|
806
|
+
stopGraphAnimation();
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let activeFamily = null;
|
|
811
|
+
const docs = INDEX.documents;
|
|
812
|
+
const famSet = [...new Set(docs.map(d => d.family))].sort();
|
|
813
|
+
const familyCounts = {};
|
|
814
|
+
let currentDocListIds = docs.map(doc => doc.id);
|
|
815
|
+
const detailState = { currentId: null, docIds: currentDocListIds.slice() };
|
|
816
|
+
docs.forEach(doc => { familyCounts[doc.family] = (familyCounts[doc.family] || 0) + 1; });
|
|
817
|
+
const docById = {};
|
|
818
|
+
docs.forEach(d => { docById[d.id] = d; });
|
|
819
|
+
const docEdgeTypes = new Set(['references_path', 'references_wiki_link']);
|
|
820
|
+
const docEdges = GRAPH.edges.filter(e => docEdgeTypes.has(e.type) && docById[e.source] && docById[e.target]);
|
|
821
|
+
const nodeStats = {};
|
|
822
|
+
const adjacency = {};
|
|
823
|
+
docs.forEach(d => {
|
|
824
|
+
nodeStats[d.id] = { degree: 0 };
|
|
825
|
+
adjacency[d.id] = [];
|
|
826
|
+
});
|
|
827
|
+
docEdges.forEach(e => {
|
|
828
|
+
nodeStats[e.source].degree += 1;
|
|
829
|
+
nodeStats[e.target].degree += 1;
|
|
830
|
+
adjacency[e.source].push({ id: e.target, type: e.type, direction: 'out' });
|
|
831
|
+
adjacency[e.target].push({ id: e.source, type: e.type, direction: 'in' });
|
|
832
|
+
});
|
|
833
|
+
const graphState = {
|
|
834
|
+
positions: null,
|
|
835
|
+
renderedPositions: [],
|
|
836
|
+
layoutKey: null,
|
|
837
|
+
selectedId: null,
|
|
838
|
+
hoveredId: null,
|
|
839
|
+
familyFilter: null,
|
|
840
|
+
footerFamilySelection: null,
|
|
841
|
+
familyOverflowOpen: false,
|
|
842
|
+
settingsOpen: false,
|
|
843
|
+
scopeFilter: null,
|
|
844
|
+
edgeFilter: 'all',
|
|
845
|
+
groupMode: 'family',
|
|
846
|
+
motionTime: 0,
|
|
847
|
+
lastFrameTs: null,
|
|
848
|
+
animationHandle: null,
|
|
849
|
+
motionSeeds: {},
|
|
850
|
+
layoutCache: {},
|
|
851
|
+
layoutCacheOrder: [],
|
|
852
|
+
history: [],
|
|
853
|
+
historyIndex: -1,
|
|
854
|
+
searchResults: [],
|
|
855
|
+
searchResultIndex: -1,
|
|
856
|
+
searchTypeFilter: 'all',
|
|
857
|
+
overlayMode: 'compact',
|
|
858
|
+
overlayCollapsed: false,
|
|
859
|
+
fitVisibleRequested: false,
|
|
860
|
+
zoom: 1,
|
|
861
|
+
targetZoom: 1,
|
|
862
|
+
panX: 0,
|
|
863
|
+
panY: 0,
|
|
864
|
+
targetPanX: 0,
|
|
865
|
+
targetPanY: 0,
|
|
866
|
+
autoFocus: false,
|
|
867
|
+
isDragging: false,
|
|
868
|
+
didDrag: false,
|
|
869
|
+
dragLastPoint: null,
|
|
870
|
+
detailMode: 'summary',
|
|
871
|
+
manualViewport: false,
|
|
872
|
+
toolbarExpanded: false,
|
|
873
|
+
toolbarMinimized: true,
|
|
874
|
+
overlayMinimized: false,
|
|
875
|
+
toolbarPosition: null,
|
|
876
|
+
focusPosition: null,
|
|
877
|
+
suppressFocusPeekClick: false,
|
|
878
|
+
suppressToolbarPeekClick: false,
|
|
879
|
+
};
|
|
880
|
+
const wikiAskState = {
|
|
881
|
+
open: false,
|
|
882
|
+
mode: 'visible-group',
|
|
883
|
+
selectedDocIds: [],
|
|
884
|
+
status: 'idle',
|
|
885
|
+
answer: null,
|
|
886
|
+
error: '',
|
|
887
|
+
health: null,
|
|
888
|
+
healthLoaded: false,
|
|
889
|
+
question: '',
|
|
890
|
+
lastQuestion: '',
|
|
891
|
+
pendingQuestion: '',
|
|
892
|
+
turns: [],
|
|
893
|
+
runtimeAgent: '',
|
|
894
|
+
model: '',
|
|
895
|
+
advancedOpen: false,
|
|
896
|
+
mention: null,
|
|
897
|
+
mentionIndex: -1,
|
|
898
|
+
history: [],
|
|
899
|
+
historyLoaded: false,
|
|
900
|
+
historyError: '',
|
|
901
|
+
position: null,
|
|
902
|
+
suppressLaunchClick: false,
|
|
903
|
+
};
|
|
904
|
+
const PANEL_TITLES = { dash: 'SDTK-WIKI Dashboard', docs: 'SDTK-WIKI Docs View', graph: 'SDTK-WIKI Knowledge Graph' };
|
|
905
|
+
const ATLAS_NAV_STORAGE_KEY = 'sdtkAtlasNavOpen';
|
|
906
|
+
const WIKI_ASK_POSITION_STORAGE_KEY = 'sdtkWikiAskDockPositionV3';
|
|
907
|
+
const GRAPH_TOOLBAR_POSITION_STORAGE_KEY = 'sdtkAtlasGraphToolbarPositionV3';
|
|
908
|
+
const GRAPH_FOCUS_POSITION_STORAGE_KEY = 'sdtkAtlasGraphFocusPosition';
|
|
909
|
+
const VIEW_PARAMS = new URLSearchParams(window.location.search);
|
|
910
|
+
const EMBEDDED_ATLAS = VIEW_PARAMS.get('embedded') === '1';
|
|
911
|
+
const REQUESTED_PANEL = VIEW_PARAMS.get('panel');
|
|
912
|
+
const INITIAL_PANEL = ['dash', 'docs', 'graph'].includes(REQUESTED_PANEL) ? REQUESTED_PANEL : (EMBEDDED_ATLAS ? 'graph' : 'dash');
|
|
913
|
+
const EMBEDDED_ATLAS_READY_MESSAGE = 'sdtk-atlas-viewer-ready';
|
|
914
|
+
let embeddedAtlasReadySent = false;
|
|
915
|
+
const wikiAskDragState = { active: false, moved: false, startX: 0, startY: 0, originX: 0, originY: 0 };
|
|
916
|
+
const graphOverlayDragState = { active: false, kind: '', moved: false, startX: 0, startY: 0, originX: 0, originY: 0 };
|
|
917
|
+
const WIKI_ASK_RUNTIME_OPTIONS = [
|
|
918
|
+
{ id: 'claude', label: 'Claude', models: ['claude-sonnet-4-6', 'claude-opus-4-1', 'claude-haiku-4-5'] },
|
|
919
|
+
{ id: 'gpt', label: 'GPT', models: ['gpt-5.4', 'gpt-5.4-mini', 'gpt-5.3-codex'] },
|
|
920
|
+
];
|
|
921
|
+
const WIKI_ASK_MODEL_LABELS = {
|
|
922
|
+
'claude-sonnet-4-6': 'Claude Sonnet 4.6',
|
|
923
|
+
'claude-opus-4-1': 'Claude Opus',
|
|
924
|
+
'claude-haiku-4-5': 'Claude Haiku',
|
|
925
|
+
'gpt-5.4': 'GPT-5.4',
|
|
926
|
+
'gpt-5.4-mini': 'GPT-5.4 Mini',
|
|
927
|
+
'gpt-5.3-codex': 'GPT-5.3 Codex',
|
|
928
|
+
};
|
|
929
|
+
if (EMBEDDED_ATLAS) {
|
|
930
|
+
document.body.classList.add('embedded-atlas');
|
|
931
|
+
}
|
|
932
|
+
function notifyEmbeddedAtlasReady() {
|
|
933
|
+
if (!EMBEDDED_ATLAS || embeddedAtlasReadySent || !window.parent || window.parent === window) return;
|
|
934
|
+
embeddedAtlasReadySent = true;
|
|
935
|
+
try {
|
|
936
|
+
window.parent.postMessage({
|
|
937
|
+
type: EMBEDDED_ATLAS_READY_MESSAGE,
|
|
938
|
+
panel: 'graph',
|
|
939
|
+
stamp: VIEW_PARAMS.get('ts') || '',
|
|
940
|
+
}, '*');
|
|
941
|
+
} catch (error) {}
|
|
942
|
+
}
|
|
943
|
+
const initialPanelButton = document.querySelector(`.nav-panel-btn[data-panel="${INITIAL_PANEL}"]`);
|
|
944
|
+
showPanel(INITIAL_PANEL, initialPanelButton);
|
|
945
|
+
function escapeHtml(value) {
|
|
946
|
+
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
947
|
+
}
|
|
948
|
+
function escapeHtmlAttribute(value) {
|
|
949
|
+
return escapeHtml(value).replace(/"/g, '"').replace(/'/g, ''');
|
|
950
|
+
}
|
|
951
|
+
let mermaidInitialized = false;
|
|
952
|
+
let mermaidRenderSerial = 0;
|
|
953
|
+
function escapeMermaidQuotedLabel(value) {
|
|
954
|
+
return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '"');
|
|
955
|
+
}
|
|
956
|
+
function normalizeMermaidRectLabels(line) {
|
|
957
|
+
const input = String(line || '');
|
|
958
|
+
let cursor = 0;
|
|
959
|
+
let normalized = '';
|
|
960
|
+
while (cursor < input.length) {
|
|
961
|
+
const openIndex = input.indexOf('[', cursor);
|
|
962
|
+
if (openIndex === -1) {
|
|
963
|
+
normalized += input.slice(cursor);
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
const nextChar = input[openIndex + 1] || '';
|
|
967
|
+
let idStart = openIndex;
|
|
968
|
+
while (idStart > cursor && /[A-Za-z0-9_.:-]/.test(input[idStart - 1])) idStart -= 1;
|
|
969
|
+
const hasNodeId = idStart < openIndex && /[A-Za-z0-9_]/.test(input[openIndex - 1] || '');
|
|
970
|
+
const skip = !hasNodeId || nextChar === '[' || nextChar === '"' || nextChar === "'";
|
|
971
|
+
if (skip) {
|
|
972
|
+
normalized += input.slice(cursor, openIndex + 1);
|
|
973
|
+
cursor = openIndex + 1;
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
let depth = 1;
|
|
977
|
+
let closeIndex = -1;
|
|
978
|
+
let quote = null;
|
|
979
|
+
for (let index = openIndex + 1; index < input.length; index += 1) {
|
|
980
|
+
const char = input[index];
|
|
981
|
+
const prevChar = index > openIndex + 1 ? input[index - 1] : '';
|
|
982
|
+
if (quote) {
|
|
983
|
+
if (char === quote && prevChar !== '\\') quote = null;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if ((char === '"' || char === "'") && prevChar !== '\\') {
|
|
987
|
+
quote = char;
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
if (char === '[') {
|
|
991
|
+
depth += 1;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (char === ']') {
|
|
995
|
+
depth -= 1;
|
|
996
|
+
if (depth === 0) {
|
|
997
|
+
closeIndex = index;
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (closeIndex === -1) {
|
|
1003
|
+
normalized += input.slice(cursor);
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
const label = input.slice(openIndex + 1, closeIndex);
|
|
1007
|
+
normalized += input.slice(cursor, openIndex) + `["${escapeMermaidQuotedLabel(label)}"]`;
|
|
1008
|
+
cursor = closeIndex + 1;
|
|
1009
|
+
}
|
|
1010
|
+
return normalized;
|
|
1011
|
+
}
|
|
1012
|
+
function normalizeMermaidSource(source) {
|
|
1013
|
+
return String(source || '').split('\n').map(normalizeMermaidRectLabels).join('\n');
|
|
1014
|
+
}
|
|
1015
|
+
function renderMermaidBlock(source) {
|
|
1016
|
+
const encoded = encodeURIComponent(String(source || ''));
|
|
1017
|
+
return `<div class="graph-note-mermaid-shell"><div class="graph-note-mermaid" data-mermaid-source="${escapeHtmlAttribute(encoded)}"><div class="graph-note-mermaid-status">Rendering Mermaid diagram...</div></div></div>`;
|
|
1018
|
+
}
|
|
1019
|
+
async function renderMermaidDiagrams(root) {
|
|
1020
|
+
const scope = root || document;
|
|
1021
|
+
const nodes = Array.from(scope.querySelectorAll('.graph-note-mermaid')).filter(node => !node.dataset.mermaidRendered);
|
|
1022
|
+
if (!nodes.length) return;
|
|
1023
|
+
if (typeof mermaid === 'undefined') {
|
|
1024
|
+
nodes.forEach(node => {
|
|
1025
|
+
node.dataset.mermaidRendered = 'error';
|
|
1026
|
+
node.innerHTML = '<div class="graph-note-mermaid-status error">Mermaid runtime is unavailable in this atlas viewer.</div>';
|
|
1027
|
+
});
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (!mermaidInitialized) {
|
|
1031
|
+
mermaid.initialize({ startOnLoad: false, securityLevel: 'loose', theme: 'neutral', fontFamily: 'inherit' });
|
|
1032
|
+
mermaidInitialized = true;
|
|
1033
|
+
}
|
|
1034
|
+
const renderSerial = ++mermaidRenderSerial;
|
|
1035
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
1036
|
+
const node = nodes[index];
|
|
1037
|
+
const encoded = node.getAttribute('data-mermaid-source') || '';
|
|
1038
|
+
const source = decodeURIComponent(encoded);
|
|
1039
|
+
const normalizedSource = normalizeMermaidSource(source);
|
|
1040
|
+
try {
|
|
1041
|
+
const result = await mermaid.render(`sdtk-atlas-mermaid-${renderSerial}-${index}`, normalizedSource);
|
|
1042
|
+
const svg = typeof result === 'string' ? result : result.svg;
|
|
1043
|
+
node.innerHTML = svg;
|
|
1044
|
+
if (result && typeof result.bindFunctions === 'function') result.bindFunctions(node);
|
|
1045
|
+
node.dataset.mermaidRendered = '1';
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
node.dataset.mermaidRendered = 'error';
|
|
1048
|
+
node.innerHTML = `<pre><code>${escapeHtml(source)}</code></pre><div class="graph-note-mermaid-status error">Mermaid render failed after atlas normalization: ${escapeHtml(error && error.message ? error.message : String(error))}</div>`;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function scheduleMermaidRender(root) {
|
|
1053
|
+
if (!root) return;
|
|
1054
|
+
requestAnimationFrame(() => {
|
|
1055
|
+
renderMermaidDiagrams(root);
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
function normalizeEmbeddedAssetPath(rawPath) {
|
|
1059
|
+
let value = String(rawPath || '').trim();
|
|
1060
|
+
if (!value) return '';
|
|
1061
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
1062
|
+
value = value.slice(1, -1).trim();
|
|
1063
|
+
}
|
|
1064
|
+
if (value.startsWith('<') && value.endsWith('>')) {
|
|
1065
|
+
value = value.slice(1, -1).trim();
|
|
1066
|
+
}
|
|
1067
|
+
const titleMatch = value.match(/^(\S+)\s+(?:"[^"]*"|'[^']*')$/);
|
|
1068
|
+
if (titleMatch) {
|
|
1069
|
+
value = titleMatch[1].trim();
|
|
1070
|
+
}
|
|
1071
|
+
return value;
|
|
1072
|
+
}
|
|
1073
|
+
function resolveEmbeddedAssetPath(rawPath) {
|
|
1074
|
+
const value = normalizeEmbeddedAssetPath(rawPath);
|
|
1075
|
+
if (!value) return '';
|
|
1076
|
+
if (/^(https?:|data:|file:|#)/i.test(value)) return value;
|
|
1077
|
+
return `../../../../${value.replace(/^\.\//, '').replace(/\\/g, '/')}`;
|
|
1078
|
+
}
|
|
1079
|
+
function extractHtmlAttribute(rawHtml, name) {
|
|
1080
|
+
const match = String(rawHtml || '').match(new RegExp(`${name}\s*=\s*["']([^"']+)["']`, 'i'));
|
|
1081
|
+
return match ? match[1] : '';
|
|
1082
|
+
}
|
|
1083
|
+
function renderMarkdownImage(altText, assetPath) {
|
|
1084
|
+
const resolvedPath = resolveEmbeddedAssetPath(assetPath);
|
|
1085
|
+
const safeAlt = escapeHtml(altText || 'Embedded SDTK asset');
|
|
1086
|
+
const safePath = escapeHtml(resolvedPath);
|
|
1087
|
+
return `<figure class="graph-note-figure"><img class="graph-note-image" src="${safePath}" alt="${safeAlt}" loading="lazy">${safeAlt ? `<figcaption>${safeAlt}</figcaption>` : ''}</figure>`;
|
|
1088
|
+
}
|
|
1089
|
+
function renderHtmlImageTag(rawHtml) {
|
|
1090
|
+
const assetPath = extractHtmlAttribute(rawHtml, 'src');
|
|
1091
|
+
const altText = extractHtmlAttribute(rawHtml, 'alt') || extractHtmlAttribute(rawHtml, 'title');
|
|
1092
|
+
if (!assetPath) return `<pre><code>${escapeHtml(rawHtml)}</code></pre>`;
|
|
1093
|
+
return renderMarkdownImage(altText, assetPath);
|
|
1094
|
+
}
|
|
1095
|
+
function formatInlineMarkdown(value) {
|
|
1096
|
+
let html = escapeHtml(value);
|
|
1097
|
+
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, assetPath) => renderMarkdownImage(alt, assetPath));
|
|
1098
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
1099
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<span class="wiki-link">$1</span>');
|
|
1100
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
1101
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
1102
|
+
return html;
|
|
1103
|
+
}
|
|
1104
|
+
function renderHtmlTable(rawHtml) {
|
|
1105
|
+
try {
|
|
1106
|
+
const parser = new DOMParser();
|
|
1107
|
+
const parsed = parser.parseFromString(`<body>${rawHtml}</body>`, 'text/html');
|
|
1108
|
+
const table = parsed.querySelector('table');
|
|
1109
|
+
if (!table) throw new Error('missing table');
|
|
1110
|
+
const allRows = [...table.querySelectorAll('tr')];
|
|
1111
|
+
if (!allRows.length) return `<pre><code>${escapeHtml(rawHtml)}</code></pre>`;
|
|
1112
|
+
const explicitHead = table.querySelector('thead tr');
|
|
1113
|
+
let headers = [];
|
|
1114
|
+
let rows = [];
|
|
1115
|
+
if (explicitHead) {
|
|
1116
|
+
headers = [...explicitHead.children].map(cell => (cell.textContent || '').trim());
|
|
1117
|
+
rows = [...table.querySelectorAll('tbody tr')].map(row => [...row.children].map(cell => (cell.textContent || '').trim()));
|
|
1118
|
+
} else {
|
|
1119
|
+
const firstCells = [...allRows[0].children];
|
|
1120
|
+
const firstIsHeader = firstCells.some(cell => cell.tagName.toLowerCase() === 'th');
|
|
1121
|
+
if (firstIsHeader) {
|
|
1122
|
+
headers = firstCells.map(cell => (cell.textContent || '').trim());
|
|
1123
|
+
rows = allRows.slice(1).map(row => [...row.children].map(cell => (cell.textContent || '').trim()));
|
|
1124
|
+
} else {
|
|
1125
|
+
headers = firstCells.map((_, index) => `Column ${index + 1}`);
|
|
1126
|
+
rows = allRows.map(row => [...row.children].map(cell => (cell.textContent || '').trim()));
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return renderMarkdownTable(headers, rows);
|
|
1130
|
+
} catch (_error) {
|
|
1131
|
+
return `<pre><code>${escapeHtml(rawHtml)}</code></pre>`;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function stashSupportedHtmlEmbeds(markdown) {
|
|
1135
|
+
const htmlBlocks = [];
|
|
1136
|
+
const stash = (html) => {
|
|
1137
|
+
const token = `__ATLAS_HTML_BLOCK_${htmlBlocks.length}__`;
|
|
1138
|
+
htmlBlocks.push({ token, html });
|
|
1139
|
+
return `\n${token}\n`;
|
|
1140
|
+
};
|
|
1141
|
+
let source = String(markdown || '');
|
|
1142
|
+
source = source.replace(/<table\b[\s\S]*?<\/table>/gi, (rawHtml) => stash(renderHtmlTable(rawHtml)));
|
|
1143
|
+
source = source.replace(/<p\b[^>]*>\s*((?:<a\b[^>]*>\s*)?<img\b[^>]*>(?:\s*<\/a>)?\s*)+<\/p>/gi, (rawHtml) => {
|
|
1144
|
+
const figures = [...rawHtml.matchAll(/<img\b[^>]*>/gi)].map(match => renderHtmlImageTag(match[0])).join('');
|
|
1145
|
+
return figures ? stash(`<div class="graph-note-image-stack">${figures}</div>`) : rawHtml;
|
|
1146
|
+
});
|
|
1147
|
+
source = source.replace(/<img\b[^>]*>/gi, (rawHtml) => stash(renderHtmlImageTag(rawHtml)));
|
|
1148
|
+
return { source, htmlBlocks };
|
|
1149
|
+
}
|
|
1150
|
+
function folderGroupForDoc(doc) {
|
|
1151
|
+
const parts = doc.id.split('/');
|
|
1152
|
+
if (parts.length <= 1) return 'repo-root';
|
|
1153
|
+
return parts.slice(0, Math.min(parts.length, 2)).join('/');
|
|
1154
|
+
}
|
|
1155
|
+
function hashString(value) {
|
|
1156
|
+
let hash = 0;
|
|
1157
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1158
|
+
hash = ((hash << 5) - hash) + value.charCodeAt(i);
|
|
1159
|
+
hash |= 0;
|
|
1160
|
+
}
|
|
1161
|
+
return Math.abs(hash);
|
|
1162
|
+
}
|
|
1163
|
+
function sortNeighbors(items) {
|
|
1164
|
+
return [...items].sort((a, b) => {
|
|
1165
|
+
const degreeDiff = (nodeStats[b.id]?.degree || 0) - (nodeStats[a.id]?.degree || 0);
|
|
1166
|
+
if (degreeDiff !== 0) return degreeDiff;
|
|
1167
|
+
return (docById[a.id]?.title || a.id).localeCompare(docById[b.id]?.title || b.id);
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
function getVisibleDocs() {
|
|
1171
|
+
let visible = graphState.familyFilter ? docs.filter(doc => doc.family === graphState.familyFilter) : docs;
|
|
1172
|
+
if (graphState.scopeFilter && graphState.scopeFilter.docIds) {
|
|
1173
|
+
visible = visible.filter(doc => graphState.scopeFilter.docIds.has(doc.id));
|
|
1174
|
+
}
|
|
1175
|
+
return visible;
|
|
1176
|
+
}
|
|
1177
|
+
function getVisibleDocIds() {
|
|
1178
|
+
return new Set(getVisibleDocs().map(doc => doc.id));
|
|
1179
|
+
}
|
|
1180
|
+
function getVisibleDocEdges(visibleIds) {
|
|
1181
|
+
return docEdges.filter(edge => {
|
|
1182
|
+
if (!visibleIds.has(edge.source) || !visibleIds.has(edge.target)) return false;
|
|
1183
|
+
return graphState.edgeFilter === 'all' || edge.type === graphState.edgeFilter;
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
function normalizeGraphFocus(visibleIds) {
|
|
1187
|
+
if (graphState.selectedId && !visibleIds.has(graphState.selectedId)) graphState.selectedId = null;
|
|
1188
|
+
if (graphState.hoveredId && !visibleIds.has(graphState.hoveredId)) graphState.hoveredId = null;
|
|
1189
|
+
if (!graphState.selectedId) graphState.detailMode = 'summary';
|
|
1190
|
+
}
|
|
1191
|
+
function getGraphFocusId() {
|
|
1192
|
+
return graphState.selectedId;
|
|
1193
|
+
}
|
|
1194
|
+
function getNeighborIds(id, visibleEdges) {
|
|
1195
|
+
const ids = new Set();
|
|
1196
|
+
visibleEdges.forEach(edge => {
|
|
1197
|
+
if (edge.source === id) ids.add(edge.target);
|
|
1198
|
+
if (edge.target === id) ids.add(edge.source);
|
|
1199
|
+
});
|
|
1200
|
+
return ids;
|
|
1201
|
+
}
|
|
1202
|
+
function updateZoomBadge() {
|
|
1203
|
+
const badge = document.getElementById('graph-zoom-badge');
|
|
1204
|
+
if (!badge) return;
|
|
1205
|
+
badge.textContent = `${Math.round(graphState.zoom * 100)}% zoom`;
|
|
1206
|
+
}
|
|
1207
|
+
function getScopeDocIds(kind, value) {
|
|
1208
|
+
return docs.filter(doc => {
|
|
1209
|
+
if (kind === 'issue') return doc.issues.includes(value);
|
|
1210
|
+
if (kind === 'knowledge') return doc.knowledge_ids.includes(value);
|
|
1211
|
+
if (kind === 'skill') return doc.skill_refs.includes(value);
|
|
1212
|
+
if (kind === 'template') return doc.template_refs.includes(value);
|
|
1213
|
+
if (kind === 'release') return doc.release_refs.includes(value);
|
|
1214
|
+
if (kind === 'lane') return doc.lane_refs.includes(value);
|
|
1215
|
+
return false;
|
|
1216
|
+
}).map(doc => doc.id);
|
|
1217
|
+
}
|
|
1218
|
+
function getScopeLabel(kind) {
|
|
1219
|
+
return ({ issue: 'Issue', knowledge: 'Knowledge', skill: 'Skill', template: 'Template', release: 'Release', lane: 'Lane' }[kind]) || 'Scope';
|
|
1220
|
+
}
|
|
1221
|
+
function getScopeValueLabel(kind, value) {
|
|
1222
|
+
if (kind === 'template') {
|
|
1223
|
+
const parts = value.split('/');
|
|
1224
|
+
return parts[parts.length - 1] || value;
|
|
1225
|
+
}
|
|
1226
|
+
return value;
|
|
1227
|
+
}
|
|
1228
|
+
function pushGraphHistory(id) {
|
|
1229
|
+
if (!id) return;
|
|
1230
|
+
const current = graphState.history[graphState.historyIndex];
|
|
1231
|
+
if (current === id) return;
|
|
1232
|
+
graphState.history = graphState.history.slice(0, graphState.historyIndex + 1);
|
|
1233
|
+
graphState.history.push(id);
|
|
1234
|
+
graphState.historyIndex = graphState.history.length - 1;
|
|
1235
|
+
}
|
|
1236
|
+
function jumpGraphHistory(index) {
|
|
1237
|
+
if (index < 0 || index >= graphState.history.length) return;
|
|
1238
|
+
graphState.historyIndex = index;
|
|
1239
|
+
setGraphSelection(graphState.history[index], { pushHistory: false, clearSearch: false, detailMode: 'summary', animate: true });
|
|
1240
|
+
}
|
|
1241
|
+
function renderGraphHistoryStrip() {
|
|
1242
|
+
if (!graphState.history.length) return '';
|
|
1243
|
+
const start = Math.max(0, graphState.history.length - 5);
|
|
1244
|
+
const chips = graphState.history.slice(start).map((id, offset) => {
|
|
1245
|
+
const index = start + offset;
|
|
1246
|
+
const doc = docById[id];
|
|
1247
|
+
const label = doc ? (doc.title || doc.id) : id;
|
|
1248
|
+
const active = index === graphState.historyIndex;
|
|
1249
|
+
return `<button class="graph-history-chip${active ? ' active' : ''}" onclick="jumpGraphHistory(${index})"><small>${index + 1}</small><span>${escapeHtml(label)}</span></button>`;
|
|
1250
|
+
}).join('');
|
|
1251
|
+
return `<div class="graph-chip-section"><div class="graph-chip-label">Recent Focus</div><div class="graph-history-strip">${chips}</div></div>`;
|
|
1252
|
+
}
|
|
1253
|
+
function syncGraphOverlayMode() {
|
|
1254
|
+
const overlay = document.getElementById('graph-focus-overlay');
|
|
1255
|
+
if (!overlay) return;
|
|
1256
|
+
overlay.classList.toggle('compact', graphState.overlayMode === 'compact');
|
|
1257
|
+
overlay.classList.toggle('expanded', graphState.overlayMode === 'expanded');
|
|
1258
|
+
overlay.classList.toggle('collapsed', !!graphState.overlayCollapsed);
|
|
1259
|
+
overlay.classList.toggle('minimized', !!graphState.overlayMinimized);
|
|
1260
|
+
}
|
|
1261
|
+
function readGraphOverlayPosition(storageKey) {
|
|
1262
|
+
try {
|
|
1263
|
+
const raw = localStorage.getItem(storageKey);
|
|
1264
|
+
if (!raw) return null;
|
|
1265
|
+
const parsed = JSON.parse(raw);
|
|
1266
|
+
if (!parsed || !Number.isFinite(parsed.x) || !Number.isFinite(parsed.y)) return null;
|
|
1267
|
+
return { x: parsed.x, y: parsed.y };
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
function saveGraphOverlayPosition(storageKey, position) {
|
|
1273
|
+
try {
|
|
1274
|
+
if (!position) localStorage.removeItem(storageKey);
|
|
1275
|
+
else localStorage.setItem(storageKey, JSON.stringify(position));
|
|
1276
|
+
} catch (error) {}
|
|
1277
|
+
}
|
|
1278
|
+
graphState.toolbarPosition = readGraphOverlayPosition(GRAPH_TOOLBAR_POSITION_STORAGE_KEY);
|
|
1279
|
+
graphState.focusPosition = readGraphOverlayPosition(GRAPH_FOCUS_POSITION_STORAGE_KEY);
|
|
1280
|
+
function getGraphOverlayElements(kind) {
|
|
1281
|
+
const overlay = document.getElementById(kind === 'toolbar' ? 'graph-toolbar-overlay' : 'graph-focus-overlay');
|
|
1282
|
+
const shell = document.querySelector('#panel-graph .graph-canvas-shell');
|
|
1283
|
+
if (!overlay || !shell || window.innerWidth <= 1180) return null;
|
|
1284
|
+
return { overlay, shell };
|
|
1285
|
+
}
|
|
1286
|
+
function getGraphOverlayStorageKey(kind) {
|
|
1287
|
+
return kind === 'toolbar' ? GRAPH_TOOLBAR_POSITION_STORAGE_KEY : GRAPH_FOCUS_POSITION_STORAGE_KEY;
|
|
1288
|
+
}
|
|
1289
|
+
function getGraphOverlayStatePosition(kind) {
|
|
1290
|
+
return kind === 'toolbar' ? graphState.toolbarPosition : graphState.focusPosition;
|
|
1291
|
+
}
|
|
1292
|
+
function setGraphOverlayStatePosition(kind, position) {
|
|
1293
|
+
if (kind === 'toolbar') graphState.toolbarPosition = position;
|
|
1294
|
+
else graphState.focusPosition = position;
|
|
1295
|
+
}
|
|
1296
|
+
function clampGraphOverlayPosition(position, overlay, shell) {
|
|
1297
|
+
const margin = 14;
|
|
1298
|
+
const maxX = Math.max(margin, shell.clientWidth - overlay.offsetWidth - margin);
|
|
1299
|
+
const maxY = Math.max(margin, shell.clientHeight - overlay.offsetHeight - margin);
|
|
1300
|
+
return {
|
|
1301
|
+
x: Math.round(Math.min(Math.max(margin, position.x), maxX)),
|
|
1302
|
+
y: Math.round(Math.min(Math.max(margin, position.y), maxY)),
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function applyGraphOverlayPosition(kind, position, overlay) {
|
|
1306
|
+
if (!position) {
|
|
1307
|
+
overlay.style.left = '';
|
|
1308
|
+
overlay.style.top = '';
|
|
1309
|
+
overlay.style.right = '';
|
|
1310
|
+
overlay.style.bottom = '';
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
overlay.style.left = `${position.x}px`;
|
|
1314
|
+
overlay.style.top = `${position.y}px`;
|
|
1315
|
+
overlay.style.right = 'auto';
|
|
1316
|
+
overlay.style.bottom = 'auto';
|
|
1317
|
+
}
|
|
1318
|
+
function syncGraphOverlayPosition(kind) {
|
|
1319
|
+
const elements = getGraphOverlayElements(kind);
|
|
1320
|
+
const overlay = document.getElementById(kind === 'toolbar' ? 'graph-toolbar-overlay' : 'graph-focus-overlay');
|
|
1321
|
+
if (!overlay) return;
|
|
1322
|
+
if (!elements) {
|
|
1323
|
+
applyGraphOverlayPosition(kind, null, overlay);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
if (kind === 'toolbar' && graphState.toolbarMinimized) {
|
|
1327
|
+
applyGraphOverlayPosition(kind, null, elements.overlay);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
const position = getGraphOverlayStatePosition(kind);
|
|
1331
|
+
if (!position) {
|
|
1332
|
+
applyGraphOverlayPosition(kind, null, elements.overlay);
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
const next = clampGraphOverlayPosition(position, elements.overlay, elements.shell);
|
|
1336
|
+
setGraphOverlayStatePosition(kind, next);
|
|
1337
|
+
applyGraphOverlayPosition(kind, next, elements.overlay);
|
|
1338
|
+
}
|
|
1339
|
+
function syncGraphToolbarPosition() {
|
|
1340
|
+
syncGraphOverlayPosition('toolbar');
|
|
1341
|
+
}
|
|
1342
|
+
function syncGraphFocusPosition() {
|
|
1343
|
+
syncGraphOverlayPosition('focus');
|
|
1344
|
+
}
|
|
1345
|
+
function syncGraphOverlayPositions() {
|
|
1346
|
+
syncGraphToolbarPosition();
|
|
1347
|
+
syncGraphFocusPosition();
|
|
1348
|
+
}
|
|
1349
|
+
function dockGraphOverlay(kind) {
|
|
1350
|
+
setGraphOverlayStatePosition(kind, null);
|
|
1351
|
+
saveGraphOverlayPosition(getGraphOverlayStorageKey(kind), null);
|
|
1352
|
+
syncGraphOverlayPosition(kind);
|
|
1353
|
+
if (document.getElementById('panel-graph').classList.contains('active')) {
|
|
1354
|
+
graphState.layoutKey = null;
|
|
1355
|
+
drawGraph();
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function startGraphOverlayDrag(kind, event) {
|
|
1359
|
+
if (event.button !== 0) return;
|
|
1360
|
+
const elements = getGraphOverlayElements(kind);
|
|
1361
|
+
if (!elements) return;
|
|
1362
|
+
const overlayRect = elements.overlay.getBoundingClientRect();
|
|
1363
|
+
const shellRect = elements.shell.getBoundingClientRect();
|
|
1364
|
+
graphOverlayDragState.active = true;
|
|
1365
|
+
graphOverlayDragState.kind = kind;
|
|
1366
|
+
graphOverlayDragState.moved = false;
|
|
1367
|
+
graphOverlayDragState.startX = event.clientX;
|
|
1368
|
+
graphOverlayDragState.startY = event.clientY;
|
|
1369
|
+
graphOverlayDragState.originX = overlayRect.left - shellRect.left;
|
|
1370
|
+
graphOverlayDragState.originY = overlayRect.top - shellRect.top;
|
|
1371
|
+
if (kind === 'focus') graphState.suppressFocusPeekClick = false;
|
|
1372
|
+
if (kind === 'toolbar') graphState.suppressToolbarPeekClick = false;
|
|
1373
|
+
event.preventDefault();
|
|
1374
|
+
}
|
|
1375
|
+
function dragGraphOverlay(event) {
|
|
1376
|
+
if (!graphOverlayDragState.active) return;
|
|
1377
|
+
const elements = getGraphOverlayElements(graphOverlayDragState.kind);
|
|
1378
|
+
if (!elements) return;
|
|
1379
|
+
const dx = event.clientX - graphOverlayDragState.startX;
|
|
1380
|
+
const dy = event.clientY - graphOverlayDragState.startY;
|
|
1381
|
+
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) graphOverlayDragState.moved = true;
|
|
1382
|
+
const next = clampGraphOverlayPosition(
|
|
1383
|
+
{ x: graphOverlayDragState.originX + dx, y: graphOverlayDragState.originY + dy },
|
|
1384
|
+
elements.overlay,
|
|
1385
|
+
elements.shell,
|
|
1386
|
+
);
|
|
1387
|
+
setGraphOverlayStatePosition(graphOverlayDragState.kind, next);
|
|
1388
|
+
applyGraphOverlayPosition(graphOverlayDragState.kind, next, elements.overlay);
|
|
1389
|
+
event.preventDefault();
|
|
1390
|
+
}
|
|
1391
|
+
function stopGraphOverlayDrag() {
|
|
1392
|
+
if (!graphOverlayDragState.active) return;
|
|
1393
|
+
const kind = graphOverlayDragState.kind;
|
|
1394
|
+
graphOverlayDragState.active = false;
|
|
1395
|
+
if (kind === 'focus') graphState.suppressFocusPeekClick = graphOverlayDragState.moved;
|
|
1396
|
+
if (kind === 'toolbar') graphState.suppressToolbarPeekClick = graphOverlayDragState.moved;
|
|
1397
|
+
saveGraphOverlayPosition(getGraphOverlayStorageKey(kind), getGraphOverlayStatePosition(kind));
|
|
1398
|
+
if (document.getElementById('panel-graph').classList.contains('active')) {
|
|
1399
|
+
graphState.layoutKey = null;
|
|
1400
|
+
drawGraph();
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
function syncGraphToolbar() {
|
|
1404
|
+
const overlay = document.getElementById('graph-toolbar-overlay');
|
|
1405
|
+
const icon = document.getElementById('graph-toolbar-toggle-icon');
|
|
1406
|
+
const toggle = document.getElementById('graph-toolbar-toggle');
|
|
1407
|
+
if (!overlay) return;
|
|
1408
|
+
overlay.classList.toggle('minimized', !!graphState.toolbarMinimized);
|
|
1409
|
+
overlay.classList.toggle('compact', !graphState.toolbarExpanded && !graphState.toolbarMinimized);
|
|
1410
|
+
overlay.classList.toggle('expanded', !!graphState.toolbarExpanded && !graphState.toolbarMinimized);
|
|
1411
|
+
if (icon) icon.innerHTML = graphState.toolbarExpanded ? '▴' : '▾';
|
|
1412
|
+
if (toggle) {
|
|
1413
|
+
toggle.setAttribute('aria-label', graphState.toolbarExpanded ? 'Collapse graph filters' : 'Expand graph filters');
|
|
1414
|
+
toggle.title = graphState.toolbarExpanded ? 'Collapse graph filters' : 'Expand graph filters';
|
|
1415
|
+
}
|
|
1416
|
+
requestAnimationFrame(syncGraphToolbarPosition);
|
|
1417
|
+
}
|
|
1418
|
+
function toggleGraphToolbar() {
|
|
1419
|
+
toggleGraphToolbarMinimized(true);
|
|
1420
|
+
}
|
|
1421
|
+
function toggleGraphToolbarMinimized(forceState) {
|
|
1422
|
+
graphState.toolbarMinimized = typeof forceState === 'boolean' ? forceState : !graphState.toolbarMinimized;
|
|
1423
|
+
if (graphState.toolbarMinimized) graphState.toolbarExpanded = false;
|
|
1424
|
+
syncGraphToolbar();
|
|
1425
|
+
if (document.getElementById('panel-graph').classList.contains('active')) drawGraph();
|
|
1426
|
+
}
|
|
1427
|
+
function openGraphToolbarPeek(event) {
|
|
1428
|
+
if (graphState.suppressToolbarPeekClick) {
|
|
1429
|
+
graphState.suppressToolbarPeekClick = false;
|
|
1430
|
+
if (event) event.preventDefault();
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
toggleGraphToolbarMinimized(false);
|
|
1434
|
+
}
|
|
1435
|
+
function getWikiAskModeLabel(mode) {
|
|
1436
|
+
return ({ 'current-focus': 'Current Focus', 'visible-group': 'Visible Group', 'selected-docs': 'Selected Docs' }[mode]) || 'Ask SDTK-WIKI';
|
|
1437
|
+
}
|
|
1438
|
+
function getWikiAskVisibleDocIds() {
|
|
1439
|
+
return getVisibleDocs().map(doc => doc.id);
|
|
1440
|
+
}
|
|
1441
|
+
function readWikiAskPosition() {
|
|
1442
|
+
try {
|
|
1443
|
+
const raw = localStorage.getItem(WIKI_ASK_POSITION_STORAGE_KEY);
|
|
1444
|
+
if (!raw) return null;
|
|
1445
|
+
const parsed = JSON.parse(raw);
|
|
1446
|
+
if (!parsed || !Number.isFinite(parsed.x) || !Number.isFinite(parsed.y)) return null;
|
|
1447
|
+
return { x: parsed.x, y: parsed.y };
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
function saveWikiAskPosition() {
|
|
1453
|
+
if (!wikiAskState.position) return;
|
|
1454
|
+
try { localStorage.setItem(WIKI_ASK_POSITION_STORAGE_KEY, JSON.stringify(wikiAskState.position)); } catch (error) {}
|
|
1455
|
+
}
|
|
1456
|
+
function getWikiAskDockElements() {
|
|
1457
|
+
const panel = document.getElementById('panel-graph');
|
|
1458
|
+
const dock = document.getElementById('atlas-ask-dock');
|
|
1459
|
+
const shell = document.querySelector('#panel-graph .graph-canvas-shell');
|
|
1460
|
+
if (!panel || !panel.classList.contains('active') || !dock || !shell || window.innerWidth <= 1180 || !shell.clientWidth || !shell.clientHeight) return null;
|
|
1461
|
+
return { dock, shell };
|
|
1462
|
+
}
|
|
1463
|
+
function getDefaultWikiAskPosition(dock, shell) {
|
|
1464
|
+
const margin = 14;
|
|
1465
|
+
return {
|
|
1466
|
+
x: margin,
|
|
1467
|
+
y: Math.max(margin, shell.clientHeight - dock.offsetHeight - margin),
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function clampWikiAskPosition(position, dock, shell) {
|
|
1471
|
+
const margin = 14;
|
|
1472
|
+
const maxX = Math.max(margin, shell.clientWidth - dock.offsetWidth - margin);
|
|
1473
|
+
const maxY = Math.max(margin, shell.clientHeight - dock.offsetHeight - margin);
|
|
1474
|
+
return {
|
|
1475
|
+
x: Math.round(Math.min(Math.max(margin, position.x), maxX)),
|
|
1476
|
+
y: Math.round(Math.min(Math.max(margin, position.y), maxY)),
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function applyWikiAskPosition(position, dock) {
|
|
1480
|
+
dock.style.left = `${position.x}px`;
|
|
1481
|
+
dock.style.top = `${position.y}px`;
|
|
1482
|
+
dock.style.bottom = 'auto';
|
|
1483
|
+
}
|
|
1484
|
+
function syncWikiAskPosition() {
|
|
1485
|
+
const elements = getWikiAskDockElements();
|
|
1486
|
+
const dock = document.getElementById('atlas-ask-dock');
|
|
1487
|
+
if (!dock) return;
|
|
1488
|
+
if (!elements || !wikiAskState.open) {
|
|
1489
|
+
dock.style.left = '';
|
|
1490
|
+
dock.style.top = '';
|
|
1491
|
+
dock.style.bottom = '';
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
const seed = wikiAskState.position || readWikiAskPosition() || getDefaultWikiAskPosition(elements.dock, elements.shell);
|
|
1495
|
+
const next = clampWikiAskPosition(seed, elements.dock, elements.shell);
|
|
1496
|
+
wikiAskState.position = next;
|
|
1497
|
+
applyWikiAskPosition(next, elements.dock);
|
|
1498
|
+
}
|
|
1499
|
+
function startWikiAskDrag(event) {
|
|
1500
|
+
if (event.button !== 0) return;
|
|
1501
|
+
const elements = getWikiAskDockElements();
|
|
1502
|
+
if (!elements) return;
|
|
1503
|
+
syncWikiAskPosition();
|
|
1504
|
+
wikiAskDragState.active = true;
|
|
1505
|
+
wikiAskDragState.moved = false;
|
|
1506
|
+
wikiAskDragState.startX = event.clientX;
|
|
1507
|
+
wikiAskDragState.startY = event.clientY;
|
|
1508
|
+
wikiAskDragState.originX = wikiAskState.position ? wikiAskState.position.x : 14;
|
|
1509
|
+
wikiAskDragState.originY = wikiAskState.position ? wikiAskState.position.y : 14;
|
|
1510
|
+
event.preventDefault();
|
|
1511
|
+
}
|
|
1512
|
+
function dragWikiAsk(event) {
|
|
1513
|
+
if (!wikiAskDragState.active) return;
|
|
1514
|
+
const elements = getWikiAskDockElements();
|
|
1515
|
+
if (!elements) return;
|
|
1516
|
+
const dx = event.clientX - wikiAskDragState.startX;
|
|
1517
|
+
const dy = event.clientY - wikiAskDragState.startY;
|
|
1518
|
+
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) wikiAskDragState.moved = true;
|
|
1519
|
+
wikiAskState.position = clampWikiAskPosition(
|
|
1520
|
+
{ x: wikiAskDragState.originX + dx, y: wikiAskDragState.originY + dy },
|
|
1521
|
+
elements.dock,
|
|
1522
|
+
elements.shell,
|
|
1523
|
+
);
|
|
1524
|
+
applyWikiAskPosition(wikiAskState.position, elements.dock);
|
|
1525
|
+
event.preventDefault();
|
|
1526
|
+
}
|
|
1527
|
+
function stopWikiAskDrag() {
|
|
1528
|
+
if (!wikiAskDragState.active) return;
|
|
1529
|
+
wikiAskDragState.active = false;
|
|
1530
|
+
wikiAskState.suppressLaunchClick = wikiAskDragState.moved;
|
|
1531
|
+
if (wikiAskState.position) saveWikiAskPosition();
|
|
1532
|
+
}
|
|
1533
|
+
async function ensureWikiAskHealth() {
|
|
1534
|
+
if (wikiAskState.healthLoaded) return;
|
|
1535
|
+
try {
|
|
1536
|
+
const response = await fetch('/api/health');
|
|
1537
|
+
const payload = await response.json();
|
|
1538
|
+
wikiAskState.health = payload;
|
|
1539
|
+
syncWikiAskRuntimeSelection(wikiAskState.runtimeAgent || payload.runtime_agent || '', wikiAskState.model || payload.model || '');
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
wikiAskState.health = { ok: false, error: error && error.message ? error.message : String(error) };
|
|
1542
|
+
syncWikiAskRuntimeSelection(wikiAskState.runtimeAgent || 'claude', wikiAskState.model || '');
|
|
1543
|
+
}
|
|
1544
|
+
wikiAskState.healthLoaded = true;
|
|
1545
|
+
renderWikiAsk();
|
|
1546
|
+
}
|
|
1547
|
+
async function ensureWikiAskHistory(forceReload = false) {
|
|
1548
|
+
if (wikiAskState.historyLoaded && !forceReload) return;
|
|
1549
|
+
try {
|
|
1550
|
+
const response = await fetch('/api/atlas-ask-history');
|
|
1551
|
+
const payload = await response.json();
|
|
1552
|
+
wikiAskState.history = Array.isArray(payload.items) ? payload.items : [];
|
|
1553
|
+
wikiAskState.historyError = payload.ok === false ? (payload.error || 'Atlas ask history is unavailable.') : '';
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
wikiAskState.history = [];
|
|
1556
|
+
wikiAskState.historyError = error && error.message ? error.message : String(error);
|
|
1557
|
+
}
|
|
1558
|
+
wikiAskState.historyLoaded = true;
|
|
1559
|
+
renderWikiAsk();
|
|
1560
|
+
}
|
|
1561
|
+
function prependWikiAskHistory(entry) {
|
|
1562
|
+
if (!entry || !entry.id) return;
|
|
1563
|
+
wikiAskState.history = [entry].concat((wikiAskState.history || []).filter(item => item && item.id !== entry.id)).slice(0, 12);
|
|
1564
|
+
wikiAskState.historyLoaded = true;
|
|
1565
|
+
}
|
|
1566
|
+
function loadWikiAskHistoryItem(id) {
|
|
1567
|
+
const item = (wikiAskState.history || []).find(entry => entry && entry.id === id);
|
|
1568
|
+
if (!item) return;
|
|
1569
|
+
wikiAskState.question = '';
|
|
1570
|
+
wikiAskState.pendingQuestion = '';
|
|
1571
|
+
wikiAskState.lastQuestion = item.question || '';
|
|
1572
|
+
wikiAskState.mode = item.mode || 'visible-group';
|
|
1573
|
+
wikiAskState.selectedDocIds = Array.isArray(item.selected_doc_ids) ? [...item.selected_doc_ids] : [];
|
|
1574
|
+
wikiAskState.answer = {
|
|
1575
|
+
question: item.question || '',
|
|
1576
|
+
answer_markdown: item.answer_markdown || '',
|
|
1577
|
+
citations: Array.isArray(item.citations) ? item.citations : [],
|
|
1578
|
+
confidence: item.confidence || 'low',
|
|
1579
|
+
disclaimer: item.disclaimer || '',
|
|
1580
|
+
context: {
|
|
1581
|
+
source_count: Array.isArray(item.source_paths) ? item.source_paths.length : 0,
|
|
1582
|
+
source_paths: Array.isArray(item.source_paths) ? item.source_paths : [],
|
|
1583
|
+
truncated: { truncated: false },
|
|
1584
|
+
},
|
|
1585
|
+
};
|
|
1586
|
+
wikiAskState.turns = [{
|
|
1587
|
+
question: item.question || '',
|
|
1588
|
+
answer_markdown: item.answer_markdown || '',
|
|
1589
|
+
citations: Array.isArray(item.citations) ? item.citations : [],
|
|
1590
|
+
confidence: item.confidence || 'low',
|
|
1591
|
+
disclaimer: item.disclaimer || '',
|
|
1592
|
+
context: wikiAskState.answer.context,
|
|
1593
|
+
}];
|
|
1594
|
+
wikiAskState.error = '';
|
|
1595
|
+
wikiAskState.status = 'idle';
|
|
1596
|
+
wikiAskState.open = true;
|
|
1597
|
+
ensureWikiAskHealth();
|
|
1598
|
+
renderWikiAsk();
|
|
1599
|
+
}
|
|
1600
|
+
function renderWikiAskHistoryHtml() {
|
|
1601
|
+
if (wikiAskState.historyError) {
|
|
1602
|
+
return `<div class="atlas-ask-answer-card"><h3>Recent Asks</h3><p class="muted">History is unavailable: ${escapeHtml(wikiAskState.historyError)}</p></div>`;
|
|
1603
|
+
}
|
|
1604
|
+
if (!wikiAskState.historyLoaded) {
|
|
1605
|
+
return '<div class="atlas-ask-answer-card"><h3>Recent Asks</h3><p class="muted">Loading local SDTK-WIKI Ask history...</p></div>';
|
|
1606
|
+
}
|
|
1607
|
+
if (!wikiAskState.history.length) {
|
|
1608
|
+
return '<div class="atlas-ask-answer-card"><h3>Recent Asks</h3><p class="muted">No saved asks yet. Completed asks will appear here after the first grounded response is written locally.</p></div>';
|
|
1609
|
+
}
|
|
1610
|
+
const items = wikiAskState.history.slice(0, 8).map(item => {
|
|
1611
|
+
const metaBits = [item.timestamp || '', getWikiAskModeLabel(item.mode || 'current-focus')].filter(Boolean).join(' | ');
|
|
1612
|
+
const preview = escapeHtml(item.answer_preview || item.answer_markdown || 'Saved grounded answer.');
|
|
1613
|
+
return `<button class="atlas-ask-history-item" onclick="loadWikiAskHistoryItem('${item.id}')"><strong>${escapeHtml(item.question || 'Saved ask')}</strong><span class="atlas-ask-history-meta">${escapeHtml(metaBits)}</span><span class="atlas-ask-history-preview">${preview}</span></button>`;
|
|
1614
|
+
}).join('');
|
|
1615
|
+
return `<div class="atlas-ask-answer-card"><h3>Recent Asks</h3><div class="atlas-ask-history-list">${items}</div></div>`;
|
|
1616
|
+
}
|
|
1617
|
+
function toggleWikiAsk(forceState) {
|
|
1618
|
+
wikiAskState.open = typeof forceState === 'boolean' ? forceState : !wikiAskState.open;
|
|
1619
|
+
if (!wikiAskState.open) wikiAskState.advancedOpen = false;
|
|
1620
|
+
renderWikiAsk();
|
|
1621
|
+
if (wikiAskState.open) ensureWikiAskHealth();
|
|
1622
|
+
}
|
|
1623
|
+
function toggleWikiAskAdvanced(forceState) {
|
|
1624
|
+
wikiAskState.advancedOpen = typeof forceState === 'boolean' ? forceState : !wikiAskState.advancedOpen;
|
|
1625
|
+
renderWikiAsk();
|
|
1626
|
+
}
|
|
1627
|
+
function getWikiAskRuntimeOptions() {
|
|
1628
|
+
if (wikiAskState.health && Array.isArray(wikiAskState.health.runtime_options) && wikiAskState.health.runtime_options.length) {
|
|
1629
|
+
return wikiAskState.health.runtime_options.map(option => ({
|
|
1630
|
+
id: option.id,
|
|
1631
|
+
label: option.label || option.id,
|
|
1632
|
+
models: Array.isArray(option.models) ? option.models : [],
|
|
1633
|
+
}));
|
|
1634
|
+
}
|
|
1635
|
+
return WIKI_ASK_RUNTIME_OPTIONS;
|
|
1636
|
+
}
|
|
1637
|
+
function getWikiAskRuntimeLabel(runtimeAgent) {
|
|
1638
|
+
const option = getWikiAskRuntimeOptions().find(item => item.id === runtimeAgent);
|
|
1639
|
+
return option ? option.label : String(runtimeAgent || '').toUpperCase();
|
|
1640
|
+
}
|
|
1641
|
+
function getWikiAskModelOptions(runtimeAgent) {
|
|
1642
|
+
const option = getWikiAskRuntimeOptions().find(item => item.id === runtimeAgent);
|
|
1643
|
+
if (option && Array.isArray(option.models) && option.models.length) return option.models;
|
|
1644
|
+
const fallback = WIKI_ASK_RUNTIME_OPTIONS.find(item => item.id === runtimeAgent) || WIKI_ASK_RUNTIME_OPTIONS[0];
|
|
1645
|
+
return fallback ? fallback.models : [];
|
|
1646
|
+
}
|
|
1647
|
+
function getWikiAskModelLabel(model) {
|
|
1648
|
+
return WIKI_ASK_MODEL_LABELS[model] || model || 'Default';
|
|
1649
|
+
}
|
|
1650
|
+
function syncWikiAskRuntimeSelection(preferredAgent, preferredModel) {
|
|
1651
|
+
const options = getWikiAskRuntimeOptions();
|
|
1652
|
+
const fallbackAgent = options.length ? options[0].id : 'claude';
|
|
1653
|
+
const nextAgent = options.some(option => option.id === preferredAgent) ? preferredAgent : fallbackAgent;
|
|
1654
|
+
const modelOptions = getWikiAskModelOptions(nextAgent);
|
|
1655
|
+
const fallbackModel = modelOptions.length ? modelOptions[0] : '';
|
|
1656
|
+
wikiAskState.runtimeAgent = nextAgent;
|
|
1657
|
+
wikiAskState.model = modelOptions.includes(preferredModel) ? preferredModel : fallbackModel;
|
|
1658
|
+
}
|
|
1659
|
+
function setWikiAskRuntimeAgent(runtimeAgent) {
|
|
1660
|
+
syncWikiAskRuntimeSelection(runtimeAgent, '');
|
|
1661
|
+
renderWikiAsk();
|
|
1662
|
+
}
|
|
1663
|
+
function setWikiAskModel(model) {
|
|
1664
|
+
const allowed = getWikiAskModelOptions(wikiAskState.runtimeAgent);
|
|
1665
|
+
wikiAskState.model = allowed.includes(model) ? model : (allowed[0] || '');
|
|
1666
|
+
renderWikiAsk();
|
|
1667
|
+
}
|
|
1668
|
+
function setWikiAskMode(mode) {
|
|
1669
|
+
wikiAskState.mode = mode;
|
|
1670
|
+
renderWikiAsk();
|
|
1671
|
+
}
|
|
1672
|
+
function addWikiAskSource(id) {
|
|
1673
|
+
if (!id || !docById[id]) return;
|
|
1674
|
+
if (!wikiAskState.selectedDocIds.includes(id)) wikiAskState.selectedDocIds.push(id);
|
|
1675
|
+
wikiAskState.open = true;
|
|
1676
|
+
wikiAskState.error = '';
|
|
1677
|
+
renderWikiAsk();
|
|
1678
|
+
}
|
|
1679
|
+
function removeWikiAskSource(id) {
|
|
1680
|
+
wikiAskState.selectedDocIds = wikiAskState.selectedDocIds.filter(value => value !== id);
|
|
1681
|
+
renderWikiAsk();
|
|
1682
|
+
}
|
|
1683
|
+
function addCurrentFocusToWikiAsk() {
|
|
1684
|
+
const focusId = getGraphFocusId();
|
|
1685
|
+
if (!focusId) {
|
|
1686
|
+
wikiAskState.error = 'Select or hover a graph document first, then add it as an ask source.';
|
|
1687
|
+
wikiAskState.open = true;
|
|
1688
|
+
renderWikiAsk();
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
addWikiAskSource(focusId);
|
|
1692
|
+
}
|
|
1693
|
+
function clearWikiAskSources() {
|
|
1694
|
+
wikiAskState.selectedDocIds = [];
|
|
1695
|
+
wikiAskState.error = '';
|
|
1696
|
+
renderWikiAsk();
|
|
1697
|
+
}
|
|
1698
|
+
function getWikiAskSourceSummaryHtml() {
|
|
1699
|
+
const visibleCount = getWikiAskVisibleDocIds().length;
|
|
1700
|
+
const selectedCount = wikiAskState.selectedDocIds.length;
|
|
1701
|
+
const focusId = getGraphFocusId();
|
|
1702
|
+
const focusLabel = focusId ? ((docById[focusId] && (docById[focusId].title || focusId)) || focusId) : 'None';
|
|
1703
|
+
if (wikiAskState.mode === 'current-focus') {
|
|
1704
|
+
if (focusId) {
|
|
1705
|
+
return `<strong>Grounding:</strong> Current focus source. <strong>Focus:</strong> ${escapeHtml(focusLabel)}. <strong>Visible group:</strong> ${visibleCount} docs.`;
|
|
1706
|
+
}
|
|
1707
|
+
return `<strong>Grounding:</strong> Current focus source. Select a graph note to anchor this ask. <strong>Visible group:</strong> ${visibleCount} docs.`;
|
|
1708
|
+
}
|
|
1709
|
+
if (wikiAskState.mode === 'visible-group') {
|
|
1710
|
+
return `<strong>Grounding:</strong> Visible graph group. <strong>Visible docs:</strong> ${visibleCount}.${selectedCount ? ` <strong>Explicit docs:</strong> ${selectedCount}.` : ''}`;
|
|
1711
|
+
}
|
|
1712
|
+
return `<strong>Grounding:</strong> Explicit source pack. <strong>Selected docs:</strong> ${selectedCount}.${selectedCount ? '' : ' Use graph search results or @mention docs before asking.'}`;
|
|
1713
|
+
}
|
|
1714
|
+
function getWikiAskAdvancedSummaryText() {
|
|
1715
|
+
const bits = [];
|
|
1716
|
+
if (wikiAskState.runtimeAgent) bits.push(`${getWikiAskRuntimeLabel(wikiAskState.runtimeAgent)} / ${getWikiAskModelLabel(wikiAskState.model)}`);
|
|
1717
|
+
bits.push(`${getWikiAskVisibleDocIds().length} visible docs`);
|
|
1718
|
+
if (wikiAskState.answer && wikiAskState.answer.context && typeof wikiAskState.answer.context.source_count === 'number') bits.push(`${wikiAskState.answer.context.source_count} sources used`);
|
|
1719
|
+
if (wikiAskState.answer && wikiAskState.answer.context && wikiAskState.answer.context.truncated && wikiAskState.answer.context.truncated.truncated) bits.push('truncated source pack');
|
|
1720
|
+
if (wikiAskState.error) bits.push('last ask returned an error');
|
|
1721
|
+
return bits.join(' | ') || 'Runtime and grounding details appear here.';
|
|
1722
|
+
}
|
|
1723
|
+
function getWikiAskMentionState() {
|
|
1724
|
+
const input = document.getElementById('atlas-ask-input');
|
|
1725
|
+
if (!input) return null;
|
|
1726
|
+
const value = input.value || '';
|
|
1727
|
+
const caret = typeof input.selectionStart === 'number' ? input.selectionStart : value.length;
|
|
1728
|
+
const before = value.slice(0, caret);
|
|
1729
|
+
const atIndex = before.lastIndexOf('@');
|
|
1730
|
+
if (atIndex < 0) return null;
|
|
1731
|
+
const token = before.slice(atIndex + 1);
|
|
1732
|
+
if (token.includes(' ') || token.includes('\n') || token.includes('\t')) return null;
|
|
1733
|
+
if (atIndex > 0) {
|
|
1734
|
+
const prefix = before[atIndex - 1];
|
|
1735
|
+
if (prefix && !/\s|[([{-]/.test(prefix)) return null;
|
|
1736
|
+
}
|
|
1737
|
+
const query = token.trim().toLowerCase();
|
|
1738
|
+
const docs = INDEX.documents.filter(doc => {
|
|
1739
|
+
const parts = [doc.title || '', doc.id || '']
|
|
1740
|
+
.concat(doc.issues || [])
|
|
1741
|
+
.concat(doc.knowledge_ids || []);
|
|
1742
|
+
if (!query) return true;
|
|
1743
|
+
return parts.some(value => String(value).toLowerCase().includes(query));
|
|
1744
|
+
}).sort((a, b) => (a.title || a.id).localeCompare(b.title || b.id)).slice(0, 8);
|
|
1745
|
+
return { start: atIndex, end: caret, query, docs };
|
|
1746
|
+
}
|
|
1747
|
+
function closeWikiAskMentions() {
|
|
1748
|
+
wikiAskState.mention = null;
|
|
1749
|
+
wikiAskState.mentionIndex = -1;
|
|
1750
|
+
}
|
|
1751
|
+
function updateWikiAskMentions() {
|
|
1752
|
+
const mention = getWikiAskMentionState();
|
|
1753
|
+
if (!mention || !mention.docs.length) {
|
|
1754
|
+
closeWikiAskMentions();
|
|
1755
|
+
renderWikiAsk();
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
wikiAskState.mention = mention;
|
|
1759
|
+
if (wikiAskState.mentionIndex < 0 || wikiAskState.mentionIndex >= mention.docs.length) wikiAskState.mentionIndex = 0;
|
|
1760
|
+
renderWikiAsk();
|
|
1761
|
+
}
|
|
1762
|
+
function selectWikiAskMention(docId) {
|
|
1763
|
+
const mention = wikiAskState.mention;
|
|
1764
|
+
const input = document.getElementById('atlas-ask-input');
|
|
1765
|
+
const doc = docById[docId];
|
|
1766
|
+
if (!mention || !input || !doc) return;
|
|
1767
|
+
const value = input.value || '';
|
|
1768
|
+
const nextValue = `${value.slice(0, mention.start)}${value.slice(mention.end)}`.replace(/ {2,}/g, ' ');
|
|
1769
|
+
input.value = nextValue;
|
|
1770
|
+
const nextCaret = mention.start;
|
|
1771
|
+
input.focus();
|
|
1772
|
+
input.setSelectionRange(nextCaret, nextCaret);
|
|
1773
|
+
wikiAskState.question = input.value;
|
|
1774
|
+
closeWikiAskMentions();
|
|
1775
|
+
addWikiAskSource(docId);
|
|
1776
|
+
}
|
|
1777
|
+
function moveWikiAskMentionSelection(direction) {
|
|
1778
|
+
if (!wikiAskState.mention || !wikiAskState.mention.docs.length) return;
|
|
1779
|
+
const max = wikiAskState.mention.docs.length - 1;
|
|
1780
|
+
wikiAskState.mentionIndex = wikiAskState.mentionIndex < 0 ? 0 : (wikiAskState.mentionIndex + direction + wikiAskState.mention.docs.length) % wikiAskState.mention.docs.length;
|
|
1781
|
+
if (wikiAskState.mentionIndex > max) wikiAskState.mentionIndex = max;
|
|
1782
|
+
renderWikiAsk();
|
|
1783
|
+
}
|
|
1784
|
+
function commitWikiAskMentionSelection() {
|
|
1785
|
+
if (!wikiAskState.mention || !wikiAskState.mention.docs.length) return false;
|
|
1786
|
+
const index = wikiAskState.mentionIndex >= 0 ? wikiAskState.mentionIndex : 0;
|
|
1787
|
+
const doc = wikiAskState.mention.docs[index];
|
|
1788
|
+
if (!doc) return false;
|
|
1789
|
+
selectWikiAskMention(doc.id);
|
|
1790
|
+
return true;
|
|
1791
|
+
}
|
|
1792
|
+
function renderWikiAskMentions() {
|
|
1793
|
+
const root = document.getElementById('atlas-ask-mention-results');
|
|
1794
|
+
if (!root) return;
|
|
1795
|
+
const mention = wikiAskState.mention;
|
|
1796
|
+
if (!mention || !mention.docs.length) {
|
|
1797
|
+
root.classList.remove('open');
|
|
1798
|
+
root.innerHTML = '';
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
root.classList.add('open');
|
|
1802
|
+
root.innerHTML = mention.docs.map((doc, index) => {
|
|
1803
|
+
const active = index === wikiAskState.mentionIndex;
|
|
1804
|
+
const meta = [doc.id, doc.family].filter(Boolean).join(' | ');
|
|
1805
|
+
return `<div class="atlas-ask-mention-item${active ? ' active' : ''}" onclick="selectWikiAskMention('${doc.id}')"><div class="atlas-ask-mention-copy"><strong>${escapeHtml(doc.title || doc.id)}</strong><span>${escapeHtml(meta)}</span></div><span class="atlas-ask-mention-tag">Doc</span></div>`;
|
|
1806
|
+
}).join('');
|
|
1807
|
+
}
|
|
1808
|
+
function getWikiAskAdvancedHtml() {
|
|
1809
|
+
const visibleCount = getWikiAskVisibleDocIds().length;
|
|
1810
|
+
const focusId = getGraphFocusId();
|
|
1811
|
+
const focusLabelRaw = focusId ? ((docById[focusId] && (docById[focusId].title || focusId)) || focusId) : 'None';
|
|
1812
|
+
const focusLabel = focusLabelRaw.length > 72 ? `${focusLabelRaw.slice(0, 69)}...` : focusLabelRaw;
|
|
1813
|
+
const answerContext = wikiAskState.answer && wikiAskState.answer.context ? wikiAskState.answer.context : null;
|
|
1814
|
+
const sourceCount = answerContext && typeof answerContext.source_count === 'number' ? answerContext.source_count : 0;
|
|
1815
|
+
const items = [
|
|
1816
|
+
['Mode', getWikiAskModeLabel(wikiAskState.mode)],
|
|
1817
|
+
['Runtime', getWikiAskRuntimeLabel(wikiAskState.runtimeAgent)],
|
|
1818
|
+
['Model', getWikiAskModelLabel(wikiAskState.model)],
|
|
1819
|
+
['Sources', `${visibleCount} visible / ${wikiAskState.selectedDocIds.length} selected`],
|
|
1820
|
+
['Focus', focusLabel],
|
|
1821
|
+
];
|
|
1822
|
+
if (sourceCount) items.push(['Used', `${sourceCount} docs`]);
|
|
1823
|
+
if (wikiAskState.answer && wikiAskState.answer.confidence) items.push(['Confidence', wikiAskState.answer.confidence]);
|
|
1824
|
+
return items.map(([label, value]) => `<div class="atlas-ask-meta-item"><div class="atlas-ask-meta-label">${escapeHtml(label)}</div><div class="atlas-ask-meta-value">${escapeHtml(value)}</div></div>`).join('');
|
|
1825
|
+
}
|
|
1826
|
+
function renderWikiAsk() {
|
|
1827
|
+
const dock = document.getElementById('atlas-ask-dock');
|
|
1828
|
+
const overlay = document.getElementById('atlas-ask-overlay');
|
|
1829
|
+
if (dock) dock.classList.toggle('open', !!wikiAskState.open);
|
|
1830
|
+
if (!overlay) return;
|
|
1831
|
+
overlay.classList.toggle('open', !!wikiAskState.open);
|
|
1832
|
+
syncWikiAskPosition();
|
|
1833
|
+
if (!wikiAskState.open) return;
|
|
1834
|
+
syncWikiAskRuntimeSelection(
|
|
1835
|
+
wikiAskState.runtimeAgent || (wikiAskState.health && wikiAskState.health.runtime_agent) || 'claude',
|
|
1836
|
+
wikiAskState.model || (wikiAskState.health && wikiAskState.health.model) || ''
|
|
1837
|
+
);
|
|
1838
|
+
const runtimeSelect = document.getElementById('atlas-ask-runtime-select');
|
|
1839
|
+
const modelSelect = document.getElementById('atlas-ask-model-select');
|
|
1840
|
+
const modeSelect = document.getElementById('atlas-ask-mode-select');
|
|
1841
|
+
const input = document.getElementById('atlas-ask-input');
|
|
1842
|
+
const attachments = document.getElementById('atlas-ask-attachments');
|
|
1843
|
+
const sourceSummary = document.getElementById('atlas-ask-source-summary');
|
|
1844
|
+
const mentionResults = document.getElementById('atlas-ask-mention-results');
|
|
1845
|
+
const answer = document.getElementById('atlas-ask-answer');
|
|
1846
|
+
const status = document.getElementById('atlas-ask-status');
|
|
1847
|
+
const submit = document.getElementById('atlas-ask-submit');
|
|
1848
|
+
const infoPopover = document.getElementById('atlas-ask-info-popover');
|
|
1849
|
+
const runtimeOptions = getWikiAskRuntimeOptions();
|
|
1850
|
+
const modelOptions = getWikiAskModelOptions(wikiAskState.runtimeAgent);
|
|
1851
|
+
if (input && input.value !== wikiAskState.question) input.value = wikiAskState.question;
|
|
1852
|
+
if (runtimeSelect) {
|
|
1853
|
+
runtimeSelect.innerHTML = runtimeOptions.map(option => `<option value="${option.id}">${escapeHtml(option.label)}</option>`).join('');
|
|
1854
|
+
if (runtimeSelect.value !== wikiAskState.runtimeAgent) runtimeSelect.value = wikiAskState.runtimeAgent;
|
|
1855
|
+
}
|
|
1856
|
+
if (modelSelect) {
|
|
1857
|
+
modelSelect.innerHTML = modelOptions.map(model => `<option value="${model}">${escapeHtml(getWikiAskModelLabel(model))}</option>`).join('');
|
|
1858
|
+
if (modelSelect.value !== wikiAskState.model) modelSelect.value = wikiAskState.model;
|
|
1859
|
+
}
|
|
1860
|
+
if (modeSelect && modeSelect.value !== wikiAskState.mode) modeSelect.value = wikiAskState.mode;
|
|
1861
|
+
if (sourceSummary) sourceSummary.innerHTML = getWikiAskSourceSummaryHtml();
|
|
1862
|
+
if (mentionResults) renderWikiAskMentions();
|
|
1863
|
+
if (attachments) {
|
|
1864
|
+
if (!wikiAskState.selectedDocIds.length) attachments.innerHTML = '';
|
|
1865
|
+
else attachments.innerHTML = wikiAskState.selectedDocIds.map(id => {
|
|
1866
|
+
const doc = docById[id];
|
|
1867
|
+
const title = doc ? (doc.title || id) : id;
|
|
1868
|
+
const meta = doc ? (doc.family || '') : '';
|
|
1869
|
+
return `<div class="atlas-ask-attachment-chip" title="${escapeHtml(id)}"><span class="atlas-ask-attachment-icon">📄</span><div class="atlas-ask-attachment-copy"><strong>${escapeHtml(title)}</strong><span>${escapeHtml(meta || id)}</span></div><button class="atlas-ask-attachment-remove" onclick="removeWikiAskSource('${id}')" aria-label="Remove source">×</button></div>`;
|
|
1870
|
+
}).join('');
|
|
1871
|
+
}
|
|
1872
|
+
if (infoPopover) {
|
|
1873
|
+
if (!wikiAskState.advancedOpen) {
|
|
1874
|
+
infoPopover.classList.remove('open');
|
|
1875
|
+
infoPopover.innerHTML = '';
|
|
1876
|
+
} else {
|
|
1877
|
+
infoPopover.classList.add('open');
|
|
1878
|
+
infoPopover.innerHTML = `<div class="atlas-ask-info-card"><div class="atlas-ask-info-head"><div class="atlas-ask-info-title">Grounding Info</div><button class="atlas-ask-info-close" onclick="toggleWikiAskAdvanced(false)" aria-label="Close grounding info" title="Close grounding info">×</button></div><div class="atlas-ask-advanced-summary">${escapeHtml(getWikiAskAdvancedSummaryText())}</div><div class="atlas-ask-advanced-body">${getWikiAskAdvancedHtml()}</div></div>`;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (status) {
|
|
1882
|
+
status.classList.remove('error', 'busy');
|
|
1883
|
+
if (wikiAskState.status === 'asking') {
|
|
1884
|
+
status.innerHTML = renderWikiAskTypingHtml('Typing');
|
|
1885
|
+
status.classList.add('busy');
|
|
1886
|
+
} else if (wikiAskState.error) {
|
|
1887
|
+
status.textContent = wikiAskState.error;
|
|
1888
|
+
status.classList.add('error');
|
|
1889
|
+
} else if (wikiAskState.health && wikiAskState.health.ok === false) {
|
|
1890
|
+
status.textContent = wikiAskState.health.error || 'SDTK-WIKI ask server is unavailable.';
|
|
1891
|
+
status.classList.add('error');
|
|
1892
|
+
} else {
|
|
1893
|
+
status.textContent = 'Enter sends. Shift+Enter adds a newline. Answers stay grounded to the atlas source pack you selected.';
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
if (submit) submit.disabled = wikiAskState.status === 'asking' || !wikiAskState.question.trim();
|
|
1897
|
+
if (!answer) return;
|
|
1898
|
+
|
|
1899
|
+
const turns = Array.isArray(wikiAskState.turns) ? wikiAskState.turns : [];
|
|
1900
|
+
const blocks = [];
|
|
1901
|
+
turns.forEach(turn => {
|
|
1902
|
+
const citationHtml = turn.citations && turn.citations.length
|
|
1903
|
+
? `<ul>${turn.citations.map(item => `<li><button class="wiki-link" onclick="focusGraphNode('${item.path}')">${escapeHtml(item.title || item.path)}</button> · ${escapeHtml(item.reason || 'Grounded source')}</li>`).join('')}</ul>`
|
|
1904
|
+
: '<p class="muted">No citations were returned.</p>';
|
|
1905
|
+
blocks.push(`<div class="atlas-ask-message user"><div class="atlas-ask-message-label">You</div><div class="atlas-ask-message-bubble">${escapeHtml(turn.question || '')}</div></div>`);
|
|
1906
|
+
blocks.push(`<div class="atlas-ask-message assistant"><div class="atlas-ask-message-label">SDTK-WIKI Ask</div><div class="atlas-ask-answer-card"><div class="graph-note-prose">${renderMarkdownFragment(turn.answer_markdown || '')}</div>${turn.disclaimer ? `<span class="muted">${escapeHtml(turn.disclaimer)}</span>` : ''}</div><div class="atlas-ask-answer-card"><h3>Citations</h3>${citationHtml}</div></div>`);
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
if (wikiAskState.pendingQuestion) {
|
|
1910
|
+
blocks.push(`<div class="atlas-ask-message user"><div class="atlas-ask-message-label">You</div><div class="atlas-ask-message-bubble">${escapeHtml(wikiAskState.pendingQuestion)}</div></div>`);
|
|
1911
|
+
blocks.push(`<div class="atlas-ask-message assistant"><div class="atlas-ask-message-label">SDTK-WIKI Ask</div><div class="atlas-ask-answer-card busy">${renderWikiAskTypingHtml('Typing')}</div></div>`);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
if (!blocks.length) {
|
|
1915
|
+
answer.innerHTML = '<div class="atlas-ask-empty">Ask SDTK-WIKI from the visible graph group by default, or narrow the source pack with a selected node or attached docs.</div>';
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
answer.innerHTML = `<div class="atlas-ask-thread">${blocks.join('')}</div>`;
|
|
1920
|
+
scheduleMermaidRender(answer);
|
|
1921
|
+
requestAnimationFrame(syncWikiAskPosition);
|
|
1922
|
+
}
|
|
1923
|
+
async function submitWikiAsk() {
|
|
1924
|
+
const input = document.getElementById('atlas-ask-input');
|
|
1925
|
+
const draftQuestion = input ? input.value.trim() : wikiAskState.question.trim();
|
|
1926
|
+
wikiAskState.question = draftQuestion;
|
|
1927
|
+
if (!draftQuestion) {
|
|
1928
|
+
wikiAskState.error = 'Question is required.';
|
|
1929
|
+
wikiAskState.status = 'error';
|
|
1930
|
+
renderWikiAsk();
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
const payload = {
|
|
1934
|
+
question: draftQuestion,
|
|
1935
|
+
mode: wikiAskState.mode,
|
|
1936
|
+
runtime_agent: wikiAskState.runtimeAgent,
|
|
1937
|
+
model: wikiAskState.model,
|
|
1938
|
+
focus_doc_id: getGraphFocusId() || '',
|
|
1939
|
+
visible_doc_ids: getWikiAskVisibleDocIds(),
|
|
1940
|
+
selected_doc_ids: [...wikiAskState.selectedDocIds],
|
|
1941
|
+
};
|
|
1942
|
+
wikiAskState.pendingQuestion = draftQuestion;
|
|
1943
|
+
wikiAskState.question = '';
|
|
1944
|
+
if (input) input.value = '';
|
|
1945
|
+
wikiAskState.status = 'asking';
|
|
1946
|
+
wikiAskState.error = '';
|
|
1947
|
+
renderWikiAsk();
|
|
1948
|
+
try {
|
|
1949
|
+
const response = await fetch('/api/atlas-ask', {
|
|
1950
|
+
method: 'POST',
|
|
1951
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1952
|
+
body: JSON.stringify(payload),
|
|
1953
|
+
});
|
|
1954
|
+
const result = await response.json();
|
|
1955
|
+
if (!response.ok || !result.ok) throw new Error(result.error || 'SDTK-WIKI ask failed.');
|
|
1956
|
+
wikiAskState.answer = result;
|
|
1957
|
+
wikiAskState.status = 'answered';
|
|
1958
|
+
wikiAskState.lastQuestion = draftQuestion;
|
|
1959
|
+
wikiAskState.pendingQuestion = '';
|
|
1960
|
+
wikiAskState.runtimeAgent = result.runtime_agent || wikiAskState.runtimeAgent;
|
|
1961
|
+
wikiAskState.model = result.model || wikiAskState.model;
|
|
1962
|
+
wikiAskState.turns = (wikiAskState.turns || []).concat([{
|
|
1963
|
+
question: draftQuestion,
|
|
1964
|
+
answer_markdown: result.answer_markdown || '',
|
|
1965
|
+
citations: Array.isArray(result.citations) ? result.citations : [],
|
|
1966
|
+
confidence: result.confidence || 'medium',
|
|
1967
|
+
disclaimer: result.disclaimer || '',
|
|
1968
|
+
context: result.context || {},
|
|
1969
|
+
}]).slice(-20);
|
|
1970
|
+
if (result.history_entry) prependWikiAskHistory(result.history_entry);
|
|
1971
|
+
} catch (error) {
|
|
1972
|
+
wikiAskState.answer = null;
|
|
1973
|
+
wikiAskState.status = 'error';
|
|
1974
|
+
wikiAskState.error = error && error.message ? error.message : String(error);
|
|
1975
|
+
wikiAskState.pendingQuestion = '';
|
|
1976
|
+
wikiAskState.question = draftQuestion;
|
|
1977
|
+
if (input) {
|
|
1978
|
+
input.value = draftQuestion;
|
|
1979
|
+
input.focus();
|
|
1980
|
+
const end = draftQuestion.length;
|
|
1981
|
+
input.setSelectionRange(end, end);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
renderWikiAsk();
|
|
1985
|
+
}
|
|
1986
|
+
function toggleGraphFocusMinimized(forceState) {
|
|
1987
|
+
graphState.overlayMinimized = typeof forceState === 'boolean' ? forceState : !graphState.overlayMinimized;
|
|
1988
|
+
if (!graphState.overlayMinimized) {
|
|
1989
|
+
graphState.overlayCollapsed = false;
|
|
1990
|
+
syncGraphOverlayMode();
|
|
1991
|
+
renderGraphDetail();
|
|
1992
|
+
drawGraph();
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
syncGraphOverlayMode();
|
|
1996
|
+
renderGraphDetail();
|
|
1997
|
+
graphState.layoutKey = null;
|
|
1998
|
+
graphState.fitVisibleRequested = true;
|
|
1999
|
+
drawGraph();
|
|
2000
|
+
}
|
|
2001
|
+
function openGraphFocusPeek(event) {
|
|
2002
|
+
if (graphState.suppressFocusPeekClick) {
|
|
2003
|
+
graphState.suppressFocusPeekClick = false;
|
|
2004
|
+
if (event) event.preventDefault();
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
toggleGraphFocusMinimized(false);
|
|
2008
|
+
}
|
|
2009
|
+
function toggleGraphFocusCollapsed(forceState) {
|
|
2010
|
+
graphState.overlayMinimized = false;
|
|
2011
|
+
graphState.overlayCollapsed = typeof forceState === 'boolean' ? forceState : !graphState.overlayCollapsed;
|
|
2012
|
+
syncGraphOverlayMode();
|
|
2013
|
+
renderGraphDetail();
|
|
2014
|
+
drawGraph();
|
|
2015
|
+
}
|
|
2016
|
+
function renderGraphScope() {
|
|
2017
|
+
const row = document.getElementById('graph-scope-row');
|
|
2018
|
+
if (!row) return;
|
|
2019
|
+
const rowWrap = row.closest('.graph-footer-row-controls');
|
|
2020
|
+
if (!graphState.scopeFilter) {
|
|
2021
|
+
row.classList.remove('active');
|
|
2022
|
+
if (rowWrap) rowWrap.classList.remove('active');
|
|
2023
|
+
row.innerHTML = '';
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
const scope = graphState.scopeFilter;
|
|
2027
|
+
row.classList.add('active');
|
|
2028
|
+
if (rowWrap) rowWrap.classList.add('active');
|
|
2029
|
+
row.innerHTML = `<span class="graph-scope-pill">${escapeHtml(getScopeLabel(scope.kind))}: ${escapeHtml(scope.value)} · ${scope.count} docs</span><button class="graph-scope-clear" onclick="clearGraphScope()">Clear Scope</button>`;
|
|
2030
|
+
}
|
|
2031
|
+
function activateGraphScope(kind, value) {
|
|
2032
|
+
const ids = getScopeDocIds(kind, value);
|
|
2033
|
+
if (!ids.length) return;
|
|
2034
|
+
graphState.scopeFilter = { kind, value, count: ids.length, docIds: new Set(ids) };
|
|
2035
|
+
graphState.familyFilter = null;
|
|
2036
|
+
graphState.manualViewport = false;
|
|
2037
|
+
graphState.layoutKey = null;
|
|
2038
|
+
graphState.fitVisibleRequested = true;
|
|
2039
|
+
closeGraphSearchResults();
|
|
2040
|
+
renderGraphFamilyFilters();
|
|
2041
|
+
renderGraphScope();
|
|
2042
|
+
if (wikiAskState.open) renderWikiAsk();
|
|
2043
|
+
renderGraphSearchResults(document.getElementById('graph-search').value);
|
|
2044
|
+
if (!document.getElementById('graph-search').value.trim()) closeGraphSearchResults();
|
|
2045
|
+
renderGraphDetail();
|
|
2046
|
+
drawGraph();
|
|
2047
|
+
}
|
|
2048
|
+
function clearGraphScope(redraw = true) {
|
|
2049
|
+
graphState.scopeFilter = null;
|
|
2050
|
+
graphState.manualViewport = false;
|
|
2051
|
+
graphState.layoutKey = null;
|
|
2052
|
+
renderGraphScope();
|
|
2053
|
+
if (wikiAskState.open) renderWikiAsk();
|
|
2054
|
+
if (redraw) {
|
|
2055
|
+
renderGraphSearchResults(document.getElementById('graph-search').value);
|
|
2056
|
+
if (!document.getElementById('graph-search').value.trim()) closeGraphSearchResults();
|
|
2057
|
+
renderGraphDetail();
|
|
2058
|
+
drawGraph();
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
function resetGraphViewport() {
|
|
2062
|
+
graphState.zoom = 1;
|
|
2063
|
+
graphState.targetZoom = 1;
|
|
2064
|
+
graphState.panX = 0;
|
|
2065
|
+
graphState.panY = 0;
|
|
2066
|
+
graphState.targetPanX = 0;
|
|
2067
|
+
graphState.targetPanY = 0;
|
|
2068
|
+
graphState.autoFocus = false;
|
|
2069
|
+
graphState.fitVisibleRequested = false;
|
|
2070
|
+
graphState.manualViewport = false;
|
|
2071
|
+
}
|
|
2072
|
+
function closeGraphSearchResults(clearInput = false) {
|
|
2073
|
+
const results = document.getElementById('graph-search-results');
|
|
2074
|
+
graphState.searchResults = [];
|
|
2075
|
+
graphState.searchResultIndex = -1;
|
|
2076
|
+
if (results) {
|
|
2077
|
+
results.classList.remove('open');
|
|
2078
|
+
results.innerHTML = '';
|
|
2079
|
+
}
|
|
2080
|
+
if (clearInput) {
|
|
2081
|
+
const input = document.getElementById('graph-search');
|
|
2082
|
+
if (input) input.value = '';
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function setGraphSelection(id, options = {}) {
|
|
2086
|
+
if (!id || !docById[id]) return;
|
|
2087
|
+
const opts = Object.assign({ pushHistory: true, clearSearch: true, detailMode: 'summary', animate: true }, options);
|
|
2088
|
+
const currentlyVisible = getVisibleDocIds();
|
|
2089
|
+
if (!currentlyVisible.has(id)) {
|
|
2090
|
+
graphState.familyFilter = null;
|
|
2091
|
+
graphState.scopeFilter = null;
|
|
2092
|
+
graphState.layoutKey = null;
|
|
2093
|
+
renderGraphFamilyFilters();
|
|
2094
|
+
renderGraphScope();
|
|
2095
|
+
}
|
|
2096
|
+
graphState.selectedId = id;
|
|
2097
|
+
graphState.hoveredId = null;
|
|
2098
|
+
graphState.detailMode = opts.detailMode;
|
|
2099
|
+
graphState.autoFocus = opts.animate;
|
|
2100
|
+
graphState.fitVisibleRequested = false;
|
|
2101
|
+
if (opts.animate) {
|
|
2102
|
+
graphState.manualViewport = false;
|
|
2103
|
+
graphState.targetZoom = Math.max(graphState.targetZoom, 1.18);
|
|
2104
|
+
}
|
|
2105
|
+
if (opts.pushHistory) pushGraphHistory(id);
|
|
2106
|
+
if (opts.clearSearch) closeGraphSearchResults();
|
|
2107
|
+
renderGraphDetail();
|
|
2108
|
+
if (wikiAskState.open) renderWikiAsk();
|
|
2109
|
+
drawGraph();
|
|
2110
|
+
}
|
|
2111
|
+
function centerGraphOnSelection() {
|
|
2112
|
+
if (!graphState.selectedId) return;
|
|
2113
|
+
graphState.autoFocus = true;
|
|
2114
|
+
graphState.fitVisibleRequested = false;
|
|
2115
|
+
graphState.manualViewport = false;
|
|
2116
|
+
graphState.targetZoom = Math.max(graphState.targetZoom, 1.18);
|
|
2117
|
+
renderGraphDetail();
|
|
2118
|
+
drawGraph();
|
|
2119
|
+
}
|
|
2120
|
+
function fitGraphGroup() {
|
|
2121
|
+
fitVisibleGraph();
|
|
2122
|
+
}
|
|
2123
|
+
function clearGraphSelection(clearSearch = false) {
|
|
2124
|
+
graphState.overlayMinimized = false;
|
|
2125
|
+
graphState.overlayCollapsed = false;
|
|
2126
|
+
graphState.selectedId = null;
|
|
2127
|
+
graphState.hoveredId = null;
|
|
2128
|
+
graphState.detailMode = 'summary';
|
|
2129
|
+
graphState.autoFocus = false;
|
|
2130
|
+
if (clearSearch) closeGraphSearchResults(true); else closeGraphSearchResults();
|
|
2131
|
+
renderGraphDetail();
|
|
2132
|
+
if (wikiAskState.open) renderWikiAsk();
|
|
2133
|
+
drawGraph();
|
|
2134
|
+
}
|
|
2135
|
+
function navigateGraphHistory(direction) {
|
|
2136
|
+
const nextIndex = graphState.historyIndex + direction;
|
|
2137
|
+
if (nextIndex < 0 || nextIndex >= graphState.history.length) return;
|
|
2138
|
+
graphState.historyIndex = nextIndex;
|
|
2139
|
+
setGraphSelection(graphState.history[nextIndex], { pushHistory: false, clearSearch: false, detailMode: 'summary', animate: true });
|
|
2140
|
+
}
|
|
2141
|
+
function toggleGraphOverlayMode() {
|
|
2142
|
+
graphState.overlayMinimized = false;
|
|
2143
|
+
graphState.overlayCollapsed = false;
|
|
2144
|
+
graphState.overlayMode = graphState.overlayMode === 'compact' ? 'expanded' : 'compact';
|
|
2145
|
+
syncGraphOverlayMode();
|
|
2146
|
+
graphState.manualViewport = false;
|
|
2147
|
+
graphState.fitVisibleRequested = true;
|
|
2148
|
+
renderGraphDetail();
|
|
2149
|
+
drawGraph();
|
|
2150
|
+
}
|
|
2151
|
+
function focusGraphNode(id) {
|
|
2152
|
+
graphState.overlayMinimized = false;
|
|
2153
|
+
graphState.overlayCollapsed = false;
|
|
2154
|
+
setGraphSelection(id, { pushHistory: true, clearSearch: true, detailMode: 'summary', animate: true });
|
|
2155
|
+
}
|
|
2156
|
+
function lockGraphNode(id) {
|
|
2157
|
+
graphState.overlayMinimized = false;
|
|
2158
|
+
graphState.overlayCollapsed = false;
|
|
2159
|
+
setGraphSelection(id, { pushHistory: true, clearSearch: true, detailMode: 'summary', animate: false });
|
|
2160
|
+
}
|
|
2161
|
+
function openGraphFullDetail(id) {
|
|
2162
|
+
if (id && docById[id]) {
|
|
2163
|
+
graphState.selectedId = id;
|
|
2164
|
+
graphState.hoveredId = null;
|
|
2165
|
+
pushGraphHistory(id);
|
|
2166
|
+
}
|
|
2167
|
+
if (!graphState.selectedId) return;
|
|
2168
|
+
graphState.detailMode = 'full';
|
|
2169
|
+
closeGraphSearchResults();
|
|
2170
|
+
renderGraphDetail();
|
|
2171
|
+
drawGraph();
|
|
2172
|
+
}
|
|
2173
|
+
function showGraphSummary() {
|
|
2174
|
+
graphState.detailMode = 'summary';
|
|
2175
|
+
renderGraphDetail();
|
|
2176
|
+
}
|
|
2177
|
+
function fitVisibleGraph() {
|
|
2178
|
+
graphState.autoFocus = false;
|
|
2179
|
+
graphState.manualViewport = false;
|
|
2180
|
+
graphState.fitVisibleRequested = true;
|
|
2181
|
+
renderGraphDetail();
|
|
2182
|
+
drawGraph();
|
|
2183
|
+
}
|
|
2184
|
+
function setManualViewport() {
|
|
2185
|
+
graphState.autoFocus = false;
|
|
2186
|
+
graphState.fitVisibleRequested = false;
|
|
2187
|
+
graphState.manualViewport = true;
|
|
2188
|
+
graphState.targetPanX = graphState.panX;
|
|
2189
|
+
graphState.targetPanY = graphState.panY;
|
|
2190
|
+
graphState.targetZoom = graphState.zoom;
|
|
2191
|
+
}
|
|
2192
|
+
function applyCameraStep() {
|
|
2193
|
+
graphState.zoom += (graphState.targetZoom - graphState.zoom) * 0.16;
|
|
2194
|
+
graphState.panX += (graphState.targetPanX - graphState.panX) * 0.18;
|
|
2195
|
+
graphState.panY += (graphState.targetPanY - graphState.panY) * 0.18;
|
|
2196
|
+
updateZoomBadge();
|
|
2197
|
+
}
|
|
2198
|
+
function getGraphViewportMetrics(width, height) {
|
|
2199
|
+
const overlay = document.getElementById('graph-focus-overlay');
|
|
2200
|
+
const toolbar = document.getElementById('graph-toolbar-overlay');
|
|
2201
|
+
const overlayWidth = window.innerWidth > 1180 && overlay && !overlay.classList.contains('hidden') && !overlay.classList.contains('minimized') && !graphState.focusPosition ? overlay.getBoundingClientRect().width : 0;
|
|
2202
|
+
const toolbarHeight = toolbar && !graphState.toolbarPosition && !toolbar.classList.contains('minimized') ? toolbar.getBoundingClientRect().height : 0;
|
|
2203
|
+
const safeGap = overlayWidth ? 30 : 0;
|
|
2204
|
+
const usableWidth = Math.max(260, width - overlayWidth - safeGap - 46);
|
|
2205
|
+
const topInset = toolbarHeight ? Math.min(height * 0.34, toolbarHeight + 28) : 0;
|
|
2206
|
+
const usableHeight = Math.max(220, height - topInset - 46);
|
|
2207
|
+
const centerX = 26 + usableWidth / 2;
|
|
2208
|
+
const centerY = topInset + usableHeight / 2;
|
|
2209
|
+
return { overlayWidth, safeGap, usableWidth, usableHeight, centerX, centerY, topInset };
|
|
2210
|
+
}
|
|
2211
|
+
function computePositionBounds(visibleDocs) {
|
|
2212
|
+
if (!visibleDocs.length || !graphState.positions) return null;
|
|
2213
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
2214
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
2215
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
2216
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
2217
|
+
visibleDocs.forEach(doc => {
|
|
2218
|
+
const pos = graphState.positions[doc.id];
|
|
2219
|
+
if (!pos) return;
|
|
2220
|
+
minX = Math.min(minX, pos.x);
|
|
2221
|
+
maxX = Math.max(maxX, pos.x);
|
|
2222
|
+
minY = Math.min(minY, pos.y);
|
|
2223
|
+
maxY = Math.max(maxY, pos.y);
|
|
2224
|
+
});
|
|
2225
|
+
if (!Number.isFinite(minX)) return null;
|
|
2226
|
+
return {
|
|
2227
|
+
minX,
|
|
2228
|
+
maxX,
|
|
2229
|
+
minY,
|
|
2230
|
+
maxY,
|
|
2231
|
+
centerX: (minX + maxX) / 2,
|
|
2232
|
+
centerY: (minY + maxY) / 2,
|
|
2233
|
+
width: Math.max(1, maxX - minX),
|
|
2234
|
+
height: Math.max(1, maxY - minY),
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
function computePanForTargetCenter(worldX, worldY, screenX, screenY, width, height, zoom) {
|
|
2238
|
+
return {
|
|
2239
|
+
panX: screenX - ((worldX - width / 2) * zoom + width / 2),
|
|
2240
|
+
panY: screenY - ((worldY - height / 2) * zoom + height / 2),
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
function worldToScreen(worldX, worldY, width, height) {
|
|
2244
|
+
return {
|
|
2245
|
+
x: (worldX - width / 2) * graphState.zoom + width / 2 + graphState.panX,
|
|
2246
|
+
y: (worldY - height / 2) * graphState.zoom + height / 2 + graphState.panY,
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
function getMotionSeed(id) {
|
|
2250
|
+
if (graphState.motionSeeds[id]) return graphState.motionSeeds[id];
|
|
2251
|
+
const hash = hashString(id);
|
|
2252
|
+
graphState.motionSeeds[id] = {
|
|
2253
|
+
phase: (hash % 360) * (Math.PI / 180),
|
|
2254
|
+
amplitudeX: 2.4 + (hash % 9) * 0.35,
|
|
2255
|
+
amplitudeY: 2.0 + ((Math.floor(hash / 9)) % 9) * 0.33,
|
|
2256
|
+
speed: 0.00075 + ((Math.floor(hash / 81)) % 7) * 0.00008,
|
|
2257
|
+
drift: 0.55 + ((Math.floor(hash / 567)) % 5) * 0.12,
|
|
2258
|
+
};
|
|
2259
|
+
return graphState.motionSeeds[id];
|
|
2260
|
+
}
|
|
2261
|
+
function getDisplayPosition(id, base) {
|
|
2262
|
+
const seed = getMotionSeed(id);
|
|
2263
|
+
const t = graphState.motionTime * seed.speed;
|
|
2264
|
+
return {
|
|
2265
|
+
x: base.x + Math.cos(seed.phase + t) * seed.amplitudeX + Math.sin(seed.phase * 0.7 + t * 0.9) * seed.drift,
|
|
2266
|
+
y: base.y + Math.sin(seed.phase + t * 0.95) * seed.amplitudeY + Math.cos(seed.phase * 0.5 + t * 0.82) * seed.drift,
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
(function initFilters() {
|
|
2271
|
+
const fr = document.getElementById('family-filters');
|
|
2272
|
+
const allBtn = document.createElement('button');
|
|
2273
|
+
allBtn.className = 'filter-btn active';
|
|
2274
|
+
allBtn.textContent = 'All';
|
|
2275
|
+
allBtn.onclick = () => {
|
|
2276
|
+
activeFamily = null;
|
|
2277
|
+
renderDocs();
|
|
2278
|
+
document.querySelectorAll('#family-filters .filter-btn').forEach(b => b.classList.remove('active'));
|
|
2279
|
+
allBtn.classList.add('active');
|
|
2280
|
+
};
|
|
2281
|
+
fr.appendChild(allBtn);
|
|
2282
|
+
famSet.forEach(fam => {
|
|
2283
|
+
const color = FAMILY_COLORS[fam] || '#8b949e';
|
|
2284
|
+
const btn = document.createElement('button');
|
|
2285
|
+
btn.className = 'filter-btn';
|
|
2286
|
+
btn.style.borderColor = color;
|
|
2287
|
+
btn.innerHTML = `<span class="dot" style="background:${color}"></span> ${fam}`;
|
|
2288
|
+
btn.onclick = () => {
|
|
2289
|
+
activeFamily = fam;
|
|
2290
|
+
renderDocs();
|
|
2291
|
+
document.querySelectorAll('#family-filters .filter-btn').forEach(b => b.classList.remove('active'));
|
|
2292
|
+
btn.classList.add('active');
|
|
2293
|
+
};
|
|
2294
|
+
fr.appendChild(btn);
|
|
2295
|
+
});
|
|
2296
|
+
})();
|
|
2297
|
+
|
|
2298
|
+
function renderDocs(query = '') {
|
|
2299
|
+
const list = document.getElementById('doc-list');
|
|
2300
|
+
list.innerHTML = '';
|
|
2301
|
+
const q = query.toLowerCase();
|
|
2302
|
+
const filtered = docs.filter(d => {
|
|
2303
|
+
if (activeFamily && d.family !== activeFamily) return false;
|
|
2304
|
+
if (q && !d.title.toLowerCase().includes(q) && !d.id.toLowerCase().includes(q)) return false;
|
|
2305
|
+
return true;
|
|
2306
|
+
});
|
|
2307
|
+
currentDocListIds = filtered.map(d => d.id);
|
|
2308
|
+
const groups = {};
|
|
2309
|
+
filtered.forEach(d => { (groups[d.family] = groups[d.family] || []).push(d); });
|
|
2310
|
+
Object.entries(groups).sort((a,b)=>a[0].localeCompare(b[0])).forEach(([fam, items]) => {
|
|
2311
|
+
const color = FAMILY_COLORS[fam] || '#8b949e';
|
|
2312
|
+
const hdr = document.createElement('div');
|
|
2313
|
+
hdr.className = 'cat-header';
|
|
2314
|
+
hdr.innerHTML = `<span class="dot" style="background:${color}"></span> ${fam} (${items.length})`;
|
|
2315
|
+
list.appendChild(hdr);
|
|
2316
|
+
items.forEach(d => {
|
|
2317
|
+
const el = document.createElement('div');
|
|
2318
|
+
el.className = 'doc-item';
|
|
2319
|
+
const issueStr = d.issues.length ? ` • ${d.issues.slice(0,3).join(', ')}${d.issues.length > 3 ? '...' : ''}` : '';
|
|
2320
|
+
el.innerHTML = `<div class="title">${d.title || d.id}</div><div class="meta"><code>${d.id}</code>${issueStr}</div>`;
|
|
2321
|
+
el.onclick = () => openDetail(d.id);
|
|
2322
|
+
list.appendChild(el);
|
|
2323
|
+
});
|
|
2324
|
+
});
|
|
2325
|
+
if (!filtered.length) {
|
|
2326
|
+
list.innerHTML = '<div style="color:var(--text2);padding:20px;text-align:center">No documents match filter.</div>';
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
renderDocs();
|
|
2330
|
+
document.getElementById('search').addEventListener('input', e => renderDocs(e.target.value));
|
|
2331
|
+
const wikiAskInput = document.getElementById('atlas-ask-input');
|
|
2332
|
+
const wikiAskLaunch = document.getElementById('atlas-ask-launch');
|
|
2333
|
+
const wikiAskDragBar = document.getElementById('atlas-ask-drag-bar');
|
|
2334
|
+
const graphToolbarDragBar = document.getElementById('graph-toolbar-drag-bar');
|
|
2335
|
+
const graphToolbarPeek = document.getElementById('graph-toolbar-peek');
|
|
2336
|
+
const graphFocusPanel = document.getElementById('graph-focus-panel');
|
|
2337
|
+
if (wikiAskLaunch) {
|
|
2338
|
+
wikiAskLaunch.addEventListener('mousedown', startWikiAskDrag);
|
|
2339
|
+
wikiAskLaunch.addEventListener('click', event => {
|
|
2340
|
+
if (wikiAskState.suppressLaunchClick) {
|
|
2341
|
+
wikiAskState.suppressLaunchClick = false;
|
|
2342
|
+
event.preventDefault();
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
toggleWikiAsk(true);
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2348
|
+
if (wikiAskDragBar) wikiAskDragBar.addEventListener('mousedown', startWikiAskDrag);
|
|
2349
|
+
if (graphToolbarDragBar) graphToolbarDragBar.addEventListener('mousedown', event => startGraphOverlayDrag('toolbar', event));
|
|
2350
|
+
if (graphToolbarPeek) graphToolbarPeek.addEventListener('mousedown', event => startGraphOverlayDrag('toolbar', event));
|
|
2351
|
+
if (graphFocusPanel) {
|
|
2352
|
+
graphFocusPanel.addEventListener('mousedown', event => {
|
|
2353
|
+
const dragTarget = event.target.closest('.graph-focus-drag-bar, .graph-focus-peek');
|
|
2354
|
+
if (!dragTarget) return;
|
|
2355
|
+
startGraphOverlayDrag('focus', event);
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
if (wikiAskInput) {
|
|
2359
|
+
wikiAskInput.addEventListener('input', event => {
|
|
2360
|
+
wikiAskState.question = event.target.value;
|
|
2361
|
+
updateWikiAskMentions();
|
|
2362
|
+
renderWikiAsk();
|
|
2363
|
+
});
|
|
2364
|
+
wikiAskInput.addEventListener('keydown', event => {
|
|
2365
|
+
if (wikiAskState.mention && wikiAskState.mention.docs.length) {
|
|
2366
|
+
if (event.key === 'ArrowDown') {
|
|
2367
|
+
event.preventDefault();
|
|
2368
|
+
moveWikiAskMentionSelection(1);
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
if (event.key === 'ArrowUp') {
|
|
2372
|
+
event.preventDefault();
|
|
2373
|
+
moveWikiAskMentionSelection(-1);
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
if (event.key === 'Enter') {
|
|
2377
|
+
event.preventDefault();
|
|
2378
|
+
commitWikiAskMentionSelection();
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
if (event.key === 'Escape') {
|
|
2382
|
+
event.preventDefault();
|
|
2383
|
+
closeWikiAskMentions();
|
|
2384
|
+
renderWikiAsk();
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
2389
|
+
event.preventDefault();
|
|
2390
|
+
submitWikiAsk();
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
wikiAskInput.addEventListener('blur', () => {
|
|
2394
|
+
setTimeout(() => {
|
|
2395
|
+
closeWikiAskMentions();
|
|
2396
|
+
renderWikiAsk();
|
|
2397
|
+
}, 120);
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
document.addEventListener('mousedown', event => {
|
|
2401
|
+
if (!wikiAskState.advancedOpen) return;
|
|
2402
|
+
const popover = document.getElementById('atlas-ask-info-popover');
|
|
2403
|
+
const toggle = document.getElementById('atlas-ask-info-toggle');
|
|
2404
|
+
if ((popover && popover.contains(event.target)) || (toggle && toggle.contains(event.target))) return;
|
|
2405
|
+
wikiAskState.advancedOpen = false;
|
|
2406
|
+
renderWikiAsk();
|
|
2407
|
+
});
|
|
2408
|
+
document.addEventListener('mousedown', event => {
|
|
2409
|
+
const familyMore = document.getElementById('graph-family-more-wrap');
|
|
2410
|
+
if (graphState.familyOverflowOpen && familyMore && !familyMore.contains(event.target)) {
|
|
2411
|
+
graphState.familyOverflowOpen = false;
|
|
2412
|
+
renderGraphFamilyFilters();
|
|
2413
|
+
}
|
|
2414
|
+
const settingsDock = document.getElementById('graph-settings-dock');
|
|
2415
|
+
if (graphState.settingsOpen && settingsDock && !settingsDock.contains(event.target)) {
|
|
2416
|
+
graphState.settingsOpen = false;
|
|
2417
|
+
syncGraphSettings();
|
|
2418
|
+
}
|
|
2419
|
+
});
|
|
2420
|
+
window.addEventListener('mousemove', dragWikiAsk);
|
|
2421
|
+
window.addEventListener('mouseup', stopWikiAskDrag);
|
|
2422
|
+
window.addEventListener('mousemove', dragGraphOverlay);
|
|
2423
|
+
window.addEventListener('mouseup', stopGraphOverlayDrag);
|
|
2424
|
+
window.addEventListener('resize', syncWikiAskPosition);
|
|
2425
|
+
window.addEventListener('resize', syncGraphOverlayPositions);
|
|
2426
|
+
function getDetailTraversalIds() {
|
|
2427
|
+
return currentDocListIds && currentDocListIds.length ? currentDocListIds.slice() : docs.map(doc => doc.id);
|
|
2428
|
+
}
|
|
2429
|
+
function syncDetailHeader(doc) {
|
|
2430
|
+
const color = FAMILY_COLORS[doc.family] || '#8b949e';
|
|
2431
|
+
const icon = document.getElementById('detail-icon');
|
|
2432
|
+
const kicker = document.getElementById('detail-kicker');
|
|
2433
|
+
const title = document.getElementById('detail-title');
|
|
2434
|
+
const subtitle = document.getElementById('detail-subtitle');
|
|
2435
|
+
if (icon) {
|
|
2436
|
+
icon.textContent = (doc.family || doc.title || doc.id || 'D').slice(0, 1).toUpperCase();
|
|
2437
|
+
icon.style.background = color;
|
|
2438
|
+
}
|
|
2439
|
+
if (kicker) kicker.textContent = doc.family || 'Document';
|
|
2440
|
+
if (title) title.textContent = doc.title || doc.id;
|
|
2441
|
+
if (subtitle) subtitle.textContent = `${doc.id} - trust: ${doc.trust_zone || 'unknown'}`;
|
|
2442
|
+
}
|
|
2443
|
+
function syncDetailNavButtons() {
|
|
2444
|
+
const ids = detailState.docIds && detailState.docIds.length ? detailState.docIds : getDetailTraversalIds();
|
|
2445
|
+
const index = ids.indexOf(detailState.currentId);
|
|
2446
|
+
const prev = document.getElementById('detail-prev');
|
|
2447
|
+
const next = document.getElementById('detail-next');
|
|
2448
|
+
if (prev) prev.disabled = index <= 0;
|
|
2449
|
+
if (next) next.disabled = index === -1 || index >= ids.length - 1;
|
|
2450
|
+
}
|
|
2451
|
+
function navigateDetail(direction) {
|
|
2452
|
+
const ids = detailState.docIds && detailState.docIds.length ? detailState.docIds : getDetailTraversalIds();
|
|
2453
|
+
const index = ids.indexOf(detailState.currentId);
|
|
2454
|
+
if (index === -1) return;
|
|
2455
|
+
const nextIndex = index + direction;
|
|
2456
|
+
if (nextIndex < 0 || nextIndex >= ids.length) return;
|
|
2457
|
+
openDetail(ids[nextIndex], { preserveList: true });
|
|
2458
|
+
}
|
|
2459
|
+
function openDetail(id, options = {}) {
|
|
2460
|
+
const doc = docById[id];
|
|
2461
|
+
if (!doc) return;
|
|
2462
|
+
if (!options.preserveList) detailState.docIds = getDetailTraversalIds();
|
|
2463
|
+
if (!detailState.docIds.includes(id)) detailState.docIds = [id].concat(detailState.docIds);
|
|
2464
|
+
detailState.currentId = id;
|
|
2465
|
+
const color = FAMILY_COLORS[doc.family] || '#8b949e';
|
|
2466
|
+
const outgoing = docEdges.filter(e => e.source === id);
|
|
2467
|
+
const incoming = docEdges.filter(e => e.target === id);
|
|
2468
|
+
syncDetailHeader(doc);
|
|
2469
|
+
syncDetailNavButtons();
|
|
2470
|
+
let html = `<h2>${escapeHtml(doc.title || doc.id)}</h2>`;
|
|
2471
|
+
html += `<p><span class="tag" style="background:${color}22;color:${color};border:1px solid ${color}">${escapeHtml(doc.family)}</span> <span style="font-size:11px;color:var(--text2)">${escapeHtml(doc.role)}</span> <span style="font-size:11px;color:var(--text2)">• trust: ${escapeHtml(doc.trust_zone)}</span></p>`;
|
|
2472
|
+
html += `<h3>File Path</h3><p><code>${escapeHtml(doc.id)}</code></p>`;
|
|
2473
|
+
if (doc.headings && doc.headings.length) {
|
|
2474
|
+
html += '<h3>Headings</h3><ul>';
|
|
2475
|
+
doc.headings.slice(0, 10).forEach(value => { html += `<li>${escapeHtml(value)}</li>`; });
|
|
2476
|
+
html += '</ul>';
|
|
2477
|
+
}
|
|
2478
|
+
if (doc.issues.length) {
|
|
2479
|
+
html += '<h3>Backlog Issues</h3><ul>';
|
|
2480
|
+
doc.issues.forEach(value => { html += `<li><code>${escapeHtml(value)}</code></li>`; });
|
|
2481
|
+
html += '</ul>';
|
|
2482
|
+
}
|
|
2483
|
+
if (doc.knowledge_ids.length) {
|
|
2484
|
+
html += '<h3>Knowledge Objects</h3><ul>';
|
|
2485
|
+
doc.knowledge_ids.forEach(value => { html += `<li><code>${escapeHtml(value)}</code></li>`; });
|
|
2486
|
+
html += '</ul>';
|
|
2487
|
+
}
|
|
2488
|
+
if (doc.skill_refs.length || doc.template_refs.length || doc.release_refs.length || doc.lane_refs.length) {
|
|
2489
|
+
html += '<h3>Typed References</h3><ul>';
|
|
2490
|
+
doc.skill_refs.forEach(value => { html += `<li>Skill: <code>${escapeHtml(value)}</code></li>`; });
|
|
2491
|
+
doc.template_refs.forEach(value => { html += `<li>Template: <code>${escapeHtml(value)}</code></li>`; });
|
|
2492
|
+
doc.release_refs.forEach(value => { html += `<li>Release: <code>${escapeHtml(value)}</code></li>`; });
|
|
2493
|
+
doc.lane_refs.forEach(value => { html += `<li>Lane: <code>${escapeHtml(value)}</code></li>`; });
|
|
2494
|
+
html += '</ul>';
|
|
2495
|
+
}
|
|
2496
|
+
if (outgoing.length) {
|
|
2497
|
+
html += `<h3>Outgoing Doc References (${outgoing.length})</h3><ul>`;
|
|
2498
|
+
outgoing.forEach(edge => {
|
|
2499
|
+
const target = docById[edge.target];
|
|
2500
|
+
if (target) html += `<li><code>${escapeHtml(edge.type)}</code> → <span class="wiki-link" onclick="openDetail('${edge.target}', { preserveList: true })">${escapeHtml(target.title || edge.target)}</span></li>`;
|
|
2501
|
+
});
|
|
2502
|
+
html += '</ul>';
|
|
2503
|
+
}
|
|
2504
|
+
if (incoming.length) {
|
|
2505
|
+
html += `<h3>Incoming Doc References (${incoming.length})</h3><ul>`;
|
|
2506
|
+
incoming.forEach(edge => {
|
|
2507
|
+
const source = docById[edge.source];
|
|
2508
|
+
if (source) html += `<li><code>${escapeHtml(edge.type)}</code> ← <span class="wiki-link" onclick="openDetail('${edge.source}', { preserveList: true })">${escapeHtml(source.title || edge.source)}</span></li>`;
|
|
2509
|
+
});
|
|
2510
|
+
html += '</ul>';
|
|
2511
|
+
}
|
|
2512
|
+
document.getElementById('detail-content').innerHTML = html;
|
|
2513
|
+
document.getElementById('detail-panel').classList.add('open');
|
|
2514
|
+
}
|
|
2515
|
+
function closeDetail() {
|
|
2516
|
+
detailState.currentId = null;
|
|
2517
|
+
document.getElementById('detail-panel').classList.remove('open');
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
function setGraphFamilyFilter(family) {
|
|
2521
|
+
graphState.familyFilter = family;
|
|
2522
|
+
graphState.footerFamilySelection = family;
|
|
2523
|
+
graphState.familyOverflowOpen = false;
|
|
2524
|
+
graphState.scopeFilter = null;
|
|
2525
|
+
graphState.manualViewport = false;
|
|
2526
|
+
const visibleIds = getVisibleDocIds();
|
|
2527
|
+
normalizeGraphFocus(visibleIds);
|
|
2528
|
+
graphState.layoutKey = null;
|
|
2529
|
+
graphState.fitVisibleRequested = !!family;
|
|
2530
|
+
renderGraphFamilyFilters();
|
|
2531
|
+
renderGraphScope();
|
|
2532
|
+
buildGraphLegend(getVisibleDocs());
|
|
2533
|
+
renderGraphSearchResults(document.getElementById('graph-search').value);
|
|
2534
|
+
if (!document.getElementById('graph-search').value.trim()) closeGraphSearchResults();
|
|
2535
|
+
if (wikiAskState.open) renderWikiAsk();
|
|
2536
|
+
renderGraphDetail();
|
|
2537
|
+
drawGraph();
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
function buildGraphLegend() {
|
|
2541
|
+
renderGraphFamilyFilters();
|
|
2542
|
+
renderGraphEdgeFilters();
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
function setGraphFooterFamilySelection(family) {
|
|
2546
|
+
setGraphFamilyFilter(family);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
function measureGraphFooterChipWidth(label, className = 'filter-btn legend-pill') {
|
|
2550
|
+
let measurer = document.getElementById('graph-footer-chip-measurer');
|
|
2551
|
+
if (!measurer) {
|
|
2552
|
+
measurer = document.createElement('div');
|
|
2553
|
+
measurer.id = 'graph-footer-chip-measurer';
|
|
2554
|
+
measurer.style.position = 'fixed';
|
|
2555
|
+
measurer.style.left = '-9999px';
|
|
2556
|
+
measurer.style.top = '-9999px';
|
|
2557
|
+
measurer.style.visibility = 'hidden';
|
|
2558
|
+
measurer.style.pointerEvents = 'none';
|
|
2559
|
+
document.body.appendChild(measurer);
|
|
2560
|
+
}
|
|
2561
|
+
const chip = document.createElement('button');
|
|
2562
|
+
chip.className = className;
|
|
2563
|
+
chip.textContent = label;
|
|
2564
|
+
measurer.appendChild(chip);
|
|
2565
|
+
const width = chip.offsetWidth;
|
|
2566
|
+
chip.remove();
|
|
2567
|
+
return width;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
function getOrderedGraphFamilies() {
|
|
2571
|
+
const orderedFamilies = famSet.slice();
|
|
2572
|
+
if (graphState.familyFilter && orderedFamilies.includes(graphState.familyFilter)) {
|
|
2573
|
+
orderedFamilies.splice(orderedFamilies.indexOf(graphState.familyFilter), 1);
|
|
2574
|
+
orderedFamilies.unshift(graphState.familyFilter);
|
|
2575
|
+
}
|
|
2576
|
+
return orderedFamilies;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
function getGraphFamilyVisibleFamilies() {
|
|
2580
|
+
const inlineTools = document.querySelector('#panel-graph .graph-footer-inline-tools');
|
|
2581
|
+
const shell = document.querySelector('#panel-graph .graph-canvas-shell');
|
|
2582
|
+
const orderedFamilies = getOrderedGraphFamilies();
|
|
2583
|
+
const availableWidth = inlineTools && inlineTools.clientWidth
|
|
2584
|
+
? inlineTools.clientWidth
|
|
2585
|
+
: Math.max(280, (shell && shell.clientWidth ? shell.clientWidth : window.innerWidth) - 64);
|
|
2586
|
+
const allWidth = measureGraphFooterChipWidth(`All (${docs.length})`);
|
|
2587
|
+
const familyWidths = orderedFamilies.map(family => measureGraphFooterChipWidth(`${family} (${familyCounts[family] || 0})`));
|
|
2588
|
+
const gap = 8;
|
|
2589
|
+
const widthBudget = Math.max(220, availableWidth);
|
|
2590
|
+
const totalWidth = allWidth + orderedFamilies.reduce((sum, family, index) => sum + gap + familyWidths[index], 0);
|
|
2591
|
+
if (totalWidth <= widthBudget) {
|
|
2592
|
+
return orderedFamilies;
|
|
2593
|
+
}
|
|
2594
|
+
const visible = [];
|
|
2595
|
+
let used = allWidth;
|
|
2596
|
+
for (let index = 0; index < orderedFamilies.length; index += 1) {
|
|
2597
|
+
const remaining = orderedFamilies.length - index - 1;
|
|
2598
|
+
const reserveMore = remaining > 0 ? gap + measureGraphFooterChipWidth(`More (${remaining})`) : 0;
|
|
2599
|
+
const nextWidth = gap + familyWidths[index];
|
|
2600
|
+
if (used + nextWidth + reserveMore > widthBudget) {
|
|
2601
|
+
break;
|
|
2602
|
+
}
|
|
2603
|
+
used += nextWidth;
|
|
2604
|
+
visible.push(orderedFamilies[index]);
|
|
2605
|
+
}
|
|
2606
|
+
return visible;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
function toggleGraphFamilyMore(forceState) {
|
|
2610
|
+
graphState.familyOverflowOpen = typeof forceState === 'boolean' ? forceState : !graphState.familyOverflowOpen;
|
|
2611
|
+
renderGraphFamilyFilters();
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
function makeGraphFamilyButton(label, family, count, className = 'filter-btn legend-pill') {
|
|
2615
|
+
const btn = document.createElement('button');
|
|
2616
|
+
const active = graphState.familyFilter === family || (!family && !graphState.familyFilter);
|
|
2617
|
+
btn.className = className + (active ? ' active' : '');
|
|
2618
|
+
btn.textContent = `${label} (${count})`;
|
|
2619
|
+
btn.onclick = () => setGraphFamilyFilter(family);
|
|
2620
|
+
return btn;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
function renderGraphFamilyFilters() {
|
|
2624
|
+
const root = document.getElementById('graph-family-filters');
|
|
2625
|
+
const moreRoot = document.getElementById('graph-family-more-wrap');
|
|
2626
|
+
if (!root) return;
|
|
2627
|
+
root.innerHTML = '';
|
|
2628
|
+
if (moreRoot) moreRoot.innerHTML = '';
|
|
2629
|
+
root.appendChild(makeGraphFamilyButton('All', null, docs.length));
|
|
2630
|
+
const visibleFamilies = getGraphFamilyVisibleFamilies();
|
|
2631
|
+
const visibleSet = new Set(visibleFamilies);
|
|
2632
|
+
const orderedFamilies = getOrderedGraphFamilies();
|
|
2633
|
+
visibleFamilies.forEach(family => root.appendChild(makeGraphFamilyButton(family, family, familyCounts[family] || 0)));
|
|
2634
|
+
const hiddenFamilies = orderedFamilies.filter(family => !visibleSet.has(family));
|
|
2635
|
+
if (moreRoot && hiddenFamilies.length) {
|
|
2636
|
+
const moreButton = document.createElement('button');
|
|
2637
|
+
moreButton.className = 'filter-btn legend-pill' + (graphState.familyOverflowOpen ? ' active' : '');
|
|
2638
|
+
moreButton.textContent = graphState.familyOverflowOpen ? 'Less' : `More (${hiddenFamilies.length})`;
|
|
2639
|
+
moreButton.onclick = (event) => {
|
|
2640
|
+
event.stopPropagation();
|
|
2641
|
+
toggleGraphFamilyMore();
|
|
2642
|
+
};
|
|
2643
|
+
const panel = document.createElement('div');
|
|
2644
|
+
panel.className = 'graph-family-more-panel' + (graphState.familyOverflowOpen ? ' open' : '');
|
|
2645
|
+
hiddenFamilies.forEach(family => panel.appendChild(makeGraphFamilyButton(family, family, familyCounts[family] || 0)));
|
|
2646
|
+
moreRoot.appendChild(moreButton);
|
|
2647
|
+
moreRoot.appendChild(panel);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
function syncGraphSettingsCorner() {
|
|
2652
|
+
const dock = document.getElementById('graph-settings-dock');
|
|
2653
|
+
const overlay = document.getElementById('graph-focus-overlay');
|
|
2654
|
+
if (!dock) return;
|
|
2655
|
+
const hasVisibleFocus = !!(overlay && !overlay.classList.contains('hidden') && !graphState.overlayMinimized);
|
|
2656
|
+
dock.classList.toggle('shifted', hasVisibleFocus);
|
|
2657
|
+
dock.classList.toggle('expanded', hasVisibleFocus && graphState.overlayMode === 'expanded');
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
function syncGraphSettings() {
|
|
2661
|
+
const panel = document.getElementById('graph-settings-panel');
|
|
2662
|
+
const toggle = document.getElementById('graph-settings-toggle');
|
|
2663
|
+
syncGraphSettingsCorner();
|
|
2664
|
+
if (panel) panel.classList.toggle('open', !!graphState.settingsOpen);
|
|
2665
|
+
if (toggle) toggle.classList.toggle('active', !!graphState.settingsOpen);
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
function renderWikiAskTypingHtml(label = 'Typing') {
|
|
2669
|
+
return `<span class="atlas-ask-typing"><span class="atlas-ask-typing-text">${escapeHtml(label)}</span><span class="atlas-ask-typing-dots" aria-hidden="true"><span class="atlas-ask-typing-dot"></span><span class="atlas-ask-typing-dot"></span><span class="atlas-ask-typing-dot"></span></span></span>`;
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
function toggleGraphSettings(forceState) {
|
|
2673
|
+
graphState.settingsOpen = typeof forceState === 'boolean' ? forceState : !graphState.settingsOpen;
|
|
2674
|
+
syncGraphSettings();
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
function renderGraphEdgeFilters() {
|
|
2678
|
+
const root = document.getElementById('graph-edge-filters');
|
|
2679
|
+
if (!root) return;
|
|
2680
|
+
root.innerHTML = '';
|
|
2681
|
+
const edgesByType = {
|
|
2682
|
+
references_path: docEdges.filter(edge => edge.type === 'references_path').length,
|
|
2683
|
+
references_wiki_link: docEdges.filter(edge => edge.type === 'references_wiki_link').length,
|
|
2684
|
+
};
|
|
2685
|
+
const defs = [
|
|
2686
|
+
{ key: 'all', label: 'All Links', count: docEdges.length },
|
|
2687
|
+
{ key: 'references_path', label: 'Path', count: edgesByType.references_path },
|
|
2688
|
+
{ key: 'references_wiki_link', label: 'Wiki', count: edgesByType.references_wiki_link },
|
|
2689
|
+
];
|
|
2690
|
+
defs.forEach(def => {
|
|
2691
|
+
const btn = document.createElement('button');
|
|
2692
|
+
btn.className = 'filter-btn legend-pill edge' + (graphState.edgeFilter === def.key ? ' active' : '');
|
|
2693
|
+
btn.textContent = `${def.label} (${def.count})`;
|
|
2694
|
+
btn.onclick = () => {
|
|
2695
|
+
graphState.edgeFilter = def.key;
|
|
2696
|
+
renderGraphEdgeFilters();
|
|
2697
|
+
if (!document.getElementById('graph-search').value.trim()) closeGraphSearchResults();
|
|
2698
|
+
renderGraphDetail();
|
|
2699
|
+
drawGraph();
|
|
2700
|
+
};
|
|
2701
|
+
root.appendChild(btn);
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
function setGraphSearchTypeFilter(type) {
|
|
2706
|
+
graphState.searchTypeFilter = type;
|
|
2707
|
+
renderGraphSearchResults(document.getElementById('graph-search').value);
|
|
2708
|
+
}
|
|
2709
|
+
function moveGraphSearchSelection(direction) {
|
|
2710
|
+
if (!graphState.searchResults.length) return;
|
|
2711
|
+
const max = graphState.searchResults.length - 1;
|
|
2712
|
+
if (graphState.searchResultIndex < 0) {
|
|
2713
|
+
graphState.searchResultIndex = direction > 0 ? 0 : max;
|
|
2714
|
+
} else {
|
|
2715
|
+
graphState.searchResultIndex = (graphState.searchResultIndex + direction + graphState.searchResults.length) % graphState.searchResults.length;
|
|
2716
|
+
}
|
|
2717
|
+
renderGraphSearchResults(document.getElementById('graph-search').value);
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
function activateGraphSearchResult(result) {
|
|
2721
|
+
if (!result) return;
|
|
2722
|
+
if (result.type === 'ask') {
|
|
2723
|
+
toggleWikiAsk(true);
|
|
2724
|
+
loadWikiAskHistoryItem(result.id);
|
|
2725
|
+
closeGraphSearchResults();
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
focusGraphNode(result.id);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
function commitGraphSearchSelection() {
|
|
2732
|
+
if (graphState.searchResultIndex >= 0 && graphState.searchResultIndex < graphState.searchResults.length) {
|
|
2733
|
+
activateGraphSearchResult(graphState.searchResults[graphState.searchResultIndex]);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
if (graphState.searchResults.length) {
|
|
2737
|
+
activateGraphSearchResult(graphState.searchResults[0]);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
function scoreSearchText(query, value, exactScore, prefixScore, includesScore) {
|
|
2742
|
+
const text = String(value || '').toLowerCase().trim();
|
|
2743
|
+
if (!text || !query) return 0;
|
|
2744
|
+
if (text === query) return exactScore;
|
|
2745
|
+
if (text.startsWith(query)) return prefixScore;
|
|
2746
|
+
if (text.includes(query)) return includesScore;
|
|
2747
|
+
return 0;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
function buildUnifiedDocSearchResults(query) {
|
|
2751
|
+
return INDEX.documents.map(doc => {
|
|
2752
|
+
const score =
|
|
2753
|
+
scoreSearchText(query, doc.title || '', 220, 150, 100) +
|
|
2754
|
+
scoreSearchText(query, doc.id || '', 180, 120, 80) +
|
|
2755
|
+
(doc.issues || []).reduce((total, value) => total + scoreSearchText(query, value, 140, 100, 70), 0) +
|
|
2756
|
+
(doc.knowledge_ids || []).reduce((total, value) => total + scoreSearchText(query, value, 140, 100, 70), 0) +
|
|
2757
|
+
Math.min(24, nodeStats[doc.id]?.degree || 0);
|
|
2758
|
+
return {
|
|
2759
|
+
type: 'doc',
|
|
2760
|
+
id: doc.id,
|
|
2761
|
+
score,
|
|
2762
|
+
doc,
|
|
2763
|
+
sortKey: (doc.title || doc.id || '').toLowerCase(),
|
|
2764
|
+
};
|
|
2765
|
+
}).filter(item => item.score > 0).sort((a, b) => (b.score - a.score) || a.sortKey.localeCompare(b.sortKey));
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
function buildUnifiedAskSearchResults(query) {
|
|
2769
|
+
return (wikiAskState.history || []).map((item, index) => {
|
|
2770
|
+
const score =
|
|
2771
|
+
scoreSearchText(query, item.question || '', 240, 170, 110) +
|
|
2772
|
+
scoreSearchText(query, item.answer_preview || '', 60, 45, 30) +
|
|
2773
|
+
(item.source_paths || []).reduce((total, value) => total + scoreSearchText(query, value, 170, 120, 85), 0) +
|
|
2774
|
+
Math.max(0, 18 - index);
|
|
2775
|
+
return {
|
|
2776
|
+
type: 'ask',
|
|
2777
|
+
id: item.id,
|
|
2778
|
+
score,
|
|
2779
|
+
item,
|
|
2780
|
+
sortKey: String(item.timestamp || ''),
|
|
2781
|
+
};
|
|
2782
|
+
}).filter(result => result.score > 0).sort((a, b) => (b.score - a.score) || String(b.item.timestamp || '').localeCompare(String(a.item.timestamp || '')));
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
function renderGraphSearchToolbar(root, counts) {
|
|
2786
|
+
const toolbar = document.createElement('div');
|
|
2787
|
+
toolbar.className = 'graph-results-toolbar';
|
|
2788
|
+
const filters = document.createElement('div');
|
|
2789
|
+
filters.className = 'graph-results-filter-row';
|
|
2790
|
+
[
|
|
2791
|
+
['all', `All (${counts.all})`],
|
|
2792
|
+
['doc', `Docs (${counts.doc})`],
|
|
2793
|
+
['ask', `Asks (${counts.ask})`],
|
|
2794
|
+
].forEach(([type, label]) => {
|
|
2795
|
+
const button = document.createElement('button');
|
|
2796
|
+
button.className = 'graph-results-type-btn' + (graphState.searchTypeFilter === type ? ' active' : '');
|
|
2797
|
+
button.textContent = label;
|
|
2798
|
+
button.onclick = () => setGraphSearchTypeFilter(type);
|
|
2799
|
+
filters.appendChild(button);
|
|
2800
|
+
});
|
|
2801
|
+
const meta = document.createElement('div');
|
|
2802
|
+
meta.className = 'graph-results-meta';
|
|
2803
|
+
meta.textContent = graphState.searchTypeFilter === 'all'
|
|
2804
|
+
? (wikiAskState.historyLoaded ? 'Unified SDTK-WIKI search' : 'Doc search; saved asks loading')
|
|
2805
|
+
: graphState.searchTypeFilter === 'doc'
|
|
2806
|
+
? 'Doc results only'
|
|
2807
|
+
: 'Saved ask results only';
|
|
2808
|
+
toolbar.appendChild(filters);
|
|
2809
|
+
toolbar.appendChild(meta);
|
|
2810
|
+
root.appendChild(toolbar);
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
async function renderGraphSearchResults(query) {
|
|
2814
|
+
const root = document.getElementById('graph-search-results');
|
|
2815
|
+
const q = query.trim().toLowerCase();
|
|
2816
|
+
if (!q) {
|
|
2817
|
+
graphState.searchResults = [];
|
|
2818
|
+
graphState.searchResultIndex = -1;
|
|
2819
|
+
root.classList.remove('open');
|
|
2820
|
+
root.innerHTML = '';
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
const needsHistory = !wikiAskState.historyLoaded;
|
|
2824
|
+
if (needsHistory) {
|
|
2825
|
+
ensureWikiAskHistory().then(() => {
|
|
2826
|
+
const input = document.getElementById('graph-search');
|
|
2827
|
+
if (input && input.value.trim().toLowerCase() === q) renderGraphSearchResults(input.value);
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
const docResults = buildUnifiedDocSearchResults(q);
|
|
2831
|
+
const askResults = needsHistory ? [] : buildUnifiedAskSearchResults(q);
|
|
2832
|
+
const counts = { all: docResults.length + askResults.length, doc: docResults.length, ask: askResults.length };
|
|
2833
|
+
let matches = docResults.concat(askResults).sort((a, b) => (b.score - a.score) || a.sortKey.localeCompare(b.sortKey));
|
|
2834
|
+
if (graphState.searchTypeFilter !== 'all') matches = matches.filter(item => item.type === graphState.searchTypeFilter);
|
|
2835
|
+
matches = matches.slice(0, 12);
|
|
2836
|
+
graphState.searchResults = matches;
|
|
2837
|
+
if (!matches.length) {
|
|
2838
|
+
graphState.searchResultIndex = -1;
|
|
2839
|
+
root.classList.add('open');
|
|
2840
|
+
root.innerHTML = '';
|
|
2841
|
+
renderGraphSearchToolbar(root, counts);
|
|
2842
|
+
root.insertAdjacentHTML('beforeend', '<div class="result-item"><strong>No match</strong><span>Try a shorter title, path, issue id, knowledge id, or previous ask question.</span></div>');
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
if (graphState.searchResultIndex >= matches.length) graphState.searchResultIndex = matches.length - 1;
|
|
2846
|
+
root.classList.add('open');
|
|
2847
|
+
root.innerHTML = '';
|
|
2848
|
+
renderGraphSearchToolbar(root, counts);
|
|
2849
|
+
matches.forEach((result, index) => {
|
|
2850
|
+
const item = document.createElement('div');
|
|
2851
|
+
item.className = 'result-item' + (index == graphState.searchResultIndex ? ' active' : '');
|
|
2852
|
+
if (result.type === 'ask') {
|
|
2853
|
+
const ask = result.item;
|
|
2854
|
+
const meta = [ask.timestamp || '', getWikiAskModeLabel(ask.mode || 'current-focus')].filter(Boolean).join(' | ');
|
|
2855
|
+
item.innerHTML = `<div class="result-copy"><strong>${escapeHtml(ask.question || 'Saved ask')}</strong><span>${escapeHtml(meta)} • ${(ask.source_paths || []).length} sources</span></div><span class="result-kind-tag ask">Ask</span>`;
|
|
2856
|
+
} else {
|
|
2857
|
+
const atlasDoc = result.doc;
|
|
2858
|
+
item.innerHTML = `<div class="result-copy"><strong>${escapeHtml(atlasDoc.title || atlasDoc.id)}</strong><span>${escapeHtml(atlasDoc.id)} • ${escapeHtml(atlasDoc.family)}</span></div><div style="display:flex;align-items:center;gap:8px"><span class="result-kind-tag doc">Doc</span><button class="result-add" title="Add to Ask Sources">+ Source</button></div>`;
|
|
2859
|
+
const addButton = item.querySelector('.result-add');
|
|
2860
|
+
if (addButton) {
|
|
2861
|
+
addButton.onclick = (event) => {
|
|
2862
|
+
event.stopPropagation();
|
|
2863
|
+
addWikiAskSource(atlasDoc.id);
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
item.onclick = () => {
|
|
2868
|
+
activateGraphSearchResult(result);
|
|
2869
|
+
};
|
|
2870
|
+
root.appendChild(item);
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
function renderGraphHoverCard() {
|
|
2875
|
+
const card = document.getElementById('graph-hover-card');
|
|
2876
|
+
if (!card) return;
|
|
2877
|
+
if (graphState.selectedId || !graphState.hoveredId) {
|
|
2878
|
+
card.classList.remove('open');
|
|
2879
|
+
card.innerHTML = '';
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
const hovered = graphState.renderedPositions.find(pos => pos.id === graphState.hoveredId);
|
|
2883
|
+
if (!hovered) {
|
|
2884
|
+
card.classList.remove('open');
|
|
2885
|
+
card.innerHTML = '';
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
const doc = hovered.doc;
|
|
2889
|
+
const preview = String(doc.body_markdown || '').replace(/\s+/g, ' ').trim();
|
|
2890
|
+
const shell = document.querySelector('.graph-canvas-shell');
|
|
2891
|
+
const shellRect = shell ? shell.getBoundingClientRect() : { width: 0, height: 0 };
|
|
2892
|
+
const cardWidth = 280;
|
|
2893
|
+
const cardHeight = 124;
|
|
2894
|
+
const left = Math.max(18, Math.min(hovered.x + 18, shellRect.width - cardWidth - 18));
|
|
2895
|
+
const top = Math.max(18, Math.min(hovered.y - cardHeight / 2, shellRect.height - cardHeight - 18));
|
|
2896
|
+
card.style.left = `${left}px`;
|
|
2897
|
+
card.style.top = `${top}px`;
|
|
2898
|
+
card.innerHTML = `<div class="graph-hover-title">${escapeHtml(doc.title || doc.id)}</div><div class="graph-hover-meta">${escapeHtml(doc.family)} · ${escapeHtml(doc.trust_zone)} trust</div><div class="graph-hover-body">${escapeHtml(preview ? `${preview.slice(0, 140)}${preview.length > 140 ? '...' : ''}` : doc.id)}</div>`;
|
|
2899
|
+
card.classList.add('open');
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
function isMarkdownTableSeparator(line) {
|
|
2903
|
+
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(line.trim());
|
|
2904
|
+
}
|
|
2905
|
+
function splitMarkdownTableRow(line) {
|
|
2906
|
+
const normalized = line.trim().replace(/^\|/, '').replace(/\|$/, '');
|
|
2907
|
+
return normalized.split('|').map(cell => cell.trim());
|
|
2908
|
+
}
|
|
2909
|
+
function renderMarkdownTable(headers, rows) {
|
|
2910
|
+
const headHtml = headers.map(header => `<th>${formatInlineMarkdown(header)}</th>`).join('');
|
|
2911
|
+
const bodyHtml = rows.map(row => `<tr>${headers.map((_, index) => `<td>${formatInlineMarkdown(row[index] || '')}</td>`).join('')}</tr>`).join('');
|
|
2912
|
+
return `<div class="graph-note-table-wrap"><table class="graph-note-table"><thead><tr>${headHtml}</tr></thead><tbody>${bodyHtml}</tbody></table></div>`;
|
|
2913
|
+
}
|
|
2914
|
+
function renderMarkdownFragment(markdown) {
|
|
2915
|
+
const embedded = stashSupportedHtmlEmbeds(markdown);
|
|
2916
|
+
const htmlBlockByToken = new Map(embedded.htmlBlocks.map(item => [item.token, item.html]));
|
|
2917
|
+
const source = embedded.source.replace(/\r\n/g, '\n');
|
|
2918
|
+
const lines = source.split('\n');
|
|
2919
|
+
const chunks = [];
|
|
2920
|
+
let paragraph = [];
|
|
2921
|
+
let listItems = [];
|
|
2922
|
+
let orderedList = false;
|
|
2923
|
+
let codeLines = [];
|
|
2924
|
+
let codeLanguage = '';
|
|
2925
|
+
let inCode = false;
|
|
2926
|
+
|
|
2927
|
+
const flushParagraph = () => {
|
|
2928
|
+
if (!paragraph.length) return;
|
|
2929
|
+
chunks.push(`<p>${formatInlineMarkdown(paragraph.join(' '))}</p>`);
|
|
2930
|
+
paragraph = [];
|
|
2931
|
+
};
|
|
2932
|
+
const flushList = () => {
|
|
2933
|
+
if (!listItems.length) return;
|
|
2934
|
+
const tag = orderedList ? 'ol' : 'ul';
|
|
2935
|
+
chunks.push(`<${tag}>${listItems.map(item => `<li>${formatInlineMarkdown(item)}</li>`).join('')}</${tag}>`);
|
|
2936
|
+
listItems = [];
|
|
2937
|
+
orderedList = false;
|
|
2938
|
+
};
|
|
2939
|
+
const flushCode = () => {
|
|
2940
|
+
if (!codeLines.length) {
|
|
2941
|
+
codeLanguage = '';
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
const codeSource = codeLines.join('\n');
|
|
2945
|
+
if (codeLanguage === 'mermaid') {
|
|
2946
|
+
chunks.push(renderMermaidBlock(codeSource));
|
|
2947
|
+
} else {
|
|
2948
|
+
chunks.push(`<pre><code>${escapeHtml(codeSource)}</code></pre>`);
|
|
2949
|
+
}
|
|
2950
|
+
codeLines = [];
|
|
2951
|
+
codeLanguage = '';
|
|
2952
|
+
};
|
|
2953
|
+
|
|
2954
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2955
|
+
const line = lines[index].replace(/\t/g, ' ');
|
|
2956
|
+
const trimmed = line.trim();
|
|
2957
|
+
const nextTrimmed = index + 1 < lines.length ? lines[index + 1].trim() : '';
|
|
2958
|
+
|
|
2959
|
+
if (trimmed.startsWith('```')) {
|
|
2960
|
+
const fenceLanguage = trimmed.slice(3).trim().toLowerCase();
|
|
2961
|
+
flushParagraph();
|
|
2962
|
+
flushList();
|
|
2963
|
+
if (inCode) {
|
|
2964
|
+
flushCode();
|
|
2965
|
+
inCode = false;
|
|
2966
|
+
} else {
|
|
2967
|
+
inCode = true;
|
|
2968
|
+
codeLines = [];
|
|
2969
|
+
codeLanguage = fenceLanguage;
|
|
2970
|
+
}
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
if (inCode) {
|
|
2974
|
+
codeLines.push(line);
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
if (!trimmed) {
|
|
2978
|
+
flushParagraph();
|
|
2979
|
+
flushList();
|
|
2980
|
+
continue;
|
|
2981
|
+
}
|
|
2982
|
+
if (htmlBlockByToken.has(trimmed)) {
|
|
2983
|
+
flushParagraph();
|
|
2984
|
+
flushList();
|
|
2985
|
+
flushCode();
|
|
2986
|
+
chunks.push(htmlBlockByToken.get(trimmed));
|
|
2987
|
+
continue;
|
|
2988
|
+
}
|
|
2989
|
+
const standaloneImageMatch = trimmed.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
|
|
2990
|
+
if (standaloneImageMatch) {
|
|
2991
|
+
flushParagraph();
|
|
2992
|
+
flushList();
|
|
2993
|
+
chunks.push(renderMarkdownImage(standaloneImageMatch[1], standaloneImageMatch[2]));
|
|
2994
|
+
continue;
|
|
2995
|
+
}
|
|
2996
|
+
if (trimmed.includes('|') && nextTrimmed && isMarkdownTableSeparator(nextTrimmed)) {
|
|
2997
|
+
flushParagraph();
|
|
2998
|
+
flushList();
|
|
2999
|
+
const headers = splitMarkdownTableRow(trimmed);
|
|
3000
|
+
const rows = [];
|
|
3001
|
+
index += 2;
|
|
3002
|
+
while (index < lines.length) {
|
|
3003
|
+
const tableLine = lines[index].trim();
|
|
3004
|
+
if (!tableLine || !tableLine.includes('|')) {
|
|
3005
|
+
index -= 1;
|
|
3006
|
+
break;
|
|
3007
|
+
}
|
|
3008
|
+
rows.push(splitMarkdownTableRow(tableLine));
|
|
3009
|
+
index += 1;
|
|
3010
|
+
}
|
|
3011
|
+
if (index >= lines.length) {
|
|
3012
|
+
index = lines.length;
|
|
3013
|
+
}
|
|
3014
|
+
chunks.push(renderMarkdownTable(headers, rows));
|
|
3015
|
+
continue;
|
|
3016
|
+
}
|
|
3017
|
+
if (/^---+$/.test(trimmed)) {
|
|
3018
|
+
flushParagraph();
|
|
3019
|
+
flushList();
|
|
3020
|
+
chunks.push('<hr>');
|
|
3021
|
+
continue;
|
|
3022
|
+
}
|
|
3023
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
3024
|
+
if (headingMatch) {
|
|
3025
|
+
flushParagraph();
|
|
3026
|
+
flushList();
|
|
3027
|
+
const level = Math.min(4, headingMatch[1].length + 2);
|
|
3028
|
+
chunks.push(`<h${level}>${formatInlineMarkdown(headingMatch[2])}</h${level}>`);
|
|
3029
|
+
continue;
|
|
3030
|
+
}
|
|
3031
|
+
const quoteMatch = trimmed.match(/^>\s?(.*)$/);
|
|
3032
|
+
if (quoteMatch) {
|
|
3033
|
+
flushParagraph();
|
|
3034
|
+
flushList();
|
|
3035
|
+
chunks.push(`<blockquote>${formatInlineMarkdown(quoteMatch[1])}</blockquote>`);
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
const orderedMatch = trimmed.match(/^\d+\.\s+(.+)$/);
|
|
3039
|
+
if (orderedMatch) {
|
|
3040
|
+
flushParagraph();
|
|
3041
|
+
if (listItems.length && !orderedList) flushList();
|
|
3042
|
+
orderedList = true;
|
|
3043
|
+
listItems.push(orderedMatch[1]);
|
|
3044
|
+
continue;
|
|
3045
|
+
}
|
|
3046
|
+
const bulletMatch = trimmed.match(/^[-*]\s+(.+)$/);
|
|
3047
|
+
if (bulletMatch) {
|
|
3048
|
+
flushParagraph();
|
|
3049
|
+
if (listItems.length && orderedList) flushList();
|
|
3050
|
+
orderedList = false;
|
|
3051
|
+
listItems.push(bulletMatch[1]);
|
|
3052
|
+
continue;
|
|
3053
|
+
}
|
|
3054
|
+
paragraph.push(trimmed);
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
flushParagraph();
|
|
3058
|
+
flushList();
|
|
3059
|
+
flushCode();
|
|
3060
|
+
return chunks.join('') || '<p class="muted">No markdown body available for this document.</p>';
|
|
3061
|
+
}
|
|
3062
|
+
function renderInteractiveChipSections(doc) {
|
|
3063
|
+
const groups = [
|
|
3064
|
+
{ label: 'Issues', kind: 'issue', values: doc.issues },
|
|
3065
|
+
{ label: 'Knowledge', kind: 'knowledge', values: doc.knowledge_ids },
|
|
3066
|
+
{ label: 'Skills', kind: 'skill', values: doc.skill_refs },
|
|
3067
|
+
{ label: 'Templates', kind: 'template', values: doc.template_refs },
|
|
3068
|
+
{ label: 'Releases', kind: 'release', values: doc.release_refs },
|
|
3069
|
+
{ label: 'Lanes', kind: 'lane', values: doc.lane_refs },
|
|
3070
|
+
].filter(group => group.values && group.values.length);
|
|
3071
|
+
|
|
3072
|
+
return groups.map(group => {
|
|
3073
|
+
const chips = group.values.map(value => {
|
|
3074
|
+
const count = getScopeDocIds(group.kind, value).length;
|
|
3075
|
+
const active = graphState.scopeFilter && graphState.scopeFilter.kind === group.kind && graphState.scopeFilter.value === value;
|
|
3076
|
+
return `<button class="graph-chip${active ? ' active' : ''}" title="${escapeHtml(value)}" onclick="activateGraphScope('${group.kind}', ${JSON.stringify(value)})"><span>${escapeHtml(getScopeValueLabel(group.kind, value))}</span><small>${count}</small></button>`;
|
|
3077
|
+
}).join('');
|
|
3078
|
+
return `<div class="graph-chip-section"><div class="graph-chip-label">${escapeHtml(group.label)}</div><div class="graph-chip-group">${chips}</div></div>`;
|
|
3079
|
+
}).join('');
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
function renderGraphFocusActions() {
|
|
3083
|
+
const canBack = graphState.historyIndex > 0;
|
|
3084
|
+
const canForward = graphState.historyIndex >= 0 && graphState.historyIndex < graphState.history.length - 1;
|
|
3085
|
+
const overlayLabel = graphState.overlayMode === 'compact' ? 'Expand' : 'Compact';
|
|
3086
|
+
const collapseLabel = graphState.overlayCollapsed ? '▾' : '▴';
|
|
3087
|
+
const collapseTitle = graphState.overlayCollapsed ? 'Expand Graph Focus' : 'Collapse Graph Focus';
|
|
3088
|
+
return `<div class="graph-focus-actions"><button class="graph-history-btn" onclick="navigateGraphHistory(-1)" ${canBack ? '' : 'disabled'}>←</button><button class="graph-history-btn" onclick="navigateGraphHistory(1)" ${canForward ? '' : 'disabled'}>→</button><button class="graph-focus-toggle" title="${collapseTitle}" onclick="toggleGraphFocusCollapsed()">${collapseLabel}</button><button class="graph-overlay-btn" onclick="toggleGraphOverlayMode()">${overlayLabel}</button><button class="graph-focus-minimize" title="Minimize Graph Focus" onclick="toggleGraphFocusMinimized(true)">−</button><button class="graph-focus-close" onclick="clearGraphSelection()">×</button></div>`;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
function renderGraphDetail() {
|
|
3092
|
+
syncGraphOverlayMode();
|
|
3093
|
+
const overlay = document.getElementById('graph-focus-overlay');
|
|
3094
|
+
const panel = document.getElementById('graph-focus-panel');
|
|
3095
|
+
if (graphState.overlayMinimized) {
|
|
3096
|
+
if (overlay) overlay.classList.remove('hidden');
|
|
3097
|
+
const focusId = getGraphFocusId();
|
|
3098
|
+
const doc = focusId && docById[focusId] ? docById[focusId] : null;
|
|
3099
|
+
const color = doc ? (FAMILY_COLORS[doc.family] || '#5f89ff') : '#5f89ff';
|
|
3100
|
+
const glyph = doc ? (FAMILY_GLYPHS[doc.family] || 'A') : 'A';
|
|
3101
|
+
const label = doc ? escapeHtml(doc.title || doc.id) : 'Graph Focus';
|
|
3102
|
+
panel.classList.add('minimized');
|
|
3103
|
+
panel.innerHTML = `<button class="graph-focus-peek" onclick="openGraphFocusPeek(event)" title="Open Graph Focus"><span class="graph-focus-peek-glyph" style="background:${color}">${glyph}</span><span>${label}</span></button>`;
|
|
3104
|
+
syncGraphSettings();
|
|
3105
|
+
requestAnimationFrame(syncGraphFocusPosition);
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
panel.classList.remove('minimized');
|
|
3109
|
+
panel.classList.toggle('collapsed', !!graphState.overlayCollapsed);
|
|
3110
|
+
const visibleIds = getVisibleDocIds();
|
|
3111
|
+
const visibleEdges = getVisibleDocEdges(visibleIds);
|
|
3112
|
+
const visibleAllEdges = docEdges.filter(edge => visibleIds.has(edge.source) && visibleIds.has(edge.target));
|
|
3113
|
+
normalizeGraphFocus(visibleIds);
|
|
3114
|
+
const focusId = getGraphFocusId();
|
|
3115
|
+
|
|
3116
|
+
if (!focusId || !docById[focusId]) {
|
|
3117
|
+
if (overlay) overlay.classList.add('hidden');
|
|
3118
|
+
panel.innerHTML = '';
|
|
3119
|
+
syncGraphSettings();
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
if (overlay) overlay.classList.remove('hidden');
|
|
3124
|
+
const doc = docById[focusId];
|
|
3125
|
+
const color = FAMILY_COLORS[doc.family] || '#8b949e';
|
|
3126
|
+
const outgoing = sortNeighbors(visibleEdges.filter(edge => edge.source === focusId).map(edge => ({ id: edge.target, type: edge.type, direction: 'out' })));
|
|
3127
|
+
const incoming = sortNeighbors(visibleEdges.filter(edge => edge.target === focusId).map(edge => ({ id: edge.source, type: edge.type, direction: 'in' })));
|
|
3128
|
+
const visibleNeighborIds = new Set([...outgoing, ...incoming].map(item => item.id));
|
|
3129
|
+
const allNeighborIds = new Set(visibleAllEdges.flatMap(edge => edge.source === focusId ? [edge.target] : (edge.target === focusId ? [edge.source] : [])));
|
|
3130
|
+
const hiddenNeighborCount = Math.max(0, allNeighborIds.size - visibleNeighborIds.size);
|
|
3131
|
+
const previewLabel = graphState.selectedId ? 'Locked Selection' : 'Hover Preview';
|
|
3132
|
+
const filterLabel = graphState.edgeFilter === 'all' ? 'All link types' : (graphState.edgeFilter === 'references_path' ? 'Path links' : 'Wiki links');
|
|
3133
|
+
const bodyPreview = String(doc.body_markdown || '').replace(/\s+/g, ' ').trim();
|
|
3134
|
+
const summaryText = bodyPreview ? `${bodyPreview.slice(0, 220)}${bodyPreview.length > 220 ? '...' : ''}` : 'No markdown body available for this document.';
|
|
3135
|
+
const chipSections = renderInteractiveChipSections(doc);
|
|
3136
|
+
const historyStrip = renderGraphHistoryStrip();
|
|
3137
|
+
|
|
3138
|
+
const renderNeighborList = (items, title) => {
|
|
3139
|
+
if (!items.length) return '';
|
|
3140
|
+
let block = `<h3>${title} (${items.length})</h3><ul>`;
|
|
3141
|
+
items.slice(0, 12).forEach(item => {
|
|
3142
|
+
const target = docById[item.id];
|
|
3143
|
+
const label = target ? (target.title || target.id) : item.id;
|
|
3144
|
+
block += `<li><code>${escapeHtml(item.type)}</code> · <span class="wiki-link" onclick="focusGraphNode('${item.id}')">${escapeHtml(label)}</span></li>`;
|
|
3145
|
+
});
|
|
3146
|
+
block += '</ul>';
|
|
3147
|
+
return block;
|
|
3148
|
+
};
|
|
3149
|
+
|
|
3150
|
+
const header = `
|
|
3151
|
+
<div class="graph-focus-drag-bar" title="Drag Graph Focus"><span></span></div>
|
|
3152
|
+
<div class="graph-focus-header">
|
|
3153
|
+
<div class="graph-focus-icon" style="background:${color}">${escapeHtml(FAMILY_GLYPHS[doc.family] || 'M')}</div>
|
|
3154
|
+
<div class="graph-focus-meta">
|
|
3155
|
+
<div class="graph-focus-kicker">${escapeHtml(doc.family)}</div>
|
|
3156
|
+
<div class="graph-focus-doc-title">${escapeHtml(doc.title || doc.id)}</div>
|
|
3157
|
+
<div class="graph-focus-subtle">${escapeHtml(previewLabel)} · ${escapeHtml(folderGroupForDoc(doc))}</div>
|
|
3158
|
+
</div>
|
|
3159
|
+
${renderGraphFocusActions()}
|
|
3160
|
+
</div>`;
|
|
3161
|
+
|
|
3162
|
+
if (graphState.overlayCollapsed) {
|
|
3163
|
+
panel.innerHTML = `${header}`;
|
|
3164
|
+
syncGraphSettings();
|
|
3165
|
+
requestAnimationFrame(syncGraphFocusPosition);
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
if (graphState.detailMode === 'full') {
|
|
3170
|
+
panel.innerHTML = `${header}
|
|
3171
|
+
<div class="graph-detail-card">
|
|
3172
|
+
<div class="graph-badge-row">
|
|
3173
|
+
<span class="graph-pill">${escapeHtml(doc.role)}</span>
|
|
3174
|
+
<span class="graph-pill">trust ${escapeHtml(doc.trust_zone)}</span>
|
|
3175
|
+
<span class="graph-pill">degree ${nodeStats[focusId]?.degree || 0}</span>
|
|
3176
|
+
<span class="graph-pill">${escapeHtml(filterLabel)}</span>
|
|
3177
|
+
</div>
|
|
3178
|
+
${historyStrip}
|
|
3179
|
+
${chipSections}
|
|
3180
|
+
<div class="full-mode-title">Full Document</div>
|
|
3181
|
+
<p><code>${escapeHtml(doc.id)}</code></p>
|
|
3182
|
+
<div class="graph-note-prose">${renderMarkdownFragment(doc.body_markdown)}</div>
|
|
3183
|
+
</div>
|
|
3184
|
+
<div class="graph-focus-footer">
|
|
3185
|
+
<button class="inline-btn" onclick="showGraphSummary()">Back to Summary</button>
|
|
3186
|
+
<button class="inline-btn" onclick="addWikiAskSource('${focusId}')">Add to Ask Sources</button>
|
|
3187
|
+
<span class="muted">Inline view loaded from embedded SDTK-WIKI content</span>
|
|
3188
|
+
</div>`;
|
|
3189
|
+
scheduleMermaidRender(panel);
|
|
3190
|
+
syncGraphSettings();
|
|
3191
|
+
requestAnimationFrame(syncGraphFocusPosition);
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
let summaryHtml = `<div class="graph-badge-row"><span class="graph-pill"><span class="dot" style="background:${color}"></span>${escapeHtml(doc.family)}</span><span class="graph-pill">${escapeHtml(previewLabel)}</span><span class="graph-pill">degree ${nodeStats[focusId]?.degree || 0}</span><span class="graph-pill">${escapeHtml(filterLabel)}</span></div>`;
|
|
3196
|
+
summaryHtml += `<p><code>${escapeHtml(doc.id)}</code></p>`;
|
|
3197
|
+
summaryHtml += `<p class="muted">Trust zone: <code>${escapeHtml(doc.trust_zone)}</code></p>`;
|
|
3198
|
+
summaryHtml += `<p>${escapeHtml(summaryText)}</p>`;
|
|
3199
|
+
summaryHtml += historyStrip;
|
|
3200
|
+
summaryHtml += chipSections;
|
|
3201
|
+
summaryHtml += renderNeighborList(outgoing, 'Outgoing Linked Docs');
|
|
3202
|
+
summaryHtml += renderNeighborList(incoming, 'Incoming Linked Docs');
|
|
3203
|
+
if (hiddenNeighborCount > 0) {
|
|
3204
|
+
summaryHtml += `<p class="muted">${hiddenNeighborCount} linked docs are hidden by the active edge filter.</p>`;
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
panel.innerHTML = `${header}
|
|
3208
|
+
<div class="graph-detail-card">${summaryHtml}</div>
|
|
3209
|
+
<div class="graph-focus-footer">
|
|
3210
|
+
<button class="inline-btn" onclick="openGraphFullDetail('${focusId}')">Open Full Detail</button>
|
|
3211
|
+
<button class="inline-btn" onclick="centerGraphOnSelection()">Center Node</button>
|
|
3212
|
+
<button class="inline-btn" onclick="fitGraphGroup()">Fit Group</button>
|
|
3213
|
+
<button class="inline-btn" onclick="addWikiAskSource('${focusId}')">Add to Ask Sources</button>
|
|
3214
|
+
</div>`;
|
|
3215
|
+
syncGraphSettings();
|
|
3216
|
+
requestAnimationFrame(syncGraphFocusPosition);
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
function cloneGraphPositions(sourcePositions, visibleDocs, resolveGroup) {
|
|
3220
|
+
if (!sourcePositions) return null;
|
|
3221
|
+
const cloned = {};
|
|
3222
|
+
for (const doc of visibleDocs) {
|
|
3223
|
+
const existing = sourcePositions[doc.id];
|
|
3224
|
+
if (!existing) return null;
|
|
3225
|
+
cloned[doc.id] = {
|
|
3226
|
+
x: existing.x,
|
|
3227
|
+
y: existing.y,
|
|
3228
|
+
vx: 0,
|
|
3229
|
+
vy: 0,
|
|
3230
|
+
group: resolveGroup(doc),
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
3233
|
+
return cloned;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
function rememberGraphLayout(layoutKey, positions) {
|
|
3237
|
+
const snapshot = {};
|
|
3238
|
+
Object.keys(positions).forEach(id => {
|
|
3239
|
+
const pos = positions[id];
|
|
3240
|
+
snapshot[id] = { x: pos.x, y: pos.y, vx: 0, vy: 0, group: pos.group };
|
|
3241
|
+
});
|
|
3242
|
+
graphState.layoutCache[layoutKey] = snapshot;
|
|
3243
|
+
const existingIndex = graphState.layoutCacheOrder.indexOf(layoutKey);
|
|
3244
|
+
if (existingIndex !== -1) {
|
|
3245
|
+
graphState.layoutCacheOrder.splice(existingIndex, 1);
|
|
3246
|
+
}
|
|
3247
|
+
graphState.layoutCacheOrder.push(layoutKey);
|
|
3248
|
+
while (graphState.layoutCacheOrder.length > 14) {
|
|
3249
|
+
const evictedKey = graphState.layoutCacheOrder.shift();
|
|
3250
|
+
if (evictedKey) delete graphState.layoutCache[evictedKey];
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
function getGraphLayoutIterations(nodeCount, warmed) {
|
|
3255
|
+
if (warmed) {
|
|
3256
|
+
if (nodeCount >= 320) return 12;
|
|
3257
|
+
if (nodeCount >= 180) return 16;
|
|
3258
|
+
if (nodeCount >= 80) return 24;
|
|
3259
|
+
return 32;
|
|
3260
|
+
}
|
|
3261
|
+
if (nodeCount >= 320) return 28;
|
|
3262
|
+
if (nodeCount >= 180) return 40;
|
|
3263
|
+
if (nodeCount >= 80) return 60;
|
|
3264
|
+
return 90;
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
function ensureGraphLayout(width, height, visibleDocs, visibleEdges) {
|
|
3268
|
+
const scopeKey = graphState.scopeFilter ? `${graphState.scopeFilter.kind}:${graphState.scopeFilter.value}` : 'noscope';
|
|
3269
|
+
const layoutKey = `${width}x${height}:${graphState.groupMode}:${graphState.familyFilter || 'all'}:${scopeKey}:${visibleDocs.length}`;
|
|
3270
|
+
const resolveGroup = (doc) => graphState.groupMode === 'folder' ? folderGroupForDoc(doc) : doc.family;
|
|
3271
|
+
if (graphState.layoutKey === layoutKey && graphState.positions) {
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
const cachedPositions = graphState.layoutCache[layoutKey];
|
|
3275
|
+
if (cachedPositions) {
|
|
3276
|
+
graphState.positions = cloneGraphPositions(cachedPositions, visibleDocs, resolveGroup);
|
|
3277
|
+
graphState.layoutKey = layoutKey;
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
const groupBy = {};
|
|
3281
|
+
visibleDocs.forEach(doc => {
|
|
3282
|
+
const group = resolveGroup(doc);
|
|
3283
|
+
(groupBy[group] = groupBy[group] || []).push(doc);
|
|
3284
|
+
});
|
|
3285
|
+
const groupNames = Object.keys(groupBy).sort();
|
|
3286
|
+
const isEmbeddedCompactGraph = EMBEDDED_ATLAS && visibleDocs.length <= 12;
|
|
3287
|
+
const clusterRadius = isEmbeddedCompactGraph
|
|
3288
|
+
? Math.min(width, height) * 0.1
|
|
3289
|
+
: Math.min(width, height) * 0.29;
|
|
3290
|
+
const centers = {};
|
|
3291
|
+
groupNames.forEach((group, index) => {
|
|
3292
|
+
const angle = (index / Math.max(groupNames.length, 1)) * Math.PI * 2;
|
|
3293
|
+
centers[group] = {
|
|
3294
|
+
x: isEmbeddedCompactGraph
|
|
3295
|
+
? width / 2 + clusterRadius * Math.cos(angle - Math.PI / 2)
|
|
3296
|
+
: width / 2 + clusterRadius * Math.cos(angle),
|
|
3297
|
+
y: isEmbeddedCompactGraph
|
|
3298
|
+
? height / 2 + clusterRadius * Math.sin(angle - Math.PI / 2)
|
|
3299
|
+
: height / 2 + clusterRadius * Math.sin(angle),
|
|
3300
|
+
};
|
|
3301
|
+
});
|
|
3302
|
+
|
|
3303
|
+
const allLayoutKey = `${width}x${height}:${graphState.groupMode}:all:noscope:${docs.length}`;
|
|
3304
|
+
let positions = cloneGraphPositions(graphState.layoutCache[allLayoutKey], visibleDocs, resolveGroup);
|
|
3305
|
+
let warmed = Boolean(positions);
|
|
3306
|
+
if (!positions && graphState.positions) {
|
|
3307
|
+
positions = cloneGraphPositions(graphState.positions, visibleDocs, resolveGroup);
|
|
3308
|
+
warmed = Boolean(positions);
|
|
3309
|
+
}
|
|
3310
|
+
if (!positions) {
|
|
3311
|
+
positions = {};
|
|
3312
|
+
groupNames.forEach(group => {
|
|
3313
|
+
const members = groupBy[group].slice().sort((a, b) => (a.title || a.id).localeCompare(b.title || b.id));
|
|
3314
|
+
const memberRadius = isEmbeddedCompactGraph
|
|
3315
|
+
? Math.max(120, Math.min(260, 56 + members.length * 22))
|
|
3316
|
+
: Math.max(34, Math.min(140, 18 + Math.sqrt(members.length) * 12));
|
|
3317
|
+
members.forEach((doc, index) => {
|
|
3318
|
+
const angle = (index / Math.max(members.length, 1)) * Math.PI * 2;
|
|
3319
|
+
const seed = hashString(doc.id);
|
|
3320
|
+
const jitterX = ((seed % 17) - 8) * 1.8;
|
|
3321
|
+
const jitterY = (((Math.floor(seed / 17)) % 17) - 8) * 1.8;
|
|
3322
|
+
positions[doc.id] = {
|
|
3323
|
+
x: centers[group].x + memberRadius * Math.cos(angle) + jitterX,
|
|
3324
|
+
y: centers[group].y + memberRadius * Math.sin(angle) + jitterY,
|
|
3325
|
+
vx: 0,
|
|
3326
|
+
vy: 0,
|
|
3327
|
+
group,
|
|
3328
|
+
};
|
|
3329
|
+
});
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
const iterations = getGraphLayoutIterations(visibleDocs.length, warmed);
|
|
3334
|
+
for (let iter = 0; iter < iterations; iter += 1) {
|
|
3335
|
+
for (let i = 0; i < visibleDocs.length; i += 1) {
|
|
3336
|
+
for (let j = i + 1; j < visibleDocs.length; j += 1) {
|
|
3337
|
+
const a = positions[visibleDocs[i].id];
|
|
3338
|
+
const b = positions[visibleDocs[j].id];
|
|
3339
|
+
let dx = b.x - a.x;
|
|
3340
|
+
let dy = b.y - a.y;
|
|
3341
|
+
const distance = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
3342
|
+
const force = 300 / (distance * distance);
|
|
3343
|
+
a.vx -= (dx / distance) * force;
|
|
3344
|
+
a.vy -= (dy / distance) * force;
|
|
3345
|
+
b.vx += (dx / distance) * force;
|
|
3346
|
+
b.vy += (dy / distance) * force;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
visibleEdges.forEach(edge => {
|
|
3351
|
+
const a = positions[edge.source];
|
|
3352
|
+
const b = positions[edge.target];
|
|
3353
|
+
let dx = b.x - a.x;
|
|
3354
|
+
let dy = b.y - a.y;
|
|
3355
|
+
const distance = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
3356
|
+
const force = (distance - 78) * 0.009;
|
|
3357
|
+
a.vx += (dx / distance) * force;
|
|
3358
|
+
a.vy += (dy / distance) * force;
|
|
3359
|
+
b.vx -= (dx / distance) * force;
|
|
3360
|
+
b.vy -= (dy / distance) * force;
|
|
3361
|
+
});
|
|
3362
|
+
|
|
3363
|
+
visibleDocs.forEach(doc => {
|
|
3364
|
+
const pos = positions[doc.id];
|
|
3365
|
+
const target = centers[pos.group];
|
|
3366
|
+
pos.vx += (target.x - pos.x) * 0.012;
|
|
3367
|
+
pos.vy += (target.y - pos.y) * 0.012;
|
|
3368
|
+
pos.vx += (width / 2 - pos.x) * 0.0008;
|
|
3369
|
+
pos.vy += (height / 2 - pos.y) * 0.0008;
|
|
3370
|
+
pos.vx *= 0.80;
|
|
3371
|
+
pos.vy *= 0.80;
|
|
3372
|
+
pos.x += pos.vx;
|
|
3373
|
+
pos.y += pos.vy;
|
|
3374
|
+
pos.x = Math.max(26, Math.min(width - 26, pos.x));
|
|
3375
|
+
pos.y = Math.max(26, Math.min(height - 26, pos.y));
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
graphState.positions = positions;
|
|
3380
|
+
graphState.layoutKey = layoutKey;
|
|
3381
|
+
rememberGraphLayout(layoutKey, positions);
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
function drawGraph() {
|
|
3385
|
+
const visibleDocs = getVisibleDocs();
|
|
3386
|
+
const isEmbeddedCompactGraphView = EMBEDDED_ATLAS && visibleDocs.length <= 12;
|
|
3387
|
+
const visibleIds = new Set(visibleDocs.map(doc => doc.id));
|
|
3388
|
+
normalizeGraphFocus(visibleIds);
|
|
3389
|
+
const visibleEdges = getVisibleDocEdges(visibleIds);
|
|
3390
|
+
|
|
3391
|
+
const canvas = document.getElementById('graph-canvas');
|
|
3392
|
+
const shell = canvas.parentElement;
|
|
3393
|
+
const width = Math.max(540, shell.clientWidth - 28);
|
|
3394
|
+
const height = Math.max(420, shell.clientHeight - 28);
|
|
3395
|
+
const dpr = window.devicePixelRatio || 1;
|
|
3396
|
+
canvas.width = width * dpr;
|
|
3397
|
+
canvas.height = height * dpr;
|
|
3398
|
+
canvas.style.width = `${width}px`;
|
|
3399
|
+
canvas.style.height = `${height}px`;
|
|
3400
|
+
const ctx = canvas.getContext('2d');
|
|
3401
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3402
|
+
ctx.clearRect(0, 0, width, height);
|
|
3403
|
+
|
|
3404
|
+
ensureGraphLayout(width, height, visibleDocs, visibleEdges);
|
|
3405
|
+
|
|
3406
|
+
const focusId = getGraphFocusId();
|
|
3407
|
+
const neighborIds = focusId ? getNeighborIds(focusId, visibleEdges) : new Set();
|
|
3408
|
+
const viewport = getGraphViewportMetrics(width, height);
|
|
3409
|
+
syncGraphOverlayMode();
|
|
3410
|
+
syncGraphToolbar();
|
|
3411
|
+
const selectedBasePos = graphState.selectedId ? graphState.positions[graphState.selectedId] : null;
|
|
3412
|
+
const shouldFitFiltered = (graphState.familyFilter || graphState.scopeFilter) && !focusId && !graphState.isDragging;
|
|
3413
|
+
if (graphState.fitVisibleRequested && !graphState.isDragging) {
|
|
3414
|
+
const bounds = computePositionBounds(visibleDocs);
|
|
3415
|
+
if (bounds) {
|
|
3416
|
+
const paddingX = 120;
|
|
3417
|
+
const paddingY = 120;
|
|
3418
|
+
const fitZoomX = viewport.usableWidth / (bounds.width + paddingX);
|
|
3419
|
+
const fitZoomY = viewport.usableHeight / (bounds.height + paddingY);
|
|
3420
|
+
graphState.targetZoom = isEmbeddedCompactGraphView
|
|
3421
|
+
? Math.max(2.6, Math.min(6.5, fitZoomX, fitZoomY))
|
|
3422
|
+
: Math.max(0.82, Math.min(1.52, fitZoomX, fitZoomY));
|
|
3423
|
+
const fit = computePanForTargetCenter(
|
|
3424
|
+
bounds.centerX,
|
|
3425
|
+
bounds.centerY,
|
|
3426
|
+
viewport.centerX - Math.min(28, viewport.overlayWidth * 0.05),
|
|
3427
|
+
viewport.centerY,
|
|
3428
|
+
width,
|
|
3429
|
+
height,
|
|
3430
|
+
graphState.targetZoom,
|
|
3431
|
+
);
|
|
3432
|
+
graphState.targetPanX = fit.panX;
|
|
3433
|
+
graphState.targetPanY = fit.panY;
|
|
3434
|
+
}
|
|
3435
|
+
graphState.fitVisibleRequested = false;
|
|
3436
|
+
} else if (graphState.autoFocus && selectedBasePos && !graphState.manualViewport) {
|
|
3437
|
+
const focusOffsetX = viewport.overlayWidth ? Math.min(Math.max(180, viewport.overlayWidth * 0.55), viewport.overlayWidth * 0.78) : 0;
|
|
3438
|
+
graphState.targetZoom = 1.14;
|
|
3439
|
+
const focused = computePanForTargetCenter(
|
|
3440
|
+
selectedBasePos.x,
|
|
3441
|
+
selectedBasePos.y,
|
|
3442
|
+
viewport.centerX - focusOffsetX * 0.45,
|
|
3443
|
+
viewport.centerY,
|
|
3444
|
+
width,
|
|
3445
|
+
height,
|
|
3446
|
+
graphState.targetZoom,
|
|
3447
|
+
);
|
|
3448
|
+
graphState.targetPanX = focused.panX;
|
|
3449
|
+
graphState.targetPanY = focused.panY;
|
|
3450
|
+
} else if (shouldFitFiltered && !graphState.manualViewport) {
|
|
3451
|
+
const bounds = computePositionBounds(visibleDocs);
|
|
3452
|
+
if (bounds) {
|
|
3453
|
+
const paddingX = 120;
|
|
3454
|
+
const paddingY = 120;
|
|
3455
|
+
const fitZoomX = viewport.usableWidth / (bounds.width + paddingX);
|
|
3456
|
+
const fitZoomY = viewport.usableHeight / (bounds.height + paddingY);
|
|
3457
|
+
graphState.targetZoom = isEmbeddedCompactGraphView
|
|
3458
|
+
? Math.max(2.4, Math.min(6.2, fitZoomX, fitZoomY))
|
|
3459
|
+
: Math.max(0.84, Math.min(1.48, fitZoomX, fitZoomY));
|
|
3460
|
+
const fit = computePanForTargetCenter(
|
|
3461
|
+
bounds.centerX,
|
|
3462
|
+
bounds.centerY,
|
|
3463
|
+
viewport.centerX - Math.min(32, viewport.overlayWidth * 0.06),
|
|
3464
|
+
viewport.centerY,
|
|
3465
|
+
width,
|
|
3466
|
+
height,
|
|
3467
|
+
graphState.targetZoom,
|
|
3468
|
+
);
|
|
3469
|
+
graphState.targetPanX = fit.panX;
|
|
3470
|
+
graphState.targetPanY = fit.panY;
|
|
3471
|
+
}
|
|
3472
|
+
} else if (!focusId && !graphState.isDragging && !graphState.manualViewport) {
|
|
3473
|
+
if (EMBEDDED_ATLAS) {
|
|
3474
|
+
const bounds = computePositionBounds(visibleDocs);
|
|
3475
|
+
if (bounds) {
|
|
3476
|
+
const paddingX = 120;
|
|
3477
|
+
const paddingY = 120;
|
|
3478
|
+
const fitZoomX = viewport.usableWidth / (bounds.width + paddingX);
|
|
3479
|
+
const fitZoomY = viewport.usableHeight / (bounds.height + paddingY);
|
|
3480
|
+
graphState.targetZoom = isEmbeddedCompactGraphView
|
|
3481
|
+
? Math.max(2.8, Math.min(7.2, fitZoomX, fitZoomY))
|
|
3482
|
+
: Math.max(0.92, Math.min(1.9, fitZoomX, fitZoomY));
|
|
3483
|
+
const fit = computePanForTargetCenter(
|
|
3484
|
+
bounds.centerX,
|
|
3485
|
+
bounds.centerY,
|
|
3486
|
+
viewport.centerX,
|
|
3487
|
+
viewport.centerY,
|
|
3488
|
+
width,
|
|
3489
|
+
height,
|
|
3490
|
+
graphState.targetZoom,
|
|
3491
|
+
);
|
|
3492
|
+
graphState.targetPanX = fit.panX;
|
|
3493
|
+
graphState.targetPanY = fit.panY;
|
|
3494
|
+
}
|
|
3495
|
+
} else {
|
|
3496
|
+
graphState.targetZoom = 1;
|
|
3497
|
+
graphState.targetPanX = 0;
|
|
3498
|
+
graphState.targetPanY = 0;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
applyCameraStep();
|
|
3502
|
+
|
|
3503
|
+
graphState.renderedPositions = visibleDocs.map(doc => {
|
|
3504
|
+
const raw = graphState.positions[doc.id];
|
|
3505
|
+
const animated = getDisplayPosition(doc.id, raw);
|
|
3506
|
+
const screen = worldToScreen(animated.x, animated.y, width, height);
|
|
3507
|
+
const degree = nodeStats[doc.id]?.degree || 0;
|
|
3508
|
+
return {
|
|
3509
|
+
id: doc.id,
|
|
3510
|
+
x: screen.x,
|
|
3511
|
+
y: screen.y,
|
|
3512
|
+
radius: Math.max(5.2, Math.min(16.5, (4.3 + degree * 0.55) * Math.pow(graphState.zoom, 0.12))),
|
|
3513
|
+
color: FAMILY_COLORS[doc.family] || '#8b949e',
|
|
3514
|
+
degree,
|
|
3515
|
+
glyph: FAMILY_GLYPHS[doc.family] || 'M',
|
|
3516
|
+
doc,
|
|
3517
|
+
};
|
|
3518
|
+
});
|
|
3519
|
+
|
|
3520
|
+
const renderedById = {};
|
|
3521
|
+
graphState.renderedPositions.forEach(pos => { renderedById[pos.id] = pos; });
|
|
3522
|
+
renderGraphHoverCard();
|
|
3523
|
+
|
|
3524
|
+
visibleEdges.forEach(edge => {
|
|
3525
|
+
const a = renderedById[edge.source];
|
|
3526
|
+
const b = renderedById[edge.target];
|
|
3527
|
+
if (!a || !b) return;
|
|
3528
|
+
let alpha = 0.16;
|
|
3529
|
+
if (focusId) {
|
|
3530
|
+
const touchesFocus = edge.source === focusId || edge.target === focusId;
|
|
3531
|
+
const connectsNeighbors = neighborIds.has(edge.source) && neighborIds.has(edge.target);
|
|
3532
|
+
alpha = touchesFocus ? 0.90 : (connectsNeighbors ? 0.28 : 0.05);
|
|
3533
|
+
}
|
|
3534
|
+
const baseColor = GRAPH_EDGE_COLORS[edge.type] || 'rgba(126,140,158,0.24)';
|
|
3535
|
+
ctx.strokeStyle = baseColor.replace(/0\.[0-9]+\)$/, `${alpha})`);
|
|
3536
|
+
ctx.lineWidth = ((focusId && (edge.source === focusId || edge.target === focusId)) ? 2.0 : 0.95) * Math.pow(graphState.zoom, 0.08);
|
|
3537
|
+
ctx.beginPath();
|
|
3538
|
+
ctx.moveTo(a.x, a.y);
|
|
3539
|
+
ctx.lineTo(b.x, b.y);
|
|
3540
|
+
ctx.stroke();
|
|
3541
|
+
});
|
|
3542
|
+
|
|
3543
|
+
graphState.renderedPositions.forEach(pos => {
|
|
3544
|
+
const isFocus = focusId === pos.id;
|
|
3545
|
+
const isNeighbor = neighborIds.has(pos.id);
|
|
3546
|
+
let alpha = 0.92;
|
|
3547
|
+
if (focusId) {
|
|
3548
|
+
alpha = isFocus ? 1 : (isNeighbor ? 0.96 : 0.16);
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
ctx.save();
|
|
3552
|
+
ctx.beginPath();
|
|
3553
|
+
ctx.arc(pos.x, pos.y, pos.radius, 0, Math.PI * 2);
|
|
3554
|
+
ctx.fillStyle = pos.color + Math.round(alpha * 255).toString(16).padStart(2, '0');
|
|
3555
|
+
ctx.shadowColor = isFocus ? 'rgba(49,86,201,0.34)' : 'rgba(15,23,42,0.10)';
|
|
3556
|
+
ctx.shadowBlur = isFocus ? 16 : 8;
|
|
3557
|
+
ctx.fill();
|
|
3558
|
+
ctx.shadowBlur = 0;
|
|
3559
|
+
if (isFocus) {
|
|
3560
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.98)';
|
|
3561
|
+
ctx.lineWidth = 2.5;
|
|
3562
|
+
ctx.stroke();
|
|
3563
|
+
} else if (isNeighbor) {
|
|
3564
|
+
ctx.strokeStyle = 'rgba(31,42,55,0.32)';
|
|
3565
|
+
ctx.lineWidth = 1.3;
|
|
3566
|
+
ctx.stroke();
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
ctx.fillStyle = 'rgba(255,255,255,0.98)';
|
|
3570
|
+
ctx.font = `${Math.max(8, Math.min(11, pos.radius))}px -apple-system,sans-serif`;
|
|
3571
|
+
ctx.textAlign = 'center';
|
|
3572
|
+
ctx.textBaseline = 'middle';
|
|
3573
|
+
ctx.fillText(pos.glyph, pos.x, pos.y + 0.5);
|
|
3574
|
+
ctx.restore();
|
|
3575
|
+
|
|
3576
|
+
if (isFocus || pos.degree >= 8 || (isNeighbor && pos.degree >= 4)) {
|
|
3577
|
+
ctx.save();
|
|
3578
|
+
ctx.fillStyle = isFocus ? 'rgba(31,42,55,0.96)' : 'rgba(31,42,55,0.76)';
|
|
3579
|
+
ctx.font = isFocus ? '11px -apple-system,sans-serif' : '10px -apple-system,sans-serif';
|
|
3580
|
+
ctx.textAlign = 'center';
|
|
3581
|
+
ctx.shadowColor = 'rgba(255,255,255,0.90)';
|
|
3582
|
+
ctx.shadowBlur = 7;
|
|
3583
|
+
let label = (pos.doc.title || pos.id).replace(/[_-]/g, ' ');
|
|
3584
|
+
if (label.length > 28) label = label.substring(0, 26) + '...';
|
|
3585
|
+
ctx.fillText(label, pos.x, pos.y - pos.radius - 7);
|
|
3586
|
+
ctx.restore();
|
|
3587
|
+
}
|
|
3588
|
+
});
|
|
3589
|
+
notifyEmbeddedAtlasReady();
|
|
3590
|
+
}
|
|
3591
|
+
function getGraphCanvasPoint(event) {
|
|
3592
|
+
const canvas = document.getElementById('graph-canvas');
|
|
3593
|
+
const rect = canvas.getBoundingClientRect();
|
|
3594
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
function hitTestGraph(point) {
|
|
3598
|
+
for (const pos of graphState.renderedPositions) {
|
|
3599
|
+
const dx = point.x - pos.x;
|
|
3600
|
+
const dy = point.y - pos.y;
|
|
3601
|
+
if (dx * dx + dy * dy < (pos.radius + 8) * (pos.radius + 8)) {
|
|
3602
|
+
return pos.id;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
return null;
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
function startGraphAnimation() {
|
|
3609
|
+
if (graphState.animationHandle) return;
|
|
3610
|
+
const tick = (ts) => {
|
|
3611
|
+
if (!document.getElementById('panel-graph').classList.contains('active')) {
|
|
3612
|
+
graphState.animationHandle = null;
|
|
3613
|
+
graphState.lastFrameTs = null;
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3616
|
+
if (graphState.lastFrameTs === null) {
|
|
3617
|
+
graphState.lastFrameTs = ts;
|
|
3618
|
+
drawGraph();
|
|
3619
|
+
}
|
|
3620
|
+
const delta = ts - graphState.lastFrameTs;
|
|
3621
|
+
if (delta >= GRAPH_FRAME_MS) {
|
|
3622
|
+
graphState.motionTime += delta;
|
|
3623
|
+
graphState.lastFrameTs = ts;
|
|
3624
|
+
drawGraph();
|
|
3625
|
+
}
|
|
3626
|
+
graphState.animationHandle = requestAnimationFrame(tick);
|
|
3627
|
+
};
|
|
3628
|
+
graphState.animationHandle = requestAnimationFrame(tick);
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
function stopGraphAnimation() {
|
|
3632
|
+
if (graphState.animationHandle) {
|
|
3633
|
+
cancelAnimationFrame(graphState.animationHandle);
|
|
3634
|
+
graphState.animationHandle = null;
|
|
3635
|
+
}
|
|
3636
|
+
graphState.lastFrameTs = null;
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
(function initGraphControls() {
|
|
3640
|
+
const canvas = document.getElementById('graph-canvas');
|
|
3641
|
+
document.getElementById('graph-search').addEventListener('input', event => {
|
|
3642
|
+
graphState.toolbarMinimized = false;
|
|
3643
|
+
syncGraphToolbar();
|
|
3644
|
+
renderGraphSearchResults(event.target.value);
|
|
3645
|
+
});
|
|
3646
|
+
document.getElementById('graph-group-family').onclick = () => {
|
|
3647
|
+
graphState.groupMode = 'family';
|
|
3648
|
+
graphState.layoutKey = null;
|
|
3649
|
+
graphState.manualViewport = false;
|
|
3650
|
+
graphState.fitVisibleRequested = true;
|
|
3651
|
+
document.getElementById('graph-group-family').classList.add('active');
|
|
3652
|
+
document.getElementById('graph-group-folder').classList.remove('active');
|
|
3653
|
+
drawGraph();
|
|
3654
|
+
};
|
|
3655
|
+
document.getElementById('graph-group-folder').onclick = () => {
|
|
3656
|
+
graphState.groupMode = 'folder';
|
|
3657
|
+
graphState.layoutKey = null;
|
|
3658
|
+
graphState.manualViewport = false;
|
|
3659
|
+
graphState.fitVisibleRequested = true;
|
|
3660
|
+
document.getElementById('graph-group-folder').classList.add('active');
|
|
3661
|
+
document.getElementById('graph-group-family').classList.remove('active');
|
|
3662
|
+
drawGraph();
|
|
3663
|
+
};
|
|
3664
|
+
document.getElementById('graph-clear-focus').onclick = () => {
|
|
3665
|
+
resetGraphViewport();
|
|
3666
|
+
clearGraphSelection(true);
|
|
3667
|
+
};
|
|
3668
|
+
document.getElementById('graph-zoom-in').onclick = () => {
|
|
3669
|
+
setManualViewport();
|
|
3670
|
+
graphState.targetZoom = Math.min(2.6, graphState.targetZoom * 1.14);
|
|
3671
|
+
drawGraph();
|
|
3672
|
+
};
|
|
3673
|
+
document.getElementById('graph-zoom-out').onclick = () => {
|
|
3674
|
+
setManualViewport();
|
|
3675
|
+
graphState.targetZoom = Math.max(0.65, graphState.targetZoom * 0.88);
|
|
3676
|
+
drawGraph();
|
|
3677
|
+
};
|
|
3678
|
+
document.getElementById('graph-reset-view').onclick = () => {
|
|
3679
|
+
resetGraphViewport();
|
|
3680
|
+
drawGraph();
|
|
3681
|
+
};
|
|
3682
|
+
document.getElementById('graph-fit-visible').onclick = () => {
|
|
3683
|
+
fitVisibleGraph();
|
|
3684
|
+
};
|
|
3685
|
+
canvas.onmousedown = event => {
|
|
3686
|
+
const point = getGraphCanvasPoint(event);
|
|
3687
|
+
if (hitTestGraph(point)) return;
|
|
3688
|
+
graphState.isDragging = true;
|
|
3689
|
+
graphState.didDrag = false;
|
|
3690
|
+
graphState.dragLastPoint = point;
|
|
3691
|
+
canvas.classList.add('dragging');
|
|
3692
|
+
setManualViewport();
|
|
3693
|
+
};
|
|
3694
|
+
canvas.onclick = event => {
|
|
3695
|
+
if (graphState.didDrag) {
|
|
3696
|
+
graphState.didDrag = false;
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
const targetId = hitTestGraph(getGraphCanvasPoint(event));
|
|
3700
|
+
if (targetId) {
|
|
3701
|
+
lockGraphNode(targetId);
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
clearGraphSelection();
|
|
3705
|
+
};
|
|
3706
|
+
canvas.onmousemove = event => {
|
|
3707
|
+
const point = getGraphCanvasPoint(event);
|
|
3708
|
+
if (graphState.isDragging && graphState.dragLastPoint) {
|
|
3709
|
+
const dx = point.x - graphState.dragLastPoint.x;
|
|
3710
|
+
const dy = point.y - graphState.dragLastPoint.y;
|
|
3711
|
+
if (Math.abs(dx) > 1 || Math.abs(dy) > 1) graphState.didDrag = true;
|
|
3712
|
+
graphState.panX += dx;
|
|
3713
|
+
graphState.panY += dy;
|
|
3714
|
+
graphState.targetPanX = graphState.panX;
|
|
3715
|
+
graphState.targetPanY = graphState.panY;
|
|
3716
|
+
graphState.dragLastPoint = point;
|
|
3717
|
+
drawGraph();
|
|
3718
|
+
return;
|
|
3719
|
+
}
|
|
3720
|
+
if (graphState.selectedId) return;
|
|
3721
|
+
const hovered = hitTestGraph(point);
|
|
3722
|
+
if (hovered !== graphState.hoveredId) {
|
|
3723
|
+
graphState.hoveredId = hovered;
|
|
3724
|
+
renderGraphDetail();
|
|
3725
|
+
drawGraph();
|
|
3726
|
+
}
|
|
3727
|
+
};
|
|
3728
|
+
canvas.onmouseup = () => {
|
|
3729
|
+
graphState.isDragging = false;
|
|
3730
|
+
graphState.dragLastPoint = null;
|
|
3731
|
+
canvas.classList.remove('dragging');
|
|
3732
|
+
};
|
|
3733
|
+
canvas.onmouseleave = () => {
|
|
3734
|
+
graphState.isDragging = false;
|
|
3735
|
+
graphState.dragLastPoint = null;
|
|
3736
|
+
canvas.classList.remove('dragging');
|
|
3737
|
+
if (!graphState.selectedId && graphState.hoveredId) {
|
|
3738
|
+
graphState.hoveredId = null;
|
|
3739
|
+
renderGraphDetail();
|
|
3740
|
+
drawGraph();
|
|
3741
|
+
}
|
|
3742
|
+
};
|
|
3743
|
+
canvas.onwheel = event => {
|
|
3744
|
+
event.preventDefault();
|
|
3745
|
+
setManualViewport();
|
|
3746
|
+
const factor = event.deltaY < 0 ? 1.12 : 0.89;
|
|
3747
|
+
graphState.targetZoom = Math.max(0.65, Math.min(2.6, graphState.targetZoom * factor));
|
|
3748
|
+
drawGraph();
|
|
3749
|
+
};
|
|
3750
|
+
window.addEventListener('mouseup', () => {
|
|
3751
|
+
graphState.isDragging = false;
|
|
3752
|
+
graphState.dragLastPoint = null;
|
|
3753
|
+
canvas.classList.remove('dragging');
|
|
3754
|
+
});
|
|
3755
|
+
document.getElementById('graph-search').addEventListener('keydown', event => {
|
|
3756
|
+
if (event.key === 'Escape') {
|
|
3757
|
+
closeGraphSearchResults(true);
|
|
3758
|
+
return;
|
|
3759
|
+
}
|
|
3760
|
+
if (event.key === 'ArrowDown') {
|
|
3761
|
+
event.preventDefault();
|
|
3762
|
+
moveGraphSearchSelection(1);
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
if (event.key === 'ArrowUp') {
|
|
3766
|
+
event.preventDefault();
|
|
3767
|
+
moveGraphSearchSelection(-1);
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
if (event.key === 'Enter') {
|
|
3771
|
+
event.preventDefault();
|
|
3772
|
+
commitGraphSearchSelection();
|
|
3773
|
+
}
|
|
3774
|
+
});
|
|
3775
|
+
window.addEventListener('resize', () => {
|
|
3776
|
+
graphState.layoutKey = null;
|
|
3777
|
+
if (document.getElementById('panel-graph').classList.contains('active')) {
|
|
3778
|
+
drawGraph();
|
|
3779
|
+
syncGraphOverlayPositions();
|
|
3780
|
+
renderGraphFamilyFilters();
|
|
3781
|
+
}
|
|
3782
|
+
});
|
|
3783
|
+
buildGraphLegend(getVisibleDocs());
|
|
3784
|
+
renderGraphFamilyFilters();
|
|
3785
|
+
renderGraphEdgeFilters();
|
|
3786
|
+
renderGraphScope();
|
|
3787
|
+
syncGraphToolbar();
|
|
3788
|
+
renderGraphDetail();
|
|
3789
|
+
syncGraphSettings();
|
|
3790
|
+
updateZoomBadge();
|
|
3791
|
+
syncWikiAskPosition();
|
|
3792
|
+
syncGraphOverlayPositions();
|
|
3793
|
+
})();
|
|
3794
|
+
</script>
|
|
3795
|
+
</body>
|
|
3796
|
+
</html>
|