@@ -55,7 +55,7 @@ const SESSION_ID = getSessionId();
5555// onto the /fetch, /parse, and /enrich requests the app already makes for
5656// functional reasons — SESSION_ID is attached to those, but there is no
5757// dedicated client-initiated logging call. Invisible to browser DevTools.
58- const APP_VERSION = "v134 ";
58+ const APP_VERSION = "v135 ";
5959
6060// ============================================================
6161// IOC Whitelist — exact-match auto-removal from parsed results
@@ -2220,6 +2220,7 @@ export default function App() {
22202220 const [aiSummary, setAiSummary] = useState(null); // { headline, summary, recommendations[] }
22212221 const [aiState, setAiState] = useState("idle"); // idle | loading | done | error
22222222 const [aiOpen, setAiOpen] = useState(false);
2223+ const [aiScanOpen, setAiScanOpen] = useState(false);
22232224 const [retryCount, setRetryCount] = useState(0);
22242225 const [cooldown, setCooldown] = useState(0); // seconds until retry re-enabled
22252226 const [enrichCache, setEnrichCache] = useState({}); // {iocKey: {loading,data,error}}
@@ -7239,142 +7240,159 @@ export default function App() {
72397240 )}
72407241
72417242 {(articleClean || rawArticle) && sourceUrl && (
7242- <div className="rounded-xl mb-4 overflow-hidden" style={{ ...panel, borderColor: "rgba(253,224,71,0.35)" }}>
7243- <div className="flex items-center justify-between px-4 py-3 gap-3 flex-wrap">
7244- <span className="flex items-center gap-2.5 min-w-0">
7245- <span className="shrink-0 flex h-7 w-7 items-center justify-center rounded-lg"
7246- style={{ backgroundColor: "rgba(253,224,71,0.08)", border: "1px solid rgba(253,224,71,0.35)" }}>
7247- <span style={{ fontSize: 14 }}>🧠</span>
7248- </span>
7249- <span className="text-sm font-bold tracking-wide" style={{ color: "#fde047" }}>AI Scan Threat Hunting Artifacts</span>
7250- {aiScanState === "loading" && (
7251- <span className="text-[10px] uppercase tracking-widest flex items-center gap-1" style={{ color: "#8aa0ad" }}>
7252- <Loader2 size={11} className="animate-spin" /> scanning
7253- </span>
7254- )}
7255- {aiScanState === "done" && aiScanCounts && (
7256- <span className="text-[10px] uppercase tracking-widest rounded-full px-2 py-0.5"
7257- style={{ color: "#00ff9c", border: "1px solid rgba(0,255,156,0.35)", backgroundColor: "rgba(0,255,156,0.06)" }}>
7258- +{aiScanCounts.scheduled_tasks + aiScanCounts.services + aiScanCounts.registry_ops + aiScanCounts.command_lines + aiScanCounts.file_paths} artifacts merged
7243+ <div className="flex flex-col sm:flex-row gap-3 mb-4 items-start">
7244+ <div className="rounded-xl overflow-hidden flex-1 w-full" style={{ ...panel, borderColor: "rgba(192,132,252,0.35)", boxShadow: aiOpen ? "0 0 24px rgba(192,132,252,0.10)" : "none" }}>
7245+ <button onClick={toggleAiPanel}
7246+ className="w-full flex items-center justify-between px-4 py-3 text-left gap-3"
7247+ style={{ backgroundColor: aiOpen ? "rgba(192,132,252,0.06)" : "transparent" }}>
7248+ <span className="flex items-center gap-2.5 min-w-0">
7249+ <span className="shrink-0 flex h-7 w-7 items-center justify-center rounded-lg"
7250+ style={{ backgroundColor: "rgba(192,132,252,0.08)", border: "1px solid rgba(192,132,252,0.35)" }}>
7251+ <Sparkles size={14} style={{ color: "#c084fc" }} />
72597252 </span>
7260- )}
7261- </span>
7262- {aiScanState === "done" ? (
7263- <button onClick={() => { setAiScanState("idle"); setAiScanCounts(null); }}
7264- className="text-[11px] underline shrink-0" style={{ color: "#8aa0ad", cursor: "pointer", background: "none", border: "none" }}>
7265- re-scan
7266- </button>
7267- ) : (
7268- <button onClick={runAIScan}
7269- disabled={aiScanState === "loading"}
7270- title="Deep artifact extraction — scheduled tasks, services, registry ops, command lines, file paths"
7271- className="flex items-center gap-1 rounded-lg px-2.5 py-1 text-[11px] font-semibold shrink-0"
7272- style={{
7273- color: "#04111a",
7274- backgroundColor: "#fde047",
7275- border: "1px solid rgba(253,224,71,0.6)",
7276- cursor: aiScanState === "loading" ? "not-allowed" : "pointer",
7277- }}>
7278- {aiScanState === "loading" ? <Loader2 size={12} className="animate-spin" /> : <span style={{ fontSize: 11 }}>🧠</span>}
7279- {aiScanState === "loading" ? "Scanning…" : "AI Scan Artifacts"}
7280- </button>
7253+ <span className="text-sm font-bold tracking-wide truncate" style={{ color: "#c084fc" }}>AI Summary</span>
7254+ {aiState === "idle" && (
7255+ <span className="text-[10px] uppercase tracking-widest rounded-full px-2 py-0.5 hidden sm:inline shrink-0"
7256+ style={{ color: "#8aa0ad", border: "1px solid rgba(120,160,180,0.3)" }}>
7257+ click to generate
7258+ </span>
7259+ )}
7260+ </span>
7261+ <ChevronDown size={18} className="shrink-0 transition-transform"
7262+ style={{ color: "#c084fc", transform: aiOpen ? "rotate(180deg)" : "rotate(0deg)" }} />
7263+ </button>
7264+
7265+ {aiOpen && (
7266+ <div className="px-4 pb-4 pt-1" style={{ borderTop: "1px solid rgba(192,132,252,0.2)" }}>
7267+ {aiState === "loading" && (
7268+ <p className="text-xs sm:text-sm animate-pulse pt-2" style={{ color: "#9fb3bd" }}>
7269+ Analyzing article and generating summary…
7270+ </p>
7271+ )}
7272+
7273+ {aiState === "done" && aiSummary && (
7274+ <div className="pt-2">
7275+ <h2 className="text-sm sm:text-base font-extrabold leading-snug" style={{ color: "#eafcff" }}>{aiSummary.headline}</h2>
7276+ {aiSummary.executive_summary && (
7277+ <>
7278+ <p className="text-xs sm:text-sm uppercase tracking-widest font-bold mt-2.5 mb-1" style={{ color: "#00e5ff" }}>Executive Summary</p>
7279+ <p className="text-xs sm:text-sm leading-relaxed" style={{ color: "#d4e3ea" }}>{defangProse(aiSummary.executive_summary)}</p>
7280+ </>
7281+ )}
7282+ <p className="text-xs sm:text-sm uppercase tracking-widest font-bold mt-3 mb-1" style={{ color: "#00e5ff" }}>Technical Analysis</p>
7283+ <p className="text-xs sm:text-sm font-medium leading-relaxed" style={{ color: "#b8c9d1" }}>{defangProse(aiSummary.summary)}</p>
7284+ {aiSummary.recommendations.length > 0 && (
7285+ <div className="mt-2.5">
7286+ <p className="text-xs sm:text-sm uppercase tracking-widest font-bold mb-1" style={{ color: "#00e5ff" }}>Recommendations</p>
7287+ {aiSummary.recommendations.map((rec, i) => (
7288+ <div key={i} className="flex items-start gap-1.5 text-xs sm:text-sm py-0.5 leading-relaxed font-medium" style={{ color: "#9fb3bd" }}>
7289+ <span className="shrink-0" style={{ color: "#c084fc" }}>▸</span> <span>{defangProse(rec)}</span>
7290+ </div>
7291+ ))}
7292+ </div>
7293+ )}
7294+ </div>
7295+ )}
7296+
7297+ {aiState === "error" && (
7298+ <div className="pt-2">
7299+ <p className="text-xs sm:text-sm leading-relaxed" style={{ color: "#ffb4b4" }}>
7300+ The AI engines are experiencing high traffic right now, so a summary couldn't be generated. Please give it a moment and retry.
7301+ </p>
7302+ <button onClick={retryAi} disabled={cooldown > 0}
7303+ className="mt-2.5 flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-semibold"
7304+ style={{
7305+ color: cooldown > 0 ? "#5d7382" : "#c084fc",
7306+ border: `1px solid ${cooldown > 0 ? "rgba(120,160,180,0.25)" : "rgba(192,132,252,0.45)"}`,
7307+ backgroundColor: cooldown > 0 ? "rgba(120,160,180,0.06)" : "rgba(192,132,252,0.10)",
7308+ cursor: cooldown > 0 ? "not-allowed" : "pointer",
7309+ }}>
7310+ <RefreshCw size={13} />
7311+ {cooldown > 0 ? `Retry available in ${cooldown}s` : "Retry AI Summary"}
7312+ </button>
7313+ </div>
7314+ )}
7315+
7316+ {aiState === "idle" && (
7317+ <p className="text-xs sm:text-sm pt-2 animate-pulse" style={{ color: "#9fb3bd" }}>
7318+ Initializing…
7319+ </p>
7320+ )}
7321+ </div>
72817322 )}
72827323 </div>
7283- {aiScanState === "error" && (
7284- <div className="px-4 pb-3 pt-1 text-xs" style={{ color: "#ffb4b4", borderTop: "1px solid rgba(255,77,77,0.2)" }}>
7285- {aiScanError || "AI scan failed. Please retry."}
7286- <button onClick={() => { setAiScanState("idle"); setAiScanError(""); }}
7287- className="ml-2 underline" style={{ color: "#fde047" }}>reset</button>
7288- </div>
7289- )}
7290- {aiScanState === "done" && aiScanCounts && (aiScanCounts.scheduled_tasks + aiScanCounts.services + aiScanCounts.registry_ops + aiScanCounts.command_lines + aiScanCounts.file_paths) === 0 && (
7291- <div className="px-4 pb-3 pt-1 text-xs" style={{ color: "#8aa0ad", borderTop: "1px solid rgba(253,224,71,0.15)" }}>
7292- No additional artifacts found beyond what regex already captured.
7293- </div>
7294- )}
7295- </div>
7296- )}
72977324
7298- {(articleClean || rawArticle) && sourceUrl && (
7299- <div className="rounded-xl mb-4 overflow-hidden" style={{ ...panel, borderColor: "rgba(192,132,252,0.35)", boxShadow: aiOpen ? "0 0 24px rgba(192,132,252,0.10)" : "none" }}>
7300- <button onClick={toggleAiPanel}
7301- className="w-full flex items-center justify-between px-4 py-3 text-left"
7302- style={{ backgroundColor: aiOpen ? "rgba(192,132,252,0.06)" : "transparent" }}>
7303- <span className="flex items-center gap-2.5 min-w-0">
7304- <span className="shrink-0 flex h-7 w-7 items-center justify-center rounded-lg"
7305- style={{ backgroundColor: "rgba(192,132,252,0.08)", border: "1px solid rgba(192,132,252,0.35)" }}>
7306- <Sparkles size={14} style={{ color: "#c084fc" }} />
7307- </span>
7308- <span className="text-sm font-bold tracking-wide" style={{ color: "#c084fc" }}>AI Summary</span>
7309- {aiState === "idle" && (
7310- <span className="text-[10px] uppercase tracking-widest rounded-full px-2 py-0.5 hidden sm:inline"
7311- style={{ color: "#8aa0ad", border: "1px solid rgba(120,160,180,0.3)" }}>
7312- click to generate
7325+ <div className="rounded-xl overflow-hidden flex-1 w-full" style={{ ...panel, borderColor: "rgba(253,224,71,0.35)", boxShadow: aiScanOpen ? "0 0 24px rgba(253,224,71,0.10)" : "none" }}>
7326+ <button onClick={() => setAiScanOpen((v) => !v)}
7327+ className="w-full flex items-center justify-between px-4 py-3 text-left gap-3"
7328+ style={{ backgroundColor: aiScanOpen ? "rgba(253,224,71,0.06)" : "transparent" }}>
7329+ <span className="flex items-center gap-2.5 min-w-0">
7330+ <span className="shrink-0 flex h-7 w-7 items-center justify-center rounded-lg"
7331+ style={{ backgroundColor: "rgba(253,224,71,0.08)", border: "1px solid rgba(253,224,71,0.35)" }}>
7332+ <span style={{ fontSize: 14 }}>🧠</span>
73137333 </span>
7314- )}
7315- </span>
7316- <ChevronDown size={18} className="shrink-0 transition-transform"
7317- style={{ color: "#c084fc", transform: aiOpen ? "rotate(180deg)" : "rotate(0deg)" }} />
7318- </button>
7319-
7320- {aiOpen && (
7321- <div className="px-4 pb-4 pt-1" style={{ borderTop: "1px solid rgba(192,132,252,0.2)" }}>
7322- {aiState === "loading" && (
7323- <p className="text-xs sm:text-sm animate-pulse pt-2" style={{ color: "#9fb3bd" }}>
7324- Analyzing article and generating summary…
7325- </p>
7326- )}
7334+ <span className="text-sm font-bold tracking-wide truncate" style={{ color: "#fde047" }}>AI Scan Artifacts</span>
7335+ {aiScanState === "loading" && (
7336+ <span className="text-[10px] uppercase tracking-widest flex items-center gap-1 shrink-0" style={{ color: "#8aa0ad" }}>
7337+ <Loader2 size={11} className="animate-spin" /> scanning
7338+ </span>
7339+ )}
7340+ {aiScanState === "done" && aiScanCounts && (
7341+ <span className="text-[10px] uppercase tracking-widest rounded-full px-2 py-0.5 shrink-0"
7342+ style={{ color: "#00ff9c", border: "1px solid rgba(0,255,156,0.35)", backgroundColor: "rgba(0,255,156,0.06)" }}>
7343+ +{aiScanCounts.scheduled_tasks + aiScanCounts.services + aiScanCounts.registry_ops + aiScanCounts.command_lines + aiScanCounts.file_paths} merged
7344+ </span>
7345+ )}
7346+ {aiScanState === "idle" && (
7347+ <span className="text-[10px] uppercase tracking-widest rounded-full px-2 py-0.5 hidden sm:inline shrink-0"
7348+ style={{ color: "#8aa0ad", border: "1px solid rgba(120,160,180,0.3)" }}>
7349+ click to scan
7350+ </span>
7351+ )}
7352+ </span>
7353+ <ChevronDown size={18} className="shrink-0 transition-transform"
7354+ style={{ color: "#fde047", transform: aiScanOpen ? "rotate(180deg)" : "rotate(0deg)" }} />
7355+ </button>
73277356
7328- {aiState === "done" && aiSummary && (
7357+ {aiScanOpen && (
7358+ <div className="px-4 pb-4 pt-1" style={{ borderTop: "1px solid rgba(253,224,71,0.2)" }}>
73297359 <div className="pt-2">
7330- <h2 className="text-sm sm:text-base font-extrabold leading-snug" style={{ color: "#eafcff" }}>{aiSummary.headline}</h2>
7331- {aiSummary.executive_summary && (
7332- <>
7333- <p className="text-xs sm:text-sm uppercase tracking-widest font-bold mt-2.5 mb-1" style={{ color: "#00e5ff" }}>Executive Summary</p>
7334- <p className="text-xs sm:text-sm leading-relaxed" style={{ color: "#d4e3ea" }}>{defangProse(aiSummary.executive_summary)}</p>
7335- </>
7336- )}
7337- <p className="text-xs sm:text-sm uppercase tracking-widest font-bold mt-3 mb-1" style={{ color: "#00e5ff" }}>Technical Analysis</p>
7338- <p className="text-xs sm:text-sm font-medium leading-relaxed" style={{ color: "#b8c9d1" }}>{defangProse(aiSummary.summary)}</p>
7339- {aiSummary.recommendations.length > 0 && (
7340- <div className="mt-2.5">
7341- <p className="text-xs sm:text-sm uppercase tracking-widest font-bold mb-1" style={{ color: "#00e5ff" }}>Recommendations</p>
7342- {aiSummary.recommendations.map((rec, i) => (
7343- <div key={i} className="flex items-start gap-1.5 text-xs sm:text-sm py-0.5 leading-relaxed font-medium" style={{ color: "#9fb3bd" }}>
7344- <span className="shrink-0" style={{ color: "#c084fc" }}>▸</span> <span>{defangProse(rec)}</span>
7345- </div>
7346- ))}
7347- </div>
7360+ {aiScanState === "done" ? (
7361+ <button onClick={() => { setAiScanState("idle"); setAiScanCounts(null); }}
7362+ className="text-[11px] underline" style={{ color: "#8aa0ad", cursor: "pointer", background: "none", border: "none" }}>
7363+ re-scan
7364+ </button>
7365+ ) : (
7366+ <button onClick={runAIScan}
7367+ disabled={aiScanState === "loading"}
7368+ title="Deep artifact extraction — scheduled tasks, services, registry ops, command lines, file paths"
7369+ className="flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-xs font-semibold"
7370+ style={{
7371+ color: "#04111a",
7372+ backgroundColor: "#fde047",
7373+ border: "1px solid rgba(253,224,71,0.6)",
7374+ cursor: aiScanState === "loading" ? "not-allowed" : "pointer",
7375+ }}>
7376+ {aiScanState === "loading" ? <Loader2 size={12} className="animate-spin" /> : <span style={{ fontSize: 11 }}>🧠</span>}
7377+ {aiScanState === "loading" ? "Scanning…" : "AI Scan Artifacts"}
7378+ </button>
73487379 )}
73497380 </div>
7350- )}
7351-
7352- {aiState === "error" && (
7353- <div className="pt-2">
7354- <p className="text-xs sm:text-sm leading-relaxed" style={{ color: "#ffb4b4" }}>
7355- The AI engines are experiencing high traffic right now, so a summary couldn't be generated. Please give it a moment and retry.
7381+ {aiScanState === "error" && (
7382+ <p className="pt-2 text-xs" style={{ color: "#ffb4b4" }}>
7383+ {aiScanError || "AI scan failed. Please retry."}
7384+ <button onClick={() => { setAiScanState("idle"); setAiScanError(""); }}
7385+ className="ml-2 underline" style={{ color: "#fde047" }}>reset</button>
73567386 </p>
7357- <button onClick={retryAi} disabled={cooldown > 0}
7358- className="mt-2.5 flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-semibold"
7359- style={{
7360- color: cooldown > 0 ? "#5d7382" : "#c084fc",
7361- border: `1px solid ${cooldown > 0 ? "rgba(120,160,180,0.25)" : "rgba(192,132,252,0.45)"}`,
7362- backgroundColor: cooldown > 0 ? "rgba(120,160,180,0.06)" : "rgba(192,132,252,0.10)",
7363- cursor: cooldown > 0 ? "not-allowed" : "pointer",
7364- }}>
7365- <RefreshCw size={13} />
7366- {cooldown > 0 ? `Retry available in ${cooldown}s` : "Retry AI Summary"}
7367- </button>
7368- </div>
7369- )}
7370-
7371- {aiState === "idle" && (
7372- <p className="text-xs sm:text-sm pt-2 animate-pulse" style={{ color: "#9fb3bd" }}>
7373- Initializing…
7374- </p>
7375- )}
7376- </div>
7377- )}
7387+ )}
7388+ {aiScanState === "done" && aiScanCounts && (aiScanCounts.scheduled_tasks + aiScanCounts.services + aiScanCounts.registry_ops + aiScanCounts.command_lines + aiScanCounts.file_paths) === 0 && (
7389+ <p className="pt-2 text-xs" style={{ color: "#8aa0ad" }}>
7390+ No additional artifacts found beyond what regex already captured.
7391+ </p>
7392+ )}
7393+ </div>
7394+ )}
7395+ </div>
73787396 </div>
73797397 )}
73807398
0 commit comments