Initial commit: RA-H Open Source Edition

Local-first knowledge management system with BYO API keys.

Features:
- 3-panel UI (Nodes | Focus | Helpers)
- SQLite + sqlite-vec for vector search
- Agent system (Easy/Hard mode orchestrators)
- Content extraction (YouTube, PDF, web)
- Integrate workflow for connection discovery
- Dimension system with auto-assignment

Tech stack:
- Next.js 15 + TypeScript + Tailwind CSS
- Anthropic (Claude) + OpenAI (GPT) via Vercel AI SDK

Setup:
  npm install && npm rebuild better-sqlite3
  scripts/dev/bootstrap-local.sh
  npm run dev

MIT License
This commit is contained in:
“BeeRad”
2025-12-15 16:14:28 +11:00
commit 733d1c3407
226 changed files with 46231 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
"use client";
import { useState } from 'react';
import { Video, FileText, File, Globe } from 'lucide-react';
import { Node } from '@/types/database';
interface FaviconIconProps {
domain: string;
}
const FaviconIcon = ({ domain }: FaviconIconProps) => {
const [failed, setFailed] = useState(false);
if (failed) {
return <Globe size={16} color="#94a3b8" />;
}
return (
// eslint-disable-next-line @next/next/no-img-element
<img
src={`https://www.google.com/s2/favicons?domain=${domain}&sz=16`}
width={16}
height={16}
alt=""
referrerPolicy="no-referrer"
onError={() => setFailed(true)}
/>
);
};
export function getNodeIcon(node: Node): React.ReactElement {
// No link - show generic file icon
if (!node.link) {
return <File size={16} color="#94a3b8" />;
}
const url = node.link.toLowerCase();
// YouTube videos
if (url.includes('youtube.com') || url.includes('youtu.be')) {
return <Video size={16} color="#FF0000" />;
}
// PDFs and papers
if (url.endsWith('.pdf') || node.metadata?.type === 'paper') {
return <FileText size={16} color="#94a3b8" />;
}
// Website favicon with graceful fallback
try {
const domain = new URL(node.link).hostname;
return <FaviconIcon domain={domain} />;
} catch {
return <Globe size={16} color="#94a3b8" />;
}
}
+38
View File
@@ -0,0 +1,38 @@
"use client";
export async function openExternalUrl(url: string) {
if (typeof window === "undefined") return;
try {
const newWindow = window.open(url, "_blank", "noopener,noreferrer");
if (!newWindow) {
window.location.href = url;
}
} catch (error) {
console.error("[external] window.open failed", error);
window.location.href = url;
}
}
export function shouldOpenExternally(rawUrl: string) {
if (typeof window === "undefined") return false;
try {
const resolved = new URL(rawUrl, window.location.href);
if (!["http:", "https:"].includes(resolved.protocol)) {
return false;
}
const localhostHosts = new Set([
window.location.hostname,
"127.0.0.1",
"localhost",
"0.0.0.0",
]);
return !localhostHosts.has(resolved.hostname);
} catch (error) {
console.error("[external] failed to parse url", rawUrl, error);
return false;
}
}
+31
View File
@@ -0,0 +1,31 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function extractToolContext(toolName?: string, args?: any): string | undefined {
if (!toolName || !args) return undefined;
try {
if (toolName === 'webSearch' && args.query) return `Searching for: ${String(args.query)}`;
if (toolName === 'websiteExtract' && args.url) return `Extracting: ${String(args.url)}`;
if (toolName === 'youtubeExtract' && args.url) return `Transcribing: ${String(args.url)}`;
return undefined;
} catch {
return undefined;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function extractSources(toolName?: string, result?: any): Array<{ url?: string; domain?: string }> | undefined {
if (!result) return undefined;
try {
if (toolName === 'webSearch') {
const items = result?.data?.results || result?.results || [];
if (!Array.isArray(items)) return undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return items.map((r: any) => ({ url: r?.url }));
}
// Generic single-URL tools
const url = result?.data?.url || result?.url;
if (url) return [{ url }];
return undefined;
} catch {
return undefined;
}
}