athena129 commited on
Commit
c3caf12
·
1 Parent(s): 8c64948

Initial static frontend — calls athena129/cybersecqwen-demo via @gradio/client

Browse files
Files changed (2) hide show
  1. README.md +13 -5
  2. index.html +1030 -17
README.md CHANGED
@@ -1,10 +1,18 @@
1
  ---
2
- title: Cybersecqwen Chat
3
- emoji: 🏃
4
- colorFrom: pink
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
1
  ---
2
+ title: CyberSecQwen Chat
3
+ emoji: "\U0001F6E1"
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: static
7
  pinned: false
8
+ license: apache-2.0
9
+ short_description: Polished chat UI for CyberSecQwen-4B
10
  ---
11
 
12
+ # CyberSecQwen Chat
13
+
14
+ Static frontend that calls the [`athena129/cybersecqwen-demo`](https://huggingface.co/spaces/athena129/cybersecqwen-demo) Gradio Space (ZeroGPU backend) via `@gradio/client`.
15
+
16
+ - Single-file HTML, vanilla JS, no build step
17
+ - Connects to the deployed Gradio backend's `/chat` endpoint
18
+ - Streaming token-by-token via Gradio's submit job iterator
index.html CHANGED
@@ -1,19 +1,1032 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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" />
6
+ <title>CyberSecQwen-4B</title>
7
+
8
+ <!--
9
+ CyberSecQwen-4B · single-file frontend
10
+ ──────────────────────────────────────
11
+ Two views:
12
+ #/ Chat — vertically centered hero docks composer after first message
13
+ #/about About — long-form scrollable page with sticky sub-nav
14
+
15
+ Backend hook: askModel(prompt) — swap its body for fetch('/infer', …).
16
+ Vanilla JS only. No build step.
17
+ -->
18
+
19
+ <link rel="preconnect" href="https://fonts.googleapis.com">
20
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
21
+ <link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet">
22
+
23
+ <style>
24
+ /* ── DESIGN TOKENS ─────────────────────────────────────── */
25
+ :root{
26
+ --bg: #08080a;
27
+ --surface: #0e0e11;
28
+ --surface-2: #16161a;
29
+ --border: #232326;
30
+ --border-soft: #1a1a1d;
31
+
32
+ --fg: #fafafa;
33
+ --fg-2: #e4e4e7;
34
+ --fg-3: #a1a1aa;
35
+ --fg-4: #71717a;
36
+ --fg-5: #52525b;
37
+
38
+ --accent: #22d3ee;
39
+ --accent-soft: rgba(34,211,238,0.08);
40
+ --accent-border: rgba(34,211,238,0.28);
41
+ --accent-glow: rgba(34,211,238,0.18);
42
+
43
+ --font-sans: 'Geist', ui-sans-serif, system-ui, -apple-system, sans-serif;
44
+ --font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
45
+ --font-serif: 'Instrument Serif', ui-serif, Georgia, serif;
46
+
47
+ --ease-out: cubic-bezier(.22,.61,.36,1);
48
+ }
49
+
50
+ *,*::before,*::after{box-sizing:border-box}
51
+ html,body{height:100%}
52
+ body{
53
+ margin:0;
54
+ color:var(--fg-2);
55
+ background: var(--bg);
56
+ font-family:var(--font-sans);
57
+ font-size:14px;
58
+ line-height:1.5;
59
+ -webkit-font-smoothing:antialiased;
60
+ text-rendering:optimizeLegibility;
61
+ overflow:hidden;
62
+ }
63
+ ::selection{background:var(--accent-soft);color:#fff}
64
+ a{color:inherit;text-decoration:none}
65
+ button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
66
+
67
+ /* Atmospheric background — two cyan radial meshes + grain */
68
+ .bg-mesh{
69
+ position:fixed;inset:0;pointer-events:none;z-index:0;
70
+ background:
71
+ radial-gradient(900px 540px at 92% -8%, rgba(34,211,238,.16), transparent 60%),
72
+ radial-gradient(620px 420px at 6% 108%, rgba(34,211,238,.10), transparent 60%);
73
+ }
74
+ .bg-grain{
75
+ position:fixed;inset:0;pointer-events:none;z-index:1;
76
+ opacity:.035;mix-blend-mode:overlay;
77
+ background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
78
+ }
79
+
80
+ /* ── APP SHELL ─────────────────────────────────────────── */
81
+ .app{
82
+ position:relative;z-index:2;
83
+ display:grid;
84
+ grid-template-columns: 240px 1fr;
85
+ height:100vh;
86
+ }
87
+
88
+ /* Sidebar */
89
+ .sidebar{
90
+ border-right:1px solid var(--border-soft);
91
+ background: rgba(14,14,17,.6);
92
+ backdrop-filter: blur(8px);
93
+ display:flex;flex-direction:column;
94
+ padding:18px 14px;
95
+ min-height:0;
96
+ }
97
+ .brand{display:flex;align-items:center;gap:10px;padding:4px 6px 18px}
98
+ .brand-mark{
99
+ width:30px;height:30px;border-radius:8px;
100
+ border:1px solid var(--accent-border);
101
+ display:grid;place-items:center;
102
+ font-family:var(--font-mono);font-weight:600;font-size:14px;color:var(--accent);
103
+ background:var(--accent-soft);
104
+ }
105
+ .brand-name{font-weight:600;letter-spacing:-.005em;color:var(--fg);font-size:14px}
106
+ .brand-tag{font-family:var(--font-mono);font-size:10.5px;color:var(--fg-4);letter-spacing:.02em}
107
+
108
+ .nav{display:flex;flex-direction:column;gap:2px;margin-top:6px}
109
+ .nav a{
110
+ display:flex;align-items:center;gap:10px;
111
+ padding:8px 10px;border-radius:8px;color:var(--fg-3);font-size:13.5px;
112
+ transition:background .15s, color .15s;
113
+ }
114
+ .nav a:hover{color:var(--fg-2)}
115
+ .nav a.active{background:var(--surface-2);color:var(--fg)}
116
+ .nav a svg{width:15px;height:15px;flex:none;opacity:.85}
117
+
118
+ .side-foot{
119
+ margin-top:auto;display:flex;flex-direction:column;gap:4px;
120
+ padding-top:14px;border-top:1px solid var(--border-soft);
121
+ }
122
+ .side-foot a{
123
+ padding:6px 10px;border-radius:6px;color:var(--fg-4);font-size:12.5px;
124
+ display:flex;align-items:center;gap:8px;
125
+ }
126
+ .side-foot a:hover{color:var(--fg-2);background:var(--surface)}
127
+ .side-foot a svg{width:13px;height:13px;opacity:.8}
128
+ .status-pill{
129
+ margin-top:6px;align-self:flex-start;
130
+ padding:3px 9px;border:1px solid var(--border);border-radius:999px;
131
+ font-family:var(--font-mono);font-size:10.5px;color:var(--fg-4);
132
+ display:inline-flex;align-items:center;gap:6px;
133
+ }
134
+ .status-dot{width:5px;height:5px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent)}
135
+
136
+ /* Main */
137
+ .main{display:flex;flex-direction:column;min-width:0;min-height:0}
138
+ .topbar{
139
+ height:56px;flex:none;
140
+ padding:0 28px;border-bottom:1px solid var(--border-soft);
141
+ display:flex;align-items:center;justify-content:space-between;
142
+ background:rgba(8,8,10,.5);backdrop-filter:blur(8px);
143
+ }
144
+ .topbar h1.section-title{
145
+ margin:0;font-size:13.5px;font-weight:500;color:var(--fg-2);letter-spacing:.005em;
146
+ }
147
+ .top-status{
148
+ font-family:var(--font-mono);font-size:11px;color:var(--fg-4);
149
+ padding:4px 10px;border:1px solid var(--border);border-radius:999px;
150
+ display:inline-flex;align-items:center;gap:7px;
151
+ }
152
+
153
+ .view{flex:1;min-height:0;overflow:auto;position:relative}
154
+ .view.no-scroll{overflow:hidden;display:flex;flex-direction:column}
155
+ .view.no-scroll > .chat-active{flex:1;min-height:0}
156
+
157
+ /* ── CHAT VIEW ─────────────────────────────────────────── */
158
+ .chat-empty{
159
+ min-height:100%;
160
+ display:flex;flex-direction:column;align-items:center;justify-content:center;
161
+ padding:7vh 28px 12vh;
162
+ gap:24px;
163
+ }
164
+ .hero-icon{
165
+ color:var(--accent);
166
+ width:36px;height:36px;
167
+ display:grid;place-items:center;
168
+ animation: fadeUp .6s var(--ease-out) both;
169
+ }
170
+ .hero-h1{
171
+ margin:0;
172
+ font-family:var(--font-serif);font-weight:400;
173
+ font-size:clamp(40px, 6vw, 56px);
174
+ line-height:1.05;letter-spacing:-.01em;color:var(--fg);
175
+ text-align:center;
176
+ animation: fadeUp .6s var(--ease-out) both;animation-delay:.08s;
177
+ }
178
+ .hero-h1 em{font-style:italic;color:var(--accent);font-family:var(--font-serif)}
179
+ .hero-sub{
180
+ margin:0;font-size:15px;color:var(--fg-4);max-width:54ch;text-align:center;
181
+ animation: fadeUp .6s var(--ease-out) both;animation-delay:.16s;
182
+ }
183
+
184
+ .composer{
185
+ width:100%;max-width:640px;
186
+ background:var(--surface);
187
+ border:1px solid var(--border);border-radius:14px;
188
+ padding:14px 14px 10px;
189
+ display:flex;flex-direction:column;gap:10px;
190
+ transition:border-color .2s var(--ease-out), box-shadow .2s var(--ease-out);
191
+ animation: fadeUp .6s var(--ease-out) both;animation-delay:.24s;
192
+ }
193
+ .composer:focus-within{
194
+ border-color:var(--accent-border);
195
+ box-shadow: 0 0 0 4px var(--accent-soft), 0 0 32px var(--accent-glow);
196
+ }
197
+ .composer textarea{
198
+ width:100%;min-height:24px;max-height:240px;
199
+ background:transparent;color:var(--fg);
200
+ border:0;outline:none;resize:none;
201
+ font:inherit;font-size:15px;line-height:1.5;
202
+ }
203
+ .composer textarea::placeholder{color:var(--fg-5)}
204
+ .composer-row{display:flex;align-items:center;justify-content:space-between;gap:10px}
205
+ .send-hint{font-family:var(--font-mono);font-size:10.5px;color:var(--fg-5)}
206
+ .send-btn{
207
+ display:inline-flex;align-items:center;gap:7px;
208
+ padding:8px 14px;border-radius:8px;
209
+ background:var(--accent);color:#06181c;font-weight:600;font-size:13px;
210
+ transition:transform .2s var(--ease-out), filter .15s, box-shadow .2s var(--ease-out);
211
+ box-shadow: 0 0 0 1px var(--accent-border), 0 0 24px var(--accent-glow);
212
+ }
213
+ .send-btn:hover:not(:disabled){transform:translateY(-1px);filter:brightness(1.05)}
214
+ .send-btn:disabled{opacity:.4;cursor:not-allowed;box-shadow:none}
215
+
216
+ .chips{
217
+ display:flex;flex-wrap:wrap;gap:8px;justify-content:center;
218
+ max-width:640px;
219
+ animation: fadeUp .6s var(--ease-out) both;animation-delay:.32s;
220
+ }
221
+ .chip{
222
+ padding:6px 12px;border:1px solid var(--border);border-radius:999px;
223
+ font-size:12.5px;color:var(--fg-3);
224
+ display:inline-flex;align-items:center;gap:7px;
225
+ transition:transform .2s var(--ease-out), border-color .15s, color .15s;
226
+ }
227
+ .chip .kind{font-family:var(--font-mono);font-size:10.5px;color:var(--accent)}
228
+ .chip:hover{transform:translateY(-1px);border-color:var(--accent-border);color:var(--fg-2)}
229
+
230
+ /* Active conversation layout */
231
+ .chat-active{
232
+ display:flex;flex-direction:column;
233
+ width:100%;min-height:0;
234
+ }
235
+ .thread{
236
+ flex:1;min-height:0;overflow:auto;
237
+ padding: 28px 28px 32px;
238
+ display:flex;flex-direction:column;align-items:center;
239
+ scroll-behavior:smooth;
240
+ }
241
+ .thread-inner{width:100%;max-width:720px;display:flex;flex-direction:column;gap:18px}
242
+
243
+ .msg{display:flex;flex-direction:column;gap:6px;max-width:100%}
244
+ .msg .who{
245
+ font-family:var(--font-mono);font-size:10.5px;color:var(--fg-5);
246
+ letter-spacing:.04em;text-transform:uppercase;
247
+ }
248
+ .msg.user .bubble{
249
+ align-self:flex-end;max-width:80%;
250
+ background:var(--surface-2);border:1px solid var(--border);
251
+ color:var(--fg);
252
+ padding:10px 14px;
253
+ border-radius:12px 12px 4px 12px;
254
+ white-space:pre-wrap;
255
+ font-size:14.5px;
256
+ }
257
+ .msg.user .who{align-self:flex-end}
258
+ .msg.bot .body{
259
+ color:var(--fg-2);font-size:14.5px;line-height:1.65;
260
+ white-space:pre-wrap;
261
+ }
262
+ .msg.bot .body strong{color:var(--fg)}
263
+ .msg.bot .body code{
264
+ font-family:var(--font-mono);font-size:13px;
265
+ background:var(--surface);border:1px solid var(--border-soft);
266
+ padding:1px 6px;border-radius:5px;color:var(--fg-2);
267
+ }
268
+
269
+ .typing{display:inline-flex;gap:5px;padding:8px 0}
270
+ .typing i{
271
+ width:5px;height:5px;border-radius:50%;background:var(--fg-5);
272
+ animation:blink 1.2s infinite ease-in-out;
273
+ }
274
+ .typing i:nth-child(2){animation-delay:.15s}
275
+ .typing i:nth-child(3){animation-delay:.30s}
276
+ @keyframes blink{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-2px)}}
277
+
278
+ /* Docked composer */
279
+ .dock{
280
+ flex:none;
281
+ padding:14px 28px 22px;
282
+ background:linear-gradient(to bottom, transparent, rgba(8,8,10,.85) 22%, var(--bg) 60%);
283
+ display:flex;justify-content:center;
284
+ margin-top:-32px;
285
+ position:relative;z-index:2;
286
+ pointer-events:none;
287
+ }
288
+ .dock > .dock-inner{
289
+ pointer-events:auto;width:100%;max-width:720px;
290
+ display:flex;align-items:flex-end;gap:10px;
291
+ }
292
+ .dock .composer{margin:0;flex:1;animation:none}
293
+ .new-chat{
294
+ flex:none;height:46px;padding:0 14px;
295
+ border:1px solid var(--border);border-radius:10px;
296
+ color:var(--fg-3);font-size:13px;
297
+ display:inline-flex;align-items:center;gap:7px;
298
+ transition:border-color .15s, color .15s;
299
+ }
300
+ .new-chat:hover{color:var(--fg);border-color:var(--accent-border)}
301
+
302
+ /* ── ABOUT VIEW ────────────────────────────────────────── */
303
+ .about{padding:0}
304
+ .about-inner{max-width:880px;margin:0 auto;padding:48px 32px 120px}
305
+ .subnav{
306
+ position:sticky;top:0;z-index:5;
307
+ background:rgba(8,8,10,.78);backdrop-filter:blur(10px);
308
+ border-bottom:1px solid var(--border-soft);
309
+ padding:12px 32px;
310
+ display:flex;justify-content:center;
311
+ }
312
+ .subnav-inner{display:flex;gap:6px;flex-wrap:wrap;max-width:880px;width:100%}
313
+ .subnav a{
314
+ font-family:var(--font-mono);font-size:11.5px;color:var(--fg-4);
315
+ padding:5px 10px;border-radius:6px;letter-spacing:.02em;
316
+ transition:color .15s, background .15s;
317
+ }
318
+ .subnav a:hover{color:var(--fg-2);background:var(--surface)}
319
+ .subnav a.is-current{color:var(--accent);background:var(--accent-soft)}
320
+
321
+ .about h2{
322
+ font-family:var(--font-serif);font-weight:400;
323
+ font-size:36px;line-height:1.1;letter-spacing:-.01em;color:var(--fg);
324
+ margin:0 0 14px;
325
+ }
326
+ .about h3{
327
+ font-size:13px;font-weight:600;color:var(--fg-4);
328
+ text-transform:uppercase;letter-spacing:.12em;
329
+ margin:0 0 18px;
330
+ }
331
+ .about p{color:var(--fg-3);font-size:15px;line-height:1.7;margin:0 0 14px;max-width:64ch}
332
+ .about p strong{color:var(--fg-2)}
333
+ .about section{padding:48px 0;border-top:1px solid var(--border-soft)}
334
+ .about section:first-of-type{border-top:0;padding-top:24px}
335
+ .tagline{
336
+ font-family:var(--font-serif);font-style:italic;font-weight:400;
337
+ font-size:28px;line-height:1.25;color:var(--fg);
338
+ border-left:1px solid var(--accent-border);
339
+ padding:6px 0 6px 18px;margin:18px 0 0;max-width:42ch;
340
+ }
341
+ .tagline em{font-style:italic;color:var(--accent)}
342
+
343
+ .stat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:22px 0 28px}
344
+ .stat{
345
+ border:1px solid var(--border);border-radius:12px;
346
+ background:var(--surface);padding:18px;
347
+ display:flex;flex-direction:column;gap:6px;
348
+ }
349
+ .stat .lbl{font-family:var(--font-mono);font-size:10.5px;color:var(--fg-5);text-transform:uppercase;letter-spacing:.08em}
350
+ .stat .num{
351
+ font-family:var(--font-serif);font-size:38px;line-height:1;color:var(--fg);
352
+ font-feature-settings:"tnum";letter-spacing:-.01em;
353
+ }
354
+ .stat.accent .num{color:var(--accent)}
355
+ .stat .sub{font-family:var(--font-mono);font-size:11.5px;color:var(--fg-4)}
356
+
357
+ table.cmp{
358
+ width:100%;border-collapse:collapse;font-size:13px;
359
+ border:1px solid var(--border);border-radius:12px;overflow:hidden;
360
+ background:var(--surface);
361
+ }
362
+ table.cmp th, table.cmp td{
363
+ padding:10px 14px;border-bottom:1px solid var(--border-soft);text-align:left;
364
+ font-variant-numeric:tabular-nums;
365
+ }
366
+ table.cmp th{
367
+ font-family:var(--font-mono);font-size:10.5px;letter-spacing:.06em;
368
+ text-transform:uppercase;color:var(--fg-5);font-weight:500;
369
+ background:var(--surface-2);
370
+ }
371
+ table.cmp td{color:var(--fg-3)}
372
+ table.cmp td.model{color:var(--fg-2);font-weight:500}
373
+ table.cmp tr.us td{background:var(--accent-soft);color:var(--fg)}
374
+ table.cmp tr.us td.model{color:var(--accent);font-weight:600}
375
+ table.cmp tr:last-child td{border-bottom:0}
376
+
377
+ .lift-callout{
378
+ margin-top:22px;padding:16px 18px;border:1px solid var(--accent-border);
379
+ border-radius:12px;background:var(--accent-soft);
380
+ display:flex;align-items:center;gap:18px;flex-wrap:wrap;
381
+ }
382
+ .lift-callout .lift{
383
+ font-family:var(--font-serif);font-size:32px;line-height:1;color:var(--accent);
384
+ }
385
+ .lift-callout .desc{color:var(--fg-2);font-size:14px}
386
+ .lift-callout .desc small{display:block;color:var(--fg-4);font-family:var(--font-mono);font-size:11px;margin-top:4px}
387
+
388
+ .card-row{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:18px 0 26px}
389
+ .card{
390
+ border:1px solid var(--border);border-radius:12px;background:var(--surface);
391
+ padding:18px;display:flex;flex-direction:column;gap:8px;
392
+ }
393
+ .card .pill{
394
+ font-family:var(--font-mono);font-size:10.5px;color:var(--accent);
395
+ text-transform:uppercase;letter-spacing:.08em;
396
+ }
397
+ .card h4{margin:0;color:var(--fg);font-size:15px;font-weight:600;letter-spacing:-.005em}
398
+ .card p{margin:0;color:var(--fg-3);font-size:13.5px;line-height:1.6}
399
+
400
+ .spec{
401
+ display:grid;grid-template-columns:auto 1fr;gap:0;
402
+ border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--surface);
403
+ margin:14px 0;
404
+ }
405
+ .spec dt, .spec dd{padding:9px 14px;border-bottom:1px solid var(--border-soft)}
406
+ .spec dt{
407
+ font-family:var(--font-mono);font-size:11.5px;color:var(--fg-4);
408
+ background:var(--surface-2);border-right:1px solid var(--border-soft);
409
+ text-transform:uppercase;letter-spacing:.04em;
410
+ }
411
+ .spec dd{margin:0;color:var(--fg-2);font-size:13.5px;font-family:var(--font-mono)}
412
+ .spec dt:nth-last-of-type(1), .spec dd:nth-last-of-type(1){border-bottom:0}
413
+
414
+ pre.code{
415
+ margin:14px 0;background:var(--surface);border:1px solid var(--border-soft);
416
+ border-radius:10px;padding:14px 16px;
417
+ font-family:var(--font-mono);font-size:12.5px;color:var(--fg-2);
418
+ overflow:auto;line-height:1.55;
419
+ }
420
+ pre.code .c{color:var(--fg-5)}
421
+ pre.code .k{color:var(--accent)}
422
+
423
+ ul.bullets{padding-left:18px;color:var(--fg-3);font-size:14.5px;line-height:1.7;margin:0 0 14px}
424
+ ul.bullets li{margin:4px 0}
425
+ ul.bullets li strong{color:var(--fg-2)}
426
+
427
+ .twocol{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:18px 0}
428
+
429
+ /* Scrollbar */
430
+ *::-webkit-scrollbar{width:10px;height:10px}
431
+ *::-webkit-scrollbar-track{background:transparent}
432
+ *::-webkit-scrollbar-thumb{background:#1d1d20;border-radius:99px;border:2px solid transparent;background-clip:content-box}
433
+ *::-webkit-scrollbar-thumb:hover{background:#2a2a2e;background-clip:content-box;border:2px solid transparent}
434
+
435
+ @keyframes fadeUp{
436
+ from{opacity:0;transform:translateY(8px)}
437
+ to {opacity:1;transform:translateY(0)}
438
+ }
439
+
440
+ /* Responsive */
441
+ @media (max-width: 860px){
442
+ .app{grid-template-columns: 1fr}
443
+ .sidebar{display:none}
444
+ .stat-grid, .card-row, .twocol{grid-template-columns:1fr}
445
+ }
446
+ </style>
447
+ </head>
448
+
449
+ <body>
450
+ <div class="bg-mesh" aria-hidden="true"></div>
451
+ <div class="bg-grain" aria-hidden="true"></div>
452
+
453
+ <div class="app">
454
+ <!-- ── SIDEBAR ─────────────────────────────────────────── -->
455
+ <aside class="sidebar">
456
+ <div class="brand">
457
+ <div class="brand-mark">C</div>
458
+ <div>
459
+ <div class="brand-name">CyberSecQwen</div>
460
+ <div class="brand-tag">4B · CTI</div>
461
+ </div>
462
+ </div>
463
+
464
+ <nav class="nav" id="nav">
465
+ <a href="#/" data-route="/" class="active">
466
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
467
+ Chat
468
+ </a>
469
+ <a href="#/about" data-route="/about">
470
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8h.01M11 12h1v5h1"/></svg>
471
+ About
472
+ </a>
473
+ </nav>
474
+
475
+ <div class="side-foot">
476
+ <a href="https://huggingface.co/athena129/CyberSecQwen-4B" target="_blank" rel="noopener">
477
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8"/></svg>
478
+ Model card
479
+ </a>
480
+ <a href="https://github.com/GPT-64590/CyberSecQwen-4B" target="_blank" rel="noopener">
481
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-4 1.5-4-2-6-2"/><path d="M15 22v-4a3 3 0 0 0-1-2.3c3 0 6-2 6-5.5a4 4 0 0 0-1-2.8 4 4 0 0 0 0-2.8s-1 0-3 1.3a10 10 0 0 0-5 0C7.9 2.6 7 2.6 7 2.6a4 4 0 0 0 0 2.8 4 4 0 0 0-1 2.8C6 11.7 9 13.7 12 13.7a3 3 0 0 0-1 2.3v4"/></svg>
482
+ GitHub
483
+ </a>
484
+ <span class="status-pill"><span class="status-dot"></span> ZeroGPU · A100</span>
485
+ </div>
486
+ </aside>
487
+
488
+ <!-- ── MAIN ────────────────────────────────────────────── -->
489
+ <main class="main">
490
+ <header class="topbar">
491
+ <h1 class="section-title" id="topTitle">Chat</h1>
492
+ <span class="top-status">cybersecqwen-4b · temp 0.3</span>
493
+ </header>
494
+
495
+ <div class="view" id="view"><!-- routed content --></div>
496
+ </main>
497
+ </div>
498
+
499
+ <!-- ── CHAT VIEW (template) ─────────────────────────────── -->
500
+ <template id="tpl-chat-empty">
501
+ <div class="chat-empty">
502
+ <div class="hero-icon" aria-hidden="true">
503
+ <svg viewBox="0 0 24 24" width="36" height="36" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"/><path d="M9 12l2 2 4-4"/></svg>
504
+ </div>
505
+ <h2 class="hero-h1">Map a vulnerability<br/><em>to a CWE.</em></h2>
506
+ <p class="hero-sub">Paste a snippet, log line, or CVE. The model returns a Common Weakness Enumeration with a one-paragraph rationale.</p>
507
+
508
+ <form class="composer" id="composer-empty">
509
+ <textarea rows="1" id="ta-empty" placeholder="Describe the issue, paste code, or drop a CVE id…"></textarea>
510
+ <div class="composer-row">
511
+ <span class="send-hint">↵ to send · ⇧↵ for newline</span>
512
+ <button class="send-btn" type="submit" id="send-empty" disabled>
513
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l14-7-7 14-2-5-5-2z"/></svg>
514
+ Send
515
+ </button>
516
+ </div>
517
+ </form>
518
+
519
+ <div class="chips" id="chips">
520
+ <button class="chip" data-prompt="Explain CWE-120 (classic buffer overflow) with a minimal C example and the standard mitigation.">
521
+ <span class="kind">CWE</span> Buffer overflow
522
+ </button>
523
+ <button class="chip" data-prompt="What CWE underlies the MOVEit Transfer SQL injection chain (CVE-2023-34362)?">
524
+ <span class="kind">CVE</span> MOVEit
525
+ </button>
526
+ <button class="chip" data-prompt="Audit this nginx config snippet for security weaknesses:&#10;server { listen 80; root /var/www; autoindex on; location /api/ { proxy_pass http://127.0.0.1:8000; } }">
527
+ <span class="kind">CFG</span> nginx audit
528
+ </button>
529
+ <button class="chip" data-prompt="Log line: GET /static/../../../etc/passwd HTTP/1.1 200 — what weakness, and how to fix?">
530
+ <span class="kind">LOG</span> path traversal
531
+ </button>
532
+ </div>
533
+ </div>
534
+ </template>
535
+
536
+ <template id="tpl-chat-active">
537
+ <div class="chat-active">
538
+ <div class="thread" id="thread"><div class="thread-inner" id="thread-inner"></div></div>
539
+ <div class="dock">
540
+ <div class="dock-inner">
541
+ <form class="composer" id="composer-dock">
542
+ <textarea rows="1" id="ta-dock" placeholder="Continue the conversation…"></textarea>
543
+ <div class="composer-row">
544
+ <span class="send-hint">↵ to send · ⇧↵ for newline</span>
545
+ <button class="send-btn" type="submit" id="send-dock" disabled>
546
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l14-7-7 14-2-5-5-2z"/></svg>
547
+ Send
548
+ </button>
549
+ </div>
550
+ </form>
551
+ <button class="new-chat" id="newChat" type="button" title="Start a new chat">
552
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
553
+ New chat
554
+ </button>
555
+ </div>
556
+ </div>
557
+ </div>
558
+ </template>
559
+
560
+ <!-- ── ABOUT VIEW (template) ────────────────────────────── -->
561
+ <template id="tpl-about">
562
+ <div class="about">
563
+ <div class="subnav">
564
+ <div class="subnav-inner" id="subnav">
565
+ <a href="#performance">Performance</a>
566
+ <a href="#recipe">Recipe</a>
567
+ <a href="#hardware">Hardware</a>
568
+ <a href="#limitations">Limitations</a>
569
+ <a href="#companion">Companion</a>
570
+ <a href="#citation">Citation</a>
571
+ </div>
572
+ </div>
573
+
574
+ <div class="about-inner">
575
+
576
+ <section id="overview">
577
+ <h3>Overview</h3>
578
+ <h2>CyberSecQwen-4B</h2>
579
+ <p>A Qwen3-4B base fine-tuned for cyber threat intelligence — CWE classification, CVE-to-CWE mapping, and code-pattern reasoning. Trained on AMD MI300X with a recipe designed to preserve instruction-tuned behavior while shifting the model's distribution onto the CTI domain.</p>
580
+ <p class="tagline">Beating an 8B Cisco specialist <em>at half the size.</em></p>
581
+ </section>
582
+
583
+ <section id="performance">
584
+ <h3>Performance</h3>
585
+ <h2>CTI-Bench, n=5, temp 0.3</h2>
586
+ <p>Evaluated on the public CTI-Bench multi-trial protocol. Scores below are mean ± 1σ over five runs.</p>
587
+
588
+ <div class="stat-grid">
589
+ <div class="stat">
590
+ <span class="lbl">CTI-RCM</span>
591
+ <span class="num">0.6664</span>
592
+ <span class="sub">± 0.0023 · CWE root-cause mapping</span>
593
+ </div>
594
+ <div class="stat accent">
595
+ <span class="lbl">CTI-MCQ</span>
596
+ <span class="num">0.5868</span>
597
+ <span class="sub">± 0.0029 · +8.7 pp vs Cisco-Instruct-8B</span>
598
+ </div>
599
+ <div class="stat">
600
+ <span class="lbl">Param ratio</span>
601
+ <span class="num">2×</span>
602
+ <span class="sub">smaller than Cisco-Foundation-Sec-8B</span>
603
+ </div>
604
+ </div>
605
+
606
+ <table class="cmp">
607
+ <thead>
608
+ <tr><th>Model</th><th>Params</th><th>CTI-RCM</th><th>CTI-MCQ</th></tr>
609
+ </thead>
610
+ <tbody>
611
+ <tr><td class="model">Foundation-Sec-8B (base, 5-shot)</td><td>8B</td><td>0.7450</td><td>0.6552</td></tr>
612
+ <tr><td class="model">Cisco-Foundation-Sec-Instruct-8B</td><td>8B</td><td>0.6850</td><td>0.4996</td></tr>
613
+ <tr><td class="model">CyberPal-2.0-20B</td><td>20B</td><td>0.7280</td><td>0.7384</td></tr>
614
+ <tr class="us"><td class="model">CyberSecQwen-4B</td><td>4B</td><td>0.6664</td><td>0.5868</td></tr>
615
+ <tr><td class="model">Gemma4Defense-2B (companion)</td><td>2B</td><td>0.6754</td><td>0.6042</td></tr>
616
+ <tr><td class="model">Qwen3-4B-Instruct-2507 (raw)</td><td>4B</td><td>0.5190</td><td>0.4732</td></tr>
617
+ <tr><td class="model">Qwen3-4B-Base (5-shot)</td><td>4B</td><td>0.5170</td><td>0.6672</td></tr>
618
+ <tr><td class="model">Gemma-4-E2B-it (raw)</td><td>2B</td><td>0.5800</td><td>0.5780</td></tr>
619
+ </tbody>
620
+ </table>
621
+
622
+ <div class="lift-callout">
623
+ <span class="lift">+15.1 pp</span>
624
+ <div class="desc">RCM lift over our Qwen3-4B base
625
+ <small>and +12.0 pp on MCQ — same architecture, recipe alone</small>
626
+ </div>
627
+ </div>
628
+ </section>
629
+
630
+ <section id="recipe">
631
+ <h3>Recipe</h3>
632
+ <h2>What changed, in three ideas.</h2>
633
+
634
+ <div class="card-row">
635
+ <div class="card">
636
+ <span class="pill">01 · Decontamination</span>
637
+ <h4>Strict eval-set scrub</h4>
638
+ <p>Training corpus is filtered against CTI-Bench prompts and near-duplicates before any update sees a gradient. No leakage, no shortcutting.</p>
639
+ </div>
640
+ <div class="card">
641
+ <span class="pill">02 · IT-base preservation</span>
642
+ <h4>Keep the instruction-tuned voice</h4>
643
+ <p>Loss is balanced so the model adopts CTI domain knowledge without losing Qwen3's general instruction-following. Tone, formatting, and refusal behavior remain intact.</p>
644
+ </div>
645
+ <div class="card">
646
+ <span class="pill">03 · Recipe portability</span>
647
+ <h4>Same recipe, different base</h4>
648
+ <p>The same procedure applied to Gemma-4-E2B-it produces Gemma4Defense-2B with comparable lift — evidence the gains come from the recipe, not the base.</p>
649
+ </div>
650
+ </div>
651
+
652
+ <h3>Corpus · ~14,776 supervised records</h3>
653
+ <ul class="bullets">
654
+ <li><strong>rcm-2021 (decontaminated)</strong> — ~6,776 CVE → CWE classification examples from MITRE/NVD 2021 cohort, with all CTI-Bench overlap items removed.</li>
655
+ <li><strong>cve_cti synthetic Q&amp;A</strong> — ~8,000 defensive-analyst-style Q&amp;A pairs grounded in CVE descriptions.</li>
656
+ </ul>
657
+ <p>An earlier internal CPT corpus had <strong>72.3% test-set overlap</strong> with CTI-Bench. The released model trains exclusively on the 2021 cohort with overlap removed — released numbers are post-fix.</p>
658
+
659
+ <h3>Hyperparameters</h3>
660
+ <dl class="spec">
661
+ <dt>Base</dt> <dd>Qwen3-4B-Instruct-2507</dd>
662
+ <dt>Adapter</dt> <dd>LoRA r=64, α=64, dropout=0.05</dd>
663
+ <dt>Targets</dt> <dd>q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj</dd>
664
+ <dt>Optimizer</dt> <dd>AdamW · cosine schedule · warmup_ratio=0.05 · weight_decay=0.01</dd>
665
+ <dt>LR · peak</dt> <dd>5e-5</dd>
666
+ <dt>Batch · effective</dt><dd>2 per device · grad_accum 8 · effective batch 16</dd>
667
+ <dt>Schedule</dt> <dd>10 epochs · max_seq_len 4096</dd>
668
+ <dt>Precision</dt> <dd>bfloat16 throughout</dd>
669
+ <dt>Attention</dt> <dd>flash_attention_2 (FA2)</dd>
670
+ <dt>Wall · MI300X</dt> <dd>173 min · 1,290 steps · 7.85 s/step</dd>
671
+ <dt>Eval protocol</dt> <dd>Foundation-Sec arXiv:2504.21039 §B.3-B.4 · n=5 · temp 0.3</dd>
672
+ </dl>
673
+ </section>
674
+
675
+ <section id="hardware">
676
+ <h3>Hardware</h3>
677
+ <h2>Trained on AMD MI300X.</h2>
678
+ <p>The recipe was developed on a single-node MI300X stack. Optimizations are aimed at ROCm; the recipe ports cleanly to NVIDIA H100 / A100 with the FA2 caveat noted below.</p>
679
+
680
+ <h3>Stack</h3>
681
+ <dl class="spec">
682
+ <dt>Accelerator</dt> <dd>AMD Instinct MI300X · 192 GB HBM3 (gfx942)</dd>
683
+ <dt>Runtime</dt> <dd>ROCm 7.0</dd>
684
+ <dt>Docker image</dt> <dd>vllm/vllm-openai-rocm:latest</dd>
685
+ <dt>PyTorch</dt> <dd>2.6.0 (ROCm)</dd>
686
+ <dt>flash-attn</dt> <dd>2.8.3 (preinstalled in vLLM ROCm image)</dd>
687
+ <dt>vLLM</dt> <dd>0.10.1</dd>
688
+ </dl>
689
+
690
+ <h3>FA2 viability per model family</h3>
691
+ <p style="font-size:13.5px;color:var(--fg-4);margin:0 0 10px;">FA2 via the ROCm/flash-attention Composable-Kernels backend is supported on MI300X but bounded at <strong style="color:var(--fg-2)">head_dim ≤ 256</strong> by the LDS shared-memory budget.</p>
692
+ <table class="cmp">
693
+ <thead><tr><th>Family</th><th>head_dim</th><th>FA2</th><th>Note</th></tr></thead>
694
+ <tbody>
695
+ <tr class="us"><td class="model">Qwen3 (this work)</td><td>128</td><td>✓ enabled</td><td>fits LDS budget — ~1.6× faster than sdpa</td></tr>
696
+ <tr><td class="model">Llama-3 / Mistral</td><td>128</td><td>✓ works</td><td>same head_dim class</td></tr>
697
+ <tr><td class="model">Gemma-2</td><td>256</td><td>✓ boundary</td><td>at LDS limit, viable</td></tr>
698
+ <tr><td class="model">Gemma-4 (global layers)</td><td>512</td><td>✗ disabled</td><td>exceeds LDS — fallback to sdpa</td></tr>
699
+ </tbody>
700
+ </table>
701
+
702
+ <h3>Optimizations</h3>
703
+ <ul class="bullets">
704
+ <li><strong>FA2 enabled in training</strong> — Qwen3-4B head_dim=128 fits the gfx942 LDS budget; ~7.85 s/step at LoRA r=64 / max_seq_len=4096 — <strong>~1.6× faster</strong> than the same recipe on Gemma-4 (sdpa fallback).</li>
705
+ <li><strong>TRITON_ATTN backend for vLLM inference</strong> — recommended on MI300X for Qwen3-class models.</li>
706
+ <li><strong>bf16 throughout</strong> — native MI300X precision; no mixed-precision dance.</li>
707
+ <li><strong>AITER kernels for matmul</strong> — <code>VLLM_ROCM_USE_AITER=1</code> + <code>TORCH_BLAS_PREFER_HIPBLASLT=1</code>. Note: this works for Qwen3 dense — does NOT work for gpt-oss MoE (AITER=0 required there).</li>
708
+ <li><strong>Prefix caching</strong> — vLLM <code>--enable-prefix-caching</code> for shared system-prompt batches.</li>
709
+ <li><strong>HF Transfer for push/pull</strong> — <code>HF_HUB_ENABLE_HF_TRANSFER=1</code> saturates ~240 MB/s; 8 GB merged model uploads in ~36 s.</li>
710
+ </ul>
711
+
712
+ <h3>ROCm environment (verified-working)</h3>
713
+ <pre class="code"><span class="c"># env exported inside the vLLM ROCm Docker container</span>
714
+ <span class="k">export</span> VLLM_ROCM_USE_AITER=1
715
+ <span class="k">export</span> TORCH_BLAS_PREFER_HIPBLASLT=1
716
+ <span class="k">export</span> HF_HUB_DISABLE_XET=1
717
+ <span class="k">export</span> PYTORCH_ROCM_ARCH=<span class="c">'gfx90a;gfx942;gfx950'</span>
718
+ <span class="k">export</span> AITER_ROCM_ARCH=<span class="c">'gfx942;gfx950'</span>
719
+ <span class="k">export</span> HIP_FORCE_DEV_KERNARG=1</pre>
720
+
721
+ <p><strong>Portability:</strong> the recipe runs on NVIDIA A100 / H100 with the AMD env vars dropped (no-ops there). FA2 via <code>pip install flash-attn --no-build-isolation</code>. Same VRAM minimums: 24 GB+ for training, 12 GB+ for inference.</p>
722
+ </section>
723
+
724
+ <section id="limitations">
725
+ <h3>Limitations</h3>
726
+ <h2>What it's good at, and where it isn't.</h2>
727
+
728
+ <h3>In-distribution · verified strong</h3>
729
+ <div class="card-row">
730
+ <div class="card">
731
+ <span class="pill">Strong</span>
732
+ <h4>CWE classification</h4>
733
+ <p>Mapping a described weakness or code pattern to a CWE id with a short rationale. Calibrated on CTI-Bench RCM.</p>
734
+ </div>
735
+ <div class="card">
736
+ <span class="pill">Strong</span>
737
+ <h4>CVE-to-CWE on trained CVEs</h4>
738
+ <p>Returns the underlying CWE for CVEs whose descriptions are in-distribution. Confidence drops for CVEs disclosed after the corpus cutoff.</p>
739
+ </div>
740
+ <div class="card">
741
+ <span class="pill">Strong</span>
742
+ <h4>Code-pattern reasoning</h4>
743
+ <p>Identifying the weakness class behind a small snippet (string-format SQL, unchecked path joins, inline HTML rendering, etc.).</p>
744
+ </div>
745
+ </div>
746
+
747
+ <h3>Out-of-distribution · known failure modes</h3>
748
+ <div class="card-row">
749
+ <div class="card">
750
+ <span class="pill">Weak</span>
751
+ <h4>MITRE ATT&amp;CK technique IDs</h4>
752
+ <p>Wrong T-numbers (returned T1543/003 — Windows Service — for scheduled-task persistence, vs the correct T1053.005). Wrong technique names (called LSASS dumping "Extract Web Credentials"; correct is T1003.001 OS Credential Dumping: LSASS Memory).</p>
753
+ </div>
754
+ <div class="card">
755
+ <span class="pill">Weak</span>
756
+ <h4>CVE implementation specifics</h4>
757
+ <p>Fabricates implementation details. Cited a non-existent <code>pgp</code> binary path when asked about CVE-2024-3400 (PAN-OS GlobalProtect — actual root cause is session-ID handling). Top-level CWE call usually correct; deep mechanics often invented.</p>
758
+ </div>
759
+ <div class="card">
760
+ <span class="pill">Weak</span>
761
+ <h4>Tool categorization</h4>
762
+ <p>Misclassifies offensive tooling. Listed Mimikatz as a "ransomware loader" — Mimikatz is a credential-dumping utility, not a ransomware loader.</p>
763
+ </div>
764
+ </div>
765
+
766
+ <h3>Validated empirically · we tested 14, kept 7</h3>
767
+ <p>We ran 14 candidate demo prompts through the deployed model and graded each output for factual accuracy. <strong>7 passed</strong>, <strong>7 were cut</strong>: Log4Shell vs Spring4Shell exploit-primitive comparison; CVE-2024-3400 explanation; LSASS ATT&amp;CK mapping; schtasks ATT&amp;CK mapping; Dockerfile <code>curl|bash</code> review; ransomware detection signals; Python dynamic-code-execution CWE. Most cuts were OOD hallucinations: invented technique numbers, fabricated CVE specifics, mislabeled mitigations.</p>
768
+
769
+ <h3>Out-of-scope use</h3>
770
+ <ul class="bullets">
771
+ <li>Generating exploit code, weaponized PoC, or attacker tradecraft.</li>
772
+ <li>Critical security decisions without qualified human review.</li>
773
+ <li>Legal, medical, or other regulated-advice contexts.</li>
774
+ <li>Tasks outside cybersecurity (general chat, code generation, summarization).</li>
775
+ <li>Violation of laws (CFAA, GDPR, etc.).</li>
776
+ </ul>
777
+
778
+ <h3>Recommendations</h3>
779
+ <ul class="bullets">
780
+ <li><strong>Pair with retrieval.</strong> Ground CVE / advisory queries against an authoritative source before quoting specifics.</li>
781
+ <li><strong>Sample at low temperature.</strong> CWE selection benefits from determinism (temp 0.2–0.3).</li>
782
+ <li><strong>Treat output as a triage hint,</strong> not a verdict — keep an analyst in the loop.</li>
783
+ </ul>
784
+ </section>
785
+
786
+ <section id="companion">
787
+ <h3>Companion</h3>
788
+ <h2>Gemma4Defense-2B</h2>
789
+ <p>The same recipe applied to Gemma-4-E2B-it produces a 2B companion that scores RCM <strong>0.6754 ± 0.0035</strong> and MCQ <strong>0.6042 ± 0.0090</strong> — slightly higher MCQ than CyberSecQwen-4B at half the parameters again. Use the 2B when memory is tight; use the 4B when you want stronger general instruction-following and longer-form rationales.</p>
790
+ </section>
791
+
792
+ <section id="citation">
793
+ <h3>Citation</h3>
794
+ <h2>Cite &amp; license.</h2>
795
+ <pre class="code"><span class="c">% bibtex</span>
796
+ @misc{cybersecqwen2026,
797
+ title = {CyberSecQwen-4B: A Compact CTI Specialist Fine-Tuned from
798
+ Qwen3-4B-Instruct-2507 on AMD MI300X},
799
+ author = {Mulia, Samuel},
800
+ year = {2026},
801
+ publisher = {Hugging Face},
802
+ url = {https://huggingface.co/athena129/CyberSecQwen-4B}
803
+ }</pre>
804
+ <p><strong>License:</strong> Apache 2.0, end-to-end — weights, training code, and the synthetic CVE/CTI Q&amp;A corpus. The decontaminated 2021 CVE→CWE mappings derive from public MITRE/NVD records. Evaluation protocol: <a href="https://arxiv.org/abs/2504.21039" target="_blank" style="color:var(--accent)">Foundation-Sec-8B (arXiv:2504.21039)</a>. Benchmark: <a href="https://github.com/xashru/cti-bench" target="_blank" style="color:var(--accent)">CTI-Bench</a>.</p>
805
+ </section>
806
+
807
+ </div>
808
+ </div>
809
+ </template>
810
+
811
+ <script>
812
+ /* ─── BACKEND HOOK ────────────────────────────────────────────
813
+ Calls the deployed Gradio Space `athena129/cybersecqwen-demo`
814
+ via @gradio/client. The backend exposes `/chat` which streams
815
+ {message, chat_history} tuples; we surface partials via onChunk.
816
+ */
817
+ const BACKEND_SPACE = "athena129/cybersecqwen-demo";
818
+ let _client = null;
819
+ async function getClient(){
820
+ if (_client) return _client;
821
+ const { Client } = await import("https://esm.sh/@gradio/client@1.10.0");
822
+ _client = await Client.connect(BACKEND_SPACE);
823
+ return _client;
824
+ }
825
+
826
+ async function askModel(prompt, onChunk){
827
+ const client = await getClient();
828
+ const job = client.submit("/chat", [prompt, []]);
829
+ let lastContent = "";
830
+ for await (const msg of job){
831
+ if (msg.type === "data" && msg.data && Array.isArray(msg.data)){
832
+ const history = msg.data[1];
833
+ if (Array.isArray(history) && history.length > 0){
834
+ const last = history[history.length - 1];
835
+ if (last && last.role === "assistant" && typeof last.content === "string"){
836
+ if (last.content !== lastContent){
837
+ lastContent = last.content;
838
+ if (onChunk) onChunk(lastContent);
839
+ }
840
+ }
841
+ }
842
+ }
843
+ }
844
+ return { answer: lastContent || "The model returned no output. Try again." };
845
+ }
846
+
847
+ /* ─── ROUTING ─────────────────────────────────────────────── */
848
+ const view = document.getElementById('view');
849
+ const topTitle = document.getElementById('topTitle');
850
+ const navLinks = document.querySelectorAll('#nav a');
851
+
852
+ let convo = []; // [{role:'user'|'bot', text, pending?}]
853
+
854
+ function route(){
855
+ const hash = location.hash || '#/';
856
+ navLinks.forEach(a => a.classList.toggle('active', a.dataset.route === (hash.replace(/^#/,'') || '/')));
857
+ if (hash.startsWith('#/about')){
858
+ topTitle.textContent = 'About';
859
+ renderAbout();
860
+ } else {
861
+ topTitle.textContent = 'Chat';
862
+ renderChat();
863
+ }
864
+ }
865
+ window.addEventListener('hashchange', route);
866
+
867
+ /* ─── CHAT ────────────────────────────────────────────────── */
868
+ function renderChat(){
869
+ view.innerHTML = '';
870
+ view.classList.toggle('no-scroll', convo.length > 0);
871
+ if (convo.length === 0) renderEmptyChat(); else renderActiveChat();
872
+ }
873
+
874
+ function bindComposer(form, ta, btn, onSend){
875
+ const update = () => { btn.disabled = ta.value.trim().length === 0; };
876
+ ta.addEventListener('input', () => {
877
+ ta.style.height = 'auto';
878
+ ta.style.height = Math.min(ta.scrollHeight, 240) + 'px';
879
+ update();
880
+ });
881
+ ta.addEventListener('keydown', e => {
882
+ if (e.key === 'Enter' && !e.shiftKey){ e.preventDefault(); if (!btn.disabled) form.requestSubmit(); }
883
+ });
884
+ form.addEventListener('submit', e => { e.preventDefault(); const v = ta.value.trim(); if (!v) return; onSend(v); });
885
+ update();
886
+ }
887
+
888
+ function renderEmptyChat(){
889
+ view.appendChild(document.getElementById('tpl-chat-empty').content.cloneNode(true));
890
+ const form = document.getElementById('composer-empty');
891
+ const ta = document.getElementById('ta-empty');
892
+ const btn = document.getElementById('send-empty');
893
+ bindComposer(form, ta, btn, sendMessage);
894
+ // focus
895
+ setTimeout(() => ta.focus(), 30);
896
+
897
+ document.querySelectorAll('#chips .chip').forEach(c => {
898
+ c.addEventListener('click', () => {
899
+ ta.value = c.dataset.prompt;
900
+ ta.dispatchEvent(new Event('input'));
901
+ ta.focus();
902
+ });
903
+ });
904
+ }
905
+
906
+ function renderActiveChat(){
907
+ view.innerHTML = '';
908
+ view.classList.add('no-scroll');
909
+ view.appendChild(document.getElementById('tpl-chat-active').content.cloneNode(true));
910
+ const inner = document.getElementById('thread-inner');
911
+ inner.innerHTML = '';
912
+ convo.forEach(m => inner.appendChild(renderMsg(m)));
913
+ scrollThread();
914
+
915
+ const form = document.getElementById('composer-dock');
916
+ const ta = document.getElementById('ta-dock');
917
+ const btn = document.getElementById('send-dock');
918
+ bindComposer(form, ta, btn, sendMessage);
919
+ setTimeout(() => ta.focus(), 30);
920
+
921
+ document.getElementById('newChat').addEventListener('click', () => {
922
+ convo = [];
923
+ renderChat();
924
+ });
925
+ }
926
+
927
+ function renderMsg(m){
928
+ const wrap = document.createElement('div');
929
+ wrap.className = 'msg ' + (m.role === 'user' ? 'user' : 'bot');
930
+ if (m.role === 'user'){
931
+ wrap.innerHTML = `<span class="who">You</span><div class="bubble"></div>`;
932
+ wrap.querySelector('.bubble').textContent = m.text;
933
+ } else {
934
+ wrap.innerHTML = `<span class="who">CyberSecQwen</span><div class="body"></div>`;
935
+ const body = wrap.querySelector('.body');
936
+ if (m.pending){
937
+ body.innerHTML = '<span class="typing"><i></i><i></i><i></i></span>';
938
+ } else {
939
+ body.innerHTML = renderMd(m.text);
940
+ }
941
+ }
942
+ return wrap;
943
+ }
944
+
945
+ function renderMd(s){
946
+ return s
947
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
948
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
949
+ .replace(/`([^`]+)`/g, '<code>$1</code>');
950
+ }
951
+
952
+ function scrollThread(){
953
+ const t = document.getElementById('thread');
954
+ if (t) t.scrollTop = t.scrollHeight;
955
+ }
956
+
957
+ async function sendMessage(text){
958
+ const wasEmpty = convo.length === 0;
959
+ convo.push({ role:'user', text });
960
+ convo.push({ role:'bot', text:'', pending:true });
961
+
962
+ // Always re-render the active chat so the empty-state DOM
963
+ // (hero, chips, empty composer) is fully removed on first send.
964
+ renderActiveChat();
965
+ scrollThread();
966
+
967
+ // Stream tokens into the last bot message as they arrive.
968
+ const onChunk = (partial) => {
969
+ convo[convo.length - 1] = { role:'bot', text: partial };
970
+ const inner = document.getElementById('thread-inner');
971
+ if (!inner) return;
972
+ const last = inner.lastElementChild;
973
+ if (last) last.replaceWith(renderMsg(convo[convo.length - 1]));
974
+ scrollThread();
975
+ };
976
+
977
+ try{
978
+ const { answer } = await askModel(text, onChunk);
979
+ convo[convo.length - 1] = { role:'bot', text: answer };
980
+ } catch (e){
981
+ convo[convo.length - 1] = {
982
+ role:'bot',
983
+ text: 'The model is unavailable right now. ZeroGPU may be cold-starting (first call after idle takes ~30 s) — try again in a moment.\n\n`' + (e?.message || String(e)) + '`',
984
+ };
985
+ }
986
+
987
+ // Final re-render of the bot message in place.
988
+ const inner = document.getElementById('thread-inner');
989
+ if (inner){
990
+ const last = inner.lastElementChild;
991
+ if (last) last.replaceWith(renderMsg(convo[convo.length - 1]));
992
+ scrollThread();
993
+ }
994
+ // refocus composer
995
+ const ta = document.getElementById('ta-dock');
996
+ if (ta){ ta.value = ''; ta.style.height = 'auto'; ta.dispatchEvent(new Event('input')); ta.focus(); }
997
+ }
998
+
999
+ /* ─── ABOUT ────────────────────────────────────────────────── */
1000
+ function renderAbout(){
1001
+ view.innerHTML = '';
1002
+ view.appendChild(document.getElementById('tpl-about').content.cloneNode(true));
1003
+
1004
+ // Sub-nav scroll-spy + smooth-scroll within the view (the scroll container is .view)
1005
+ const subnav = view.querySelector('#subnav');
1006
+ const links = subnav.querySelectorAll('a');
1007
+ const sections = view.querySelectorAll('.about section');
1008
+
1009
+ links.forEach(a => {
1010
+ a.addEventListener('click', e => {
1011
+ e.preventDefault();
1012
+ const id = a.getAttribute('href').slice(1);
1013
+ const target = view.querySelector('#' + id);
1014
+ if (target) view.scrollTo({ top: target.offsetTop - 60, behavior:'smooth' });
1015
+ });
1016
+ });
1017
+
1018
+ const setCurrent = () => {
1019
+ const top = view.scrollTop + 80;
1020
+ let current = sections[0]?.id;
1021
+ sections.forEach(s => { if (s.offsetTop <= top) current = s.id; });
1022
+ links.forEach(a => a.classList.toggle('is-current', a.getAttribute('href') === '#' + current));
1023
+ };
1024
+ view.addEventListener('scroll', setCurrent, { passive:true });
1025
+ setCurrent();
1026
+ }
1027
+
1028
+ /* ─── BOOT ─────────────────────────────────────────────────── */
1029
+ route();
1030
+ </script>
1031
+ </body>
1032
  </html>