Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions hypergraph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Open [http://localhost:3000](http://localhost:3000) and enter any technical topi
```bash
HYPERBROWSER_API_KEY= # from hyperbrowser.ai
SERPER_API_KEY= # from serper.dev — used to find source URLs
OPENAI_API_KEY= # used for graph generation with GPT-4o
ANTHROPIC_API_KEY= # used for graph generation with Claude Haiku 4.5
HYPERBROWSER_MAX_CONCURRENCY=1 # free plan: keep at 1. paid plans can increase this.
```

Expand All @@ -54,7 +54,7 @@ If you hit a concurrency limit the app will show an amber warning banner with an

- **Next.js 16** — App Router, API routes
- **Hyperbrowser** — scrapes source material with `onlyMainContent` for clean output
- **OpenAI GPT-4o** — generates the skill graph JSON from scraped docs
- **Claude Haiku 4.5** — generates the skill graph JSON from scraped docs
- **Serper** — Google search to find the best source URLs for any topic
- **react-force-graph-2d** — force-directed graph canvas rendering
- **react-markdown** — renders node content in the VS Code-style preview panel
Expand Down
32 changes: 17 additions & 15 deletions hypergraph/components/GraphView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ export default function GraphView({
}
}, [data]);

// Configure d3 forces via the ref (the d3Force prop is not in the TS types)
useEffect(() => {
const fg = graphRef.current;
if (!fg) return;
const charge = fg.d3Force("charge");
if (charge) {
charge.strength(-350);
charge.distanceMax(500);
}
const link = fg.d3Force("link");
if (link) {
link.distance((l: any) =>
l.source?.type === "moc" || l.target?.type === "moc" ? 180 : 90
);
}
}, [data]);

const nodeCanvasObject = useCallback(
(node: AnyNode, ctx: CanvasRenderingContext2D, globalScale: number) => {
const type = node.type as NodeType;
Expand Down Expand Up @@ -202,21 +219,6 @@ export default function GraphView({
d3VelocityDecay={0.2}
cooldownTicks={100}
onEngineStop={() => graphRef.current?.zoomToFit(400, 60)}
d3Force={(d3Force, forceName) => {
if (forceName === "charge") {
d3Force.strength(-350); // Increase repulsion (default is usually -30 or so)
d3Force.distanceMax(500);
}
if (forceName === "link") {
d3Force.distance((link: any) => {
// Push MOC nodes much further away from their children
if (link.source.type === "moc" || link.target.type === "moc") {
return 180;
}
return 90; // Standard distance for other links
});
}
}}
/>

{/* Legend */}
Expand Down
22 changes: 11 additions & 11 deletions hypergraph/lib/generator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import OpenAI from "openai";
import Anthropic from "@anthropic-ai/sdk";
import type { GraphNode, SkillGraph, GeneratedFile } from "@/types/graph";

const openai = new OpenAI();
const anthropic = new Anthropic();

const SYSTEM_PROMPT = `You are a domain knowledge graph architect. Given a topic and source material, produce a deeply interconnected JSON skill graph that enables an agent to UNDERSTAND the domain — not merely summarize it. This is the difference between an agent that follows instructions and an agent that understands a domain.

Expand Down Expand Up @@ -61,22 +61,22 @@ export async function generateGraph(
.map((d) => `## Source: ${d.url}\n\n${d.markdown.slice(0, 4000)}`)
.join("\n\n---\n\n");

const response = await openai.chat.completions.create({
model: "gpt-4o",
response_format: { type: "json_object" },
const response = await anthropic.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 8192,
temperature: 0.7,
system: SYSTEM_PROMPT,
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{
role: "user",
content: `Topic: ${topic}\n\nScraped documentation:\n\n${truncatedDocs}`,
content: `Topic: ${topic}\n\nScraped documentation:\n\n${truncatedDocs}\n\nRespond with ONLY the JSON object, no other text.`,
},
],
temperature: 0.7,
max_tokens: 8192,
});

const raw = response.choices[0].message.content;
if (!raw) throw new Error("Empty response from OpenAI");
const raw =
response.content[0].type === "text" ? response.content[0].text : null;
if (!raw) throw new Error("Empty response from Claude");

const parsed = JSON.parse(raw) as SkillGraph;

Expand Down
84 changes: 49 additions & 35 deletions hypergraph/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion hypergraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
"lint": "eslint"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.78.0",
"@hyperbrowser/sdk": "^0.83.3",
"jszip": "^3.10.1",
"next": "16.1.6",
"openai": "^6.22.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-force-graph-2d": "^1.29.1",
Expand Down