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
+118
View File
@@ -0,0 +1,118 @@
"use client";
import { CSSProperties, useState } from 'react';
interface ChipProps {
label: string;
onRemove?: () => void;
color?: string;
maxWidth?: number;
icon?: React.ReactNode;
style?: CSSProperties;
}
export default function Chip({
label,
onRemove,
color = '#1a1a1a',
maxWidth = 150,
icon,
style = {}
}: ChipProps) {
const [showTooltip, setShowTooltip] = useState(false);
const needsTruncation = label.length > 20;
const displayLabel = needsTruncation ? label.slice(0, 18) + '...' : label;
return (
<div
style={{
position: 'relative',
display: 'inline-block',
...style
}}
onMouseEnter={() => needsTruncation && setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
<span
style={{
background: color,
color: '#fff',
padding: '2px 6px',
borderRadius: '12px',
fontSize: '10px',
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
maxWidth: `${maxWidth}px`,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
border: '1px solid rgba(255, 255, 255, 0.1)'
}}
>
{icon}
<span style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
{displayLabel}
</span>
{onRemove && (
<button
onClick={onRemove}
style={{
background: 'none',
border: 'none',
color: '#ccc',
cursor: 'pointer',
fontSize: '8px',
padding: '0',
lineHeight: 1,
marginLeft: '2px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '12px',
height: '12px',
borderRadius: '50%',
transition: 'background 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.2)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'none';
}}
>
</button>
)}
</span>
{/* Tooltip */}
{showTooltip && needsTruncation && (
<div
style={{
position: 'absolute',
bottom: '100%',
left: '50%',
transform: 'translateX(-50%)',
marginBottom: '4px',
padding: '4px 8px',
background: '#333',
color: '#fff',
fontSize: '11px',
borderRadius: '3px',
whiteSpace: 'nowrap',
zIndex: 1000,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.5)',
pointerEvents: 'none'
}}
>
{label}
</div>
)}
</div>
);
}
+130
View File
@@ -0,0 +1,130 @@
"use client";
interface ConfirmDialogProps {
open: boolean;
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
onConfirm: () => void;
onCancel: () => void;
}
export default function ConfirmDialog({
open,
title,
message,
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
onConfirm,
onCancel,
}: ConfirmDialogProps) {
if (!open) return null;
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0, 0, 0, 0.8)',
backdropFilter: 'blur(4px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 9999,
padding: '20px'
}}
>
<div
style={{
width: '380px',
maxWidth: '100%',
background: '#050505',
border: '1px solid #1f1f1f',
borderRadius: '12px',
padding: '24px',
boxShadow: '0 25px 65px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.05)'
}}
>
<div style={{
fontSize: '15px',
fontWeight: 600,
color: '#f8fafc',
marginBottom: '12px',
letterSpacing: '0.02em'
}}>
{title}
</div>
<div style={{
fontSize: '13px',
color: '#94a3b8',
marginBottom: '24px',
lineHeight: 1.6
}}>
{message}
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px' }}>
<button
onClick={onCancel}
style={{
padding: '10px 16px',
borderRadius: '8px',
border: '1px solid #1f1f1f',
background: 'transparent',
color: '#94a3b8',
textTransform: 'uppercase',
letterSpacing: '0.05em',
fontSize: '11px',
fontWeight: 500,
cursor: 'pointer',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#0f0f0f';
e.currentTarget.style.borderColor = '#2a2a2a';
e.currentTarget.style.color = '#cbd5f5';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.borderColor = '#1f1f1f';
e.currentTarget.style.color = '#94a3b8';
}}
>
{cancelLabel}
</button>
<button
onClick={onConfirm}
style={{
padding: '10px 16px',
borderRadius: '8px',
border: '1px solid #dc2626',
background: '#7f1d1d',
color: '#fca5a5',
textTransform: 'uppercase',
letterSpacing: '0.05em',
fontSize: '11px',
fontWeight: 500,
cursor: 'pointer',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#991b1b';
e.currentTarget.style.borderColor = '#b91c1c';
e.currentTarget.style.color = '#fecaca';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = '#7f1d1d';
e.currentTarget.style.borderColor = '#dc2626';
e.currentTarget.style.color = '#fca5a5';
}}
>
{confirmLabel}
</button>
</div>
</div>
</div>
);
}
+129
View File
@@ -0,0 +1,129 @@
"use client";
import { ReactNode, CSSProperties } from 'react';
interface EditableSectionProps {
label: string;
summary: ReactNode;
expanded: boolean;
onToggle: () => void;
children: ReactNode;
metadata?: string;
disabled?: boolean;
}
export default function EditableSection({
label,
summary,
expanded,
onToggle,
children,
metadata,
disabled = false
}: EditableSectionProps) {
return (
<div style={{
marginBottom: '12px',
background: '#1a1a1a',
border: '1px solid #2a2a2a',
borderRadius: '4px',
overflow: 'visible'
}}>
<div
onClick={disabled ? undefined : onToggle}
style={{
padding: '8px 12px',
cursor: disabled ? 'default' : 'pointer',
display: 'flex',
alignItems: 'center',
gap: '8px',
transition: 'background 0.2s',
background: expanded ? '#202020' : 'transparent'
}}
onMouseEnter={(e) => {
if (!disabled && !expanded) {
e.currentTarget.style.background = '#1f1f1f';
}
}}
onMouseLeave={(e) => {
if (!disabled && !expanded) {
e.currentTarget.style.background = 'transparent';
}
}}
>
<span style={{
fontSize: '10px',
fontWeight: 600,
color: '#5c9aff',
textTransform: 'uppercase',
letterSpacing: '0.05em',
minWidth: '40px'
}}>
{label}
</span>
<div style={{
flex: 1,
fontSize: '11px',
color: '#888',
lineHeight: '1.4',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
{summary}
</div>
{metadata && (
<span style={{
fontSize: '10px',
color: '#555',
flexShrink: 0
}}>
{metadata}
</span>
)}
{!disabled && (
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
style={{
color: '#555',
transform: expanded ? 'rotate(90deg)' : 'rotate(0deg)',
transition: 'transform 0.2s'
}}
>
<polyline points="9 18 15 12 9 6" />
</svg>
)}
{disabled && (
<span style={{
fontSize: '9px',
color: '#555',
background: '#252525',
padding: '2px 4px',
borderRadius: '2px'
}}>
locked
</span>
)}
</div>
{expanded && (
<div style={{
padding: '12px',
borderTop: '1px solid #2a2a2a',
background: '#161616',
position: 'relative'
}}>
{children}
</div>
)}
</div>
);
}
+204
View File
@@ -0,0 +1,204 @@
"use client";
import { useState, useEffect, useRef } from 'react';
interface InputDialogProps {
open: boolean;
title: string;
message: string;
placeholder?: string;
defaultValue?: string;
confirmLabel?: string;
cancelLabel?: string;
onConfirm: (value: string) => void;
onCancel: () => void;
}
export default function InputDialog({
open,
title,
message,
placeholder = '',
defaultValue = '',
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
onConfirm,
onCancel,
}: InputDialogProps) {
const [inputValue, setInputValue] = useState(defaultValue);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (open) {
setInputValue(defaultValue);
// Focus input when dialog opens
setTimeout(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, 100);
}
}, [open, defaultValue]);
const handleConfirm = () => {
if (inputValue.trim()) {
onConfirm(inputValue.trim());
setInputValue('');
}
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
handleConfirm();
} else if (e.key === 'Escape') {
e.preventDefault();
onCancel();
}
};
if (!open) return null;
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0, 0, 0, 0.8)',
backdropFilter: 'blur(4px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 9999,
padding: '20px'
}}
onClick={(e) => {
if (e.target === e.currentTarget) {
onCancel();
}
}}
>
<div
style={{
width: '380px',
maxWidth: '100%',
background: '#050505',
border: '1px solid #1f1f1f',
borderRadius: '12px',
padding: '24px',
boxShadow: '0 25px 65px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.05)'
}}
>
<div style={{
fontSize: '15px',
fontWeight: 600,
color: '#f8fafc',
marginBottom: '12px',
letterSpacing: '0.02em'
}}>
{title}
</div>
<div style={{
fontSize: '13px',
color: '#94a3b8',
marginBottom: '16px',
lineHeight: 1.6
}}>
{message}
</div>
<input
ref={inputRef}
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
style={{
width: '100%',
padding: '10px 12px',
background: '#0f0f0f',
border: '1px solid #2a2a2a',
borderRadius: '8px',
color: '#f8fafc',
fontSize: '13px',
marginBottom: '24px',
outline: 'none',
transition: 'border-color 0.2s'
}}
onFocus={(e) => {
e.target.style.borderColor = '#3a3a3a';
}}
onBlur={(e) => {
e.target.style.borderColor = '#2a2a2a';
}}
/>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px' }}>
<button
onClick={onCancel}
style={{
padding: '10px 16px',
borderRadius: '8px',
border: '1px solid #1f1f1f',
background: 'transparent',
color: '#94a3b8',
textTransform: 'uppercase',
letterSpacing: '0.05em',
fontSize: '11px',
fontWeight: 500,
cursor: 'pointer',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#0f0f0f';
e.currentTarget.style.borderColor = '#2a2a2a';
e.currentTarget.style.color = '#cbd5f5';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.borderColor = '#1f1f1f';
e.currentTarget.style.color = '#94a3b8';
}}
>
{cancelLabel}
</button>
<button
onClick={handleConfirm}
disabled={!inputValue.trim()}
style={{
padding: '10px 16px',
borderRadius: '8px',
border: '1px solid #22c55e',
background: inputValue.trim() ? '#1a3529' : '#0f1a15',
color: inputValue.trim() ? '#7de8a5' : '#4a5a4f',
textTransform: 'uppercase',
letterSpacing: '0.05em',
fontSize: '11px',
fontWeight: 500,
cursor: inputValue.trim() ? 'pointer' : 'not-allowed',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
if (inputValue.trim()) {
e.currentTarget.style.background = '#1f3d2f';
e.currentTarget.style.borderColor = '#2dd47e';
e.currentTarget.style.color = '#9ef5b8';
}
}}
onMouseLeave={(e) => {
if (inputValue.trim()) {
e.currentTarget.style.background = '#1a3529';
e.currentTarget.style.borderColor = '#22c55e';
e.currentTarget.style.color = '#7de8a5';
}
}}
>
{confirmLabel}
</button>
</div>
</div>
</div>
);
}
+67
View File
@@ -0,0 +1,67 @@
"use client";
import { ReactNode, CSSProperties } from 'react';
interface PanelHeaderProps {
title: string;
leftContent?: ReactNode;
rightContent?: ReactNode;
className?: string;
style?: CSSProperties;
}
export default function PanelHeader({
title,
leftContent,
rightContent,
className = '',
style = {}
}: PanelHeaderProps) {
return (
<div
className={`panel-header ${className}`}
style={{
height: '48px',
padding: '0 12px',
borderBottom: '1px solid rgba(42, 42, 42, 0.8)',
background: '#0f0f0f',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexShrink: 0,
...style
}}
>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
flex: 1,
minWidth: 0
}}>
<span style={{
textTransform: 'uppercase',
fontSize: '12px',
fontWeight: 500,
color: '#888',
letterSpacing: '0.05em',
flexShrink: 0
}}>
{title}
</span>
{leftContent}
</div>
{rightContent && (
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
flexShrink: 0
}}>
{rightContent}
</div>
)}
</div>
);
}