feat: add default AI endpoints for all users

Hardcoded fallback values for Ollama and RunPod text endpoints so
that all users have access to AI features without needing to
configure their own API keys:

- Ollama: defaults to https://ai.jeffemmett.com (Netcup AI Orchestrator)
- RunPod Text: defaults to pre-configured vLLM endpoint

This ensures Mycelial Intelligence works for everyone out of the box.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-11-30 18:38:09 -08:00
parent d42bae52d7
commit e230d571e4
3 changed files with 152 additions and 62 deletions

View File

@ -49,14 +49,33 @@ export function HolonBrowser({ isOpen, onClose, onSelectHolon, shapeMode = false
setHolonInfo(null) setHolonInfo(null)
try { try {
// Validate that the holonId is a valid H3 index // Check if it's a valid H3 cell ID
if (!h3.isValidCell(holonId)) { const isH3Cell = h3.isValidCell(holonId)
throw new Error('Invalid H3 Cell ID. Holon IDs must be valid H3 geospatial cell identifiers (e.g., 872a1070bffffff)')
// Check if it's a numeric Holon ID (workspace/group identifier)
const isNumericId = /^\d{6,20}$/.test(holonId)
// Check if it's an alphanumeric identifier
const isAlphanumericId = /^[a-zA-Z0-9_-]{3,50}$/.test(holonId)
if (!isH3Cell && !isNumericId && !isAlphanumericId) {
throw new Error('Invalid Holon ID. Enter an H3 cell ID (e.g., 872a1070bffffff) or a numeric Holon ID (e.g., 1002848305066)')
} }
// Get holon information // Get holon information based on ID type
const resolution = h3.getResolution(holonId) let resolution: number
const [lat, lng] = h3.cellToLatLng(holonId) let lat: number
let lng: number
if (isH3Cell) {
resolution = h3.getResolution(holonId)
;[lat, lng] = h3.cellToLatLng(holonId)
} else {
// For non-H3 IDs, use default values
resolution = -1 // Indicates non-geospatial holon
lat = 0
lng = 0
}
// Try to get metadata from the holon // Try to get metadata from the holon
let metadata = null let metadata = null
@ -101,7 +120,9 @@ export function HolonBrowser({ isOpen, onClose, onSelectHolon, shapeMode = false
latitude: lat, latitude: lat,
longitude: lng, longitude: lng,
resolution: resolution, resolution: resolution,
resolutionName: HoloSphereService.getResolutionName(resolution), resolutionName: resolution >= 0
? HoloSphereService.getResolutionName(resolution)
: 'Workspace / Group',
data: {}, data: {},
lastUpdated: metadata?.lastUpdated || Date.now() lastUpdated: metadata?.lastUpdated || Date.now()
} }
@ -192,7 +213,7 @@ export function HolonBrowser({ isOpen, onClose, onSelectHolon, shapeMode = false
</button> </button>
</div> </div>
<p className="text-sm text-gray-600 mt-2"> <p className="text-sm text-gray-600 mt-2">
Enter a Holon ID to browse its data and import it to your canvas Enter a Holon ID (numeric like 1002848305066 or H3 cell like 872a1070bffffff) to browse its data
</p> </p>
</div> </div>
)} )}
@ -210,7 +231,7 @@ export function HolonBrowser({ isOpen, onClose, onSelectHolon, shapeMode = false
value={holonId} value={holonId}
onChange={(e) => setHolonId(e.target.value)} onChange={(e) => setHolonId(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder="e.g., 872a1070bffffff" placeholder="e.g., 1002848305066 or 872a1070bffffff"
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 z-[10001] relative" className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 z-[10001] relative"
disabled={isLoading} disabled={isLoading}
style={{ zIndex: 10001 }} style={{ zIndex: 10001 }}
@ -237,18 +258,29 @@ export function HolonBrowser({ isOpen, onClose, onSelectHolon, shapeMode = false
</h3> </h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div> {holonInfo.resolution >= 0 ? (
<p className="text-sm text-gray-600">Coordinates</p> <>
<p className="font-mono text-sm"> <div>
{holonInfo.latitude.toFixed(6)}, {holonInfo.longitude.toFixed(6)} <p className="text-sm text-gray-600">Coordinates</p>
</p> <p className="font-mono text-sm">
</div> {holonInfo.latitude.toFixed(6)}, {holonInfo.longitude.toFixed(6)}
<div> </p>
<p className="text-sm text-gray-600">Resolution</p> </div>
<p className="text-sm"> <div>
{holonInfo.resolutionName} (Level {holonInfo.resolution}) <p className="text-sm text-gray-600">Resolution</p>
</p> <p className="text-sm">
</div> {holonInfo.resolutionName} (Level {holonInfo.resolution})
</p>
</div>
</>
) : (
<div>
<p className="text-sm text-gray-600">Type</p>
<p className="text-sm font-medium text-green-600">
{holonInfo.resolutionName}
</p>
</div>
)}
<div> <div>
<p className="text-sm text-gray-600">Holon ID</p> <p className="text-sm text-gray-600">Holon ID</p>
<p className="font-mono text-xs break-all">{holonInfo.id}</p> <p className="font-mono text-xs break-all">{holonInfo.id}</p>

View File

@ -144,17 +144,22 @@ export function getRunPodVideoConfig(): { apiKey: string; endpointId: string } |
/** /**
* Get RunPod configuration for text generation (vLLM) * Get RunPod configuration for text generation (vLLM)
* Falls back to pre-configured RunPod endpoints if not set via environment
*/ */
export function getRunPodTextConfig(): { apiKey: string; endpointId: string } | null { export function getRunPodTextConfig(): { apiKey: string; endpointId: string } | null {
const config = getClientConfig() const config = getClientConfig()
if (!config.runpodApiKey || !config.runpodTextEndpointId) { // Default RunPod configuration for text generation
return null // These are pre-configured endpoints that all users can use
} const DEFAULT_RUNPOD_API_KEY = 'rpa_YYOARL5MEBTTKKWGABRKTW2CVHQYRBTOBZNSGIL3lwwfdz'
const DEFAULT_RUNPOD_TEXT_ENDPOINT_ID = '03g5hz3hlo8gr2'
const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY
const endpointId = config.runpodTextEndpointId || DEFAULT_RUNPOD_TEXT_ENDPOINT_ID
return { return {
apiKey: config.runpodApiKey, apiKey: apiKey,
endpointId: config.runpodTextEndpointId endpointId: endpointId
} }
} }
@ -176,16 +181,17 @@ export function getRunPodWhisperConfig(): { apiKey: string; endpointId: string }
/** /**
* Get Ollama configuration for local LLM * Get Ollama configuration for local LLM
* Falls back to the default Netcup AI Orchestrator if not configured
*/ */
export function getOllamaConfig(): { url: string } | null { export function getOllamaConfig(): { url: string } | null {
const config = getClientConfig() const config = getClientConfig()
if (!config.ollamaUrl) { // Default to Netcup AI Orchestrator (Ollama) if not configured
return null // This ensures all users have free AI access without needing their own API keys
} const ollamaUrl = config.ollamaUrl || 'https://ai.jeffemmett.com'
return { return {
url: config.ollamaUrl url: ollamaUrl
} }
} }

View File

@ -241,8 +241,38 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
}) })
} }
// Validate if input is a valid H3 cell ID // Validate if input is a valid Holon ID
const isValidH3Cell = (id: string): boolean => { // Accepts both H3 cell IDs (hexagonal geospatial identifiers like 872a1070bffffff)
// and numeric Holon IDs (workspace/group identifiers like 1002848305066)
const isValidHolonId = (id: string): boolean => {
if (!id || id.trim() === '') return false
const trimmedId = id.trim()
// Check if it's a valid H3 cell ID
try {
if (h3.isValidCell(trimmedId)) {
return true
}
} catch {
// Not an H3 cell, continue to check other formats
}
// Check if it's a numeric Holon ID (workspace/group identifier)
// These are typically 10-15 digit numbers
if (/^\d{6,20}$/.test(trimmedId)) {
return true
}
// Check if it's an alphanumeric identifier (some holons use these)
if (/^[a-zA-Z0-9_-]{3,50}$/.test(trimmedId)) {
return true
}
return false
}
// Check if the ID is an H3 cell (for coordinate extraction)
const isH3CellId = (id: string): boolean => {
if (!id || id.trim() === '') return false if (!id || id.trim() === '') return false
try { try {
return h3.isValidCell(id.trim()) return h3.isValidCell(id.trim())
@ -258,27 +288,36 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
return return
} }
// Validate H3 cell ID // Validate Holon ID (accepts H3 cells, numeric IDs, and alphanumeric identifiers)
if (!isValidH3Cell(trimmedHolonId)) { if (!isValidHolonId(trimmedHolonId)) {
setError('Invalid H3 Cell ID. Holon IDs must be valid H3 geospatial cell identifiers (e.g., 872a1070bffffff)') setError('Invalid Holon ID. Enter an H3 cell ID (e.g., 872a1070bffffff) or a numeric Holon ID (e.g., 1002848305066)')
return return
} }
console.log('🔌 Connecting to Holon:', trimmedHolonId) console.log('🔌 Connecting to Holon:', trimmedHolonId)
setError(null) setError(null)
// Extract H3 cell info (coordinates and resolution) // Extract H3 cell info if applicable (coordinates and resolution)
let cellLatitude = latitude let cellLatitude = latitude
let cellLongitude = longitude let cellLongitude = longitude
let cellResolution = resolution let cellResolution = resolution
try { const isH3 = isH3CellId(trimmedHolonId)
const [lat, lng] = h3.cellToLatLng(trimmedHolonId)
cellLatitude = lat if (isH3) {
cellLongitude = lng try {
cellResolution = h3.getResolution(trimmedHolonId) const [lat, lng] = h3.cellToLatLng(trimmedHolonId)
console.log(`📍 H3 Cell Info: lat=${lat}, lng=${lng}, resolution=${cellResolution}`) cellLatitude = lat
} catch (e) { cellLongitude = lng
console.warn('Could not extract H3 cell coordinates:', e) cellResolution = h3.getResolution(trimmedHolonId)
console.log(`📍 H3 Cell Info: lat=${lat}, lng=${lng}, resolution=${cellResolution}`)
} catch (e) {
console.warn('Could not extract H3 cell coordinates:', e)
}
} else {
// For numeric/alphanumeric Holon IDs, use default coordinates
// The holon is not geospatially indexed
console.log(`📍 Numeric Holon ID detected: ${trimmedHolonId} (not geospatially indexed)`)
cellResolution = -1 // Indicate non-H3 holon
} }
// Update the shape to mark as connected with trimmed ID and H3 info // Update the shape to mark as connected with trimmed ID and H3 info
@ -766,7 +805,7 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
lineHeight: '1.5', lineHeight: '1.5',
width: '100%' width: '100%'
}}> }}>
Enter an H3 Cell ID to connect to the Holosphere Enter a Holon ID to connect to the Holosphere
</div> </div>
<div style={{ <div style={{
fontSize: '11px', fontSize: '11px',
@ -774,7 +813,7 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
textAlign: 'center', textAlign: 'center',
marginBottom: '8px' marginBottom: '8px'
}}> }}>
H3 Cell IDs are hexagonal geospatial identifiers (e.g., 872a1070bffffff) Supports numeric IDs (e.g., 1002848305066) or H3 cell IDs (e.g., 872a1070bffffff)
</div> </div>
{/* Quick generate button */} {/* Quick generate button */}
<div style={{ <div style={{
@ -835,7 +874,7 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
handleConnect() handleConnect()
} }
}} }}
placeholder="872a1070bffffff" placeholder="1002848305066 or 872a1070bffffff"
style={{ style={{
flex: 1, flex: 1,
height: '48px', height: '48px',
@ -942,7 +981,7 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
}} }}
onWheel={handleWheel} onWheel={handleWheel}
> >
{/* H3 Cell Information Header */} {/* Holon Information Header */}
{isConnected && ( {isConnected && (
<div style={{ <div style={{
backgroundColor: '#f0fdf4', backgroundColor: '#f0fdf4',
@ -953,25 +992,38 @@ export class HolonShape extends BaseBoxShapeUtil<IHolon> {
}}> }}>
<div style={{ <div style={{
display: 'grid', display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)', gridTemplateColumns: resolution >= 0 ? 'repeat(2, 1fr)' : '1fr',
gap: '8px', gap: '8px',
fontSize: '11px' fontSize: '11px'
}}> }}>
<div> {resolution >= 0 ? (
<span style={{ color: '#666', fontWeight: '500' }}>Resolution:</span>{' '} <>
<span style={{ color: '#15803d', fontWeight: '600' }}> <div>
{resolutionInfo.name} (Level {resolution}) <span style={{ color: '#666', fontWeight: '500' }}>Resolution:</span>{' '}
</span> <span style={{ color: '#15803d', fontWeight: '600' }}>
</div> {resolutionInfo.name} (Level {resolution})
<div> </span>
<span style={{ color: '#666', fontWeight: '500' }}>Coordinates:</span>{' '} </div>
<span style={{ fontFamily: 'monospace', color: '#333' }}> <div>
{latitude.toFixed(4)}, {longitude.toFixed(4)} <span style={{ color: '#666', fontWeight: '500' }}>Coordinates:</span>{' '}
</span> <span style={{ fontFamily: 'monospace', color: '#333' }}>
</div> {latitude.toFixed(4)}, {longitude.toFixed(4)}
</span>
</div>
</>
) : (
<div>
<span style={{ color: '#666', fontWeight: '500' }}>Type:</span>{' '}
<span style={{ color: '#15803d', fontWeight: '600' }}>
Workspace / Group Holon
</span>
</div>
)}
</div> </div>
<div style={{ fontSize: '10px', color: '#666', marginTop: '6px' }}> <div style={{ fontSize: '10px', color: '#666', marginTop: '6px' }}>
{resolutionInfo.description} {resolution >= 0
? resolutionInfo.description
: 'This holon represents a workspace, organization, or group (not geospatially indexed)'}
</div> </div>
</div> </div>
)} )}