Skip to content

Commit 7c85ded

Browse files
committed
feat: dynamic threshold + dismissable surprising connections
- Dynamic score threshold: <20 nodes→3, <50→4, 50+→5 - Surprising connections can be dismissed via X button (hover to show) - Dismissed insights hidden from panel and badge count - Dismissed state persists during session
1 parent d4c8024 commit 7c85ded

2 files changed

Lines changed: 26 additions & 7 deletions

File tree

src/components/graph/graph-view.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Graph from "graphology"
33
import { SigmaContainer, useLoadGraph, useRegisterEvents, useSigma } from "@react-sigma/core"
44
import "@react-sigma/core/lib/style.css"
55
import forceAtlas2 from "graphology-layout-forceatlas2"
6-
import { Network, RefreshCw, ZoomIn, ZoomOut, Maximize, Layers, Tag, Lightbulb, AlertTriangle, Link2 } from "lucide-react"
6+
import { Network, RefreshCw, ZoomIn, ZoomOut, Maximize, Layers, Tag, Lightbulb, AlertTriangle, Link2, X } from "lucide-react"
77
import { Button } from "@/components/ui/button"
88
import { useWikiStore } from "@/stores/wiki-store"
99
import { readFile } from "@/commands/fs"
@@ -308,6 +308,7 @@ export function GraphView() {
308308
const [colorMode, setColorMode] = useState<ColorMode>("type")
309309
const [showInsights, setShowInsights] = useState(false)
310310
const [highlightedNodes, setHighlightedNodes] = useState<Set<string>>(new Set())
311+
const [dismissedInsights, setDismissedInsights] = useState<Set<string>>(new Set())
311312
const lastLoadedVersion = useRef(-1)
312313

313314
const loadGraph = useCallback(async () => {
@@ -428,7 +429,7 @@ export function GraphView() {
428429
<Layers className="h-3 w-3" />
429430
Community
430431
</Button>
431-
{(surprisingConns.length > 0 || knowledgeGaps.length > 0) && (
432+
{(surprisingConns.filter((c) => !dismissedInsights.has(c.key)).length > 0 || knowledgeGaps.length > 0) && (
432433
<Button
433434
variant={showInsights ? "secondary" : "ghost"}
434435
size="sm"
@@ -443,7 +444,7 @@ export function GraphView() {
443444
<Lightbulb className="h-3 w-3" />
444445
Insights
445446
<span className="rounded bg-muted px-1 text-[10px]">
446-
{surprisingConns.length + knowledgeGaps.length}
447+
{surprisingConns.filter((c) => !dismissedInsights.has(c.key)).length + knowledgeGaps.length}
447448
</span>
448449
</Button>
449450
)}
@@ -580,16 +581,28 @@ export function GraphView() {
580581
Surprising Connections
581582
</div>
582583
<div className="flex flex-col gap-1.5">
583-
{surprisingConns.map((conn, i) => {
584+
{surprisingConns
585+
.filter((conn) => !dismissedInsights.has(conn.key))
586+
.map((conn, i) => {
584587
const ids = new Set([conn.source.id, conn.target.id])
585588
const isActive = highlightedNodes.size === ids.size &&
586589
[...ids].every((id) => highlightedNodes.has(id))
587590
return (
588591
<div
589592
key={i}
590-
className={`rounded border px-2 py-1.5 cursor-pointer transition-colors ${isActive ? "bg-blue-500/15 border-blue-500/40" : "bg-muted/30 hover:bg-muted/50"}`}
593+
className={`rounded border px-2 py-1.5 cursor-pointer transition-colors relative group ${isActive ? "bg-blue-500/15 border-blue-500/40" : "bg-muted/30 hover:bg-muted/50"}`}
591594
onClick={() => setHighlightedNodes(isActive ? new Set() : ids)}
592595
>
596+
<button
597+
className="absolute top-1 right-1 hidden group-hover:block p-0.5 rounded hover:bg-muted"
598+
onClick={(e) => {
599+
e.stopPropagation()
600+
setDismissedInsights((prev) => new Set([...prev, conn.key]))
601+
if (isActive) setHighlightedNodes(new Set())
602+
}}
603+
>
604+
<X className="h-3 w-3 text-muted-foreground" />
605+
</button>
593606
<div className="font-medium text-foreground">
594607
{conn.source.label}{conn.target.label}
595608
</div>

src/lib/graph-insights.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface SurprisingConnection {
99
target: GraphNode
1010
score: number
1111
reasons: string[]
12+
key: string // stable ID for dismiss tracking
1213
}
1314

1415
export interface KnowledgeGap {
@@ -40,6 +41,10 @@ export function findSurprisingConnections(
4041
// Structural pages that link to everything — exclude from analysis
4142
const STRUCTURAL_IDS = new Set(["index", "log", "overview"])
4243

44+
// Dynamic threshold: as wiki grows, require higher score to surface
45+
const nodeCount = nodes.length
46+
const dynamicThreshold = nodeCount < 20 ? 3 : nodeCount < 50 ? 4 : 5
47+
4348
const scored: SurprisingConnection[] = []
4449

4550
for (const edge of edges) {
@@ -90,8 +95,9 @@ export function findSurprisingConnections(
9095
reasons.push("weak but present connection")
9196
}
9297

93-
if (score >= 3 && reasons.length > 0) {
94-
scored.push({ source, target, score, reasons })
98+
if (score >= dynamicThreshold && reasons.length > 0) {
99+
const key = [source.id, target.id].sort().join(":::")
100+
scored.push({ source, target, score, reasons, key })
95101
}
96102
}
97103

0 commit comments

Comments
 (0)