Spaces:
Paused
Paused
Update domain-types
Browse files- packages/domain-types/src/evolution.ts +22 -22
- packages/domain-types/src/index.ts +27 -23
- packages/domain-types/src/memory.ts +26 -26
- packages/domain-types/src/pal.ts +29 -29
- packages/domain-types/src/showpad.ts +169 -169
- packages/domain-types/src/srag.ts +20 -20
- packages/domain-types/src/vr/ForceDirectedLayout.ts +685 -0
- packages/domain-types/src/vr/VisualNode.ts +782 -0
packages/domain-types/src/evolution.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
| 1 |
-
// Evolution domain entities
|
| 2 |
-
|
| 3 |
-
export interface AgentPrompt {
|
| 4 |
-
id: number;
|
| 5 |
-
agentId: string;
|
| 6 |
-
version: number;
|
| 7 |
-
promptText: string;
|
| 8 |
-
createdAt: Date;
|
| 9 |
-
createdBy: string;
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
export interface AgentRun {
|
| 13 |
-
id: number;
|
| 14 |
-
agentId: string;
|
| 15 |
-
promptVersion: number;
|
| 16 |
-
inputSummary: string;
|
| 17 |
-
outputSummary: string;
|
| 18 |
-
kpiName: string;
|
| 19 |
-
kpiDelta: number;
|
| 20 |
-
runContext: Record<string, any>;
|
| 21 |
-
createdAt: Date;
|
| 22 |
-
}
|
|
|
|
| 1 |
+
// Evolution domain entities
|
| 2 |
+
|
| 3 |
+
export interface AgentPrompt {
|
| 4 |
+
id: number;
|
| 5 |
+
agentId: string;
|
| 6 |
+
version: number;
|
| 7 |
+
promptText: string;
|
| 8 |
+
createdAt: Date;
|
| 9 |
+
createdBy: string;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export interface AgentRun {
|
| 13 |
+
id: number;
|
| 14 |
+
agentId: string;
|
| 15 |
+
promptVersion: number;
|
| 16 |
+
inputSummary: string;
|
| 17 |
+
outputSummary: string;
|
| 18 |
+
kpiName: string;
|
| 19 |
+
kpiDelta: number;
|
| 20 |
+
runContext: Record<string, any>;
|
| 21 |
+
createdAt: Date;
|
| 22 |
+
}
|
packages/domain-types/src/index.ts
CHANGED
|
@@ -1,23 +1,27 @@
|
|
| 1 |
-
// Domain types for the widget framework
|
| 2 |
-
|
| 3 |
-
export interface WidgetContext {
|
| 4 |
-
userId: string;
|
| 5 |
-
organizationId: string;
|
| 6 |
-
boardId: string;
|
| 7 |
-
widgetId: string;
|
| 8 |
-
nowIso: string;
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
export interface WidgetDefinition {
|
| 12 |
-
id: string;
|
| 13 |
-
title: string;
|
| 14 |
-
icon: string;
|
| 15 |
-
init(context: WidgetContext): Promise<void>;
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
// Re-export specialized domain types
|
| 19 |
-
export * from './memory';
|
| 20 |
-
export * from './srag';
|
| 21 |
-
export * from './evolution';
|
| 22 |
-
export * from './pal';
|
| 23 |
-
export * from './showpad';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Domain types for the widget framework
|
| 2 |
+
|
| 3 |
+
export interface WidgetContext {
|
| 4 |
+
userId: string;
|
| 5 |
+
organizationId: string;
|
| 6 |
+
boardId: string;
|
| 7 |
+
widgetId: string;
|
| 8 |
+
nowIso: string;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export interface WidgetDefinition {
|
| 12 |
+
id: string;
|
| 13 |
+
title: string;
|
| 14 |
+
icon: string;
|
| 15 |
+
init(context: WidgetContext): Promise<void>;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
// Re-export specialized domain types
|
| 19 |
+
export * from './memory';
|
| 20 |
+
export * from './srag';
|
| 21 |
+
export * from './evolution';
|
| 22 |
+
export * from './pal';
|
| 23 |
+
export * from './showpad';
|
| 24 |
+
|
| 25 |
+
// VR types
|
| 26 |
+
export * from './vr/VisualNode';
|
| 27 |
+
export * from './vr/ForceDirectedLayout';
|
packages/domain-types/src/memory.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
| 1 |
-
// Memory domain entities
|
| 2 |
-
|
| 3 |
-
export interface MemoryEntity {
|
| 4 |
-
id: number;
|
| 5 |
-
orgId: string;
|
| 6 |
-
userId?: string;
|
| 7 |
-
entityType: string;
|
| 8 |
-
content: string;
|
| 9 |
-
importance: number;
|
| 10 |
-
createdAt: Date;
|
| 11 |
-
}
|
| 12 |
-
|
| 13 |
-
export interface MemoryRelation {
|
| 14 |
-
id: number;
|
| 15 |
-
orgId: string;
|
| 16 |
-
sourceId: number;
|
| 17 |
-
targetId: number;
|
| 18 |
-
relationType: 'depends_on' | 'contradicts' | 'same_project' | 'related_to';
|
| 19 |
-
createdAt: Date;
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
export interface MemoryTag {
|
| 23 |
-
id: number;
|
| 24 |
-
entityId: number;
|
| 25 |
-
tag: string;
|
| 26 |
-
}
|
|
|
|
| 1 |
+
// Memory domain entities
|
| 2 |
+
|
| 3 |
+
export interface MemoryEntity {
|
| 4 |
+
id: number;
|
| 5 |
+
orgId: string;
|
| 6 |
+
userId?: string;
|
| 7 |
+
entityType: string;
|
| 8 |
+
content: string;
|
| 9 |
+
importance: number;
|
| 10 |
+
createdAt: Date;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export interface MemoryRelation {
|
| 14 |
+
id: number;
|
| 15 |
+
orgId: string;
|
| 16 |
+
sourceId: number;
|
| 17 |
+
targetId: number;
|
| 18 |
+
relationType: 'depends_on' | 'contradicts' | 'same_project' | 'related_to';
|
| 19 |
+
createdAt: Date;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export interface MemoryTag {
|
| 23 |
+
id: number;
|
| 24 |
+
entityId: number;
|
| 25 |
+
tag: string;
|
| 26 |
+
}
|
packages/domain-types/src/pal.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
| 1 |
-
// PAL domain entities
|
| 2 |
-
|
| 3 |
-
export interface PalUserProfile {
|
| 4 |
-
id: number;
|
| 5 |
-
userId: string;
|
| 6 |
-
orgId: string;
|
| 7 |
-
preferenceTone: string;
|
| 8 |
-
createdAt: Date;
|
| 9 |
-
updatedAt: Date;
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
export interface PalFocusWindow {
|
| 13 |
-
id: number;
|
| 14 |
-
userId: string;
|
| 15 |
-
orgId: string;
|
| 16 |
-
weekday: number;
|
| 17 |
-
startHour: number;
|
| 18 |
-
endHour: number;
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
export interface PalEvent {
|
| 22 |
-
id: number;
|
| 23 |
-
userId: string;
|
| 24 |
-
orgId: string;
|
| 25 |
-
eventType: string;
|
| 26 |
-
payload: Record<string, any>;
|
| 27 |
-
detectedStressLevel?: 'low' | 'medium' | 'high';
|
| 28 |
-
createdAt: Date;
|
| 29 |
-
}
|
|
|
|
| 1 |
+
// PAL domain entities
|
| 2 |
+
|
| 3 |
+
export interface PalUserProfile {
|
| 4 |
+
id: number;
|
| 5 |
+
userId: string;
|
| 6 |
+
orgId: string;
|
| 7 |
+
preferenceTone: string;
|
| 8 |
+
createdAt: Date;
|
| 9 |
+
updatedAt: Date;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export interface PalFocusWindow {
|
| 13 |
+
id: number;
|
| 14 |
+
userId: string;
|
| 15 |
+
orgId: string;
|
| 16 |
+
weekday: number;
|
| 17 |
+
startHour: number;
|
| 18 |
+
endHour: number;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export interface PalEvent {
|
| 22 |
+
id: number;
|
| 23 |
+
userId: string;
|
| 24 |
+
orgId: string;
|
| 25 |
+
eventType: string;
|
| 26 |
+
payload: Record<string, any>;
|
| 27 |
+
detectedStressLevel?: 'low' | 'medium' | 'high';
|
| 28 |
+
createdAt: Date;
|
| 29 |
+
}
|
packages/domain-types/src/showpad.ts
CHANGED
|
@@ -1,169 +1,169 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* TDC Showpad Integration Types
|
| 3 |
-
*
|
| 4 |
-
* Shared type definitions for Showpad brand asset integration
|
| 5 |
-
*/
|
| 6 |
-
|
| 7 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 8 |
-
// Authentication Types
|
| 9 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 10 |
-
|
| 11 |
-
export interface ShowpadCredentials {
|
| 12 |
-
subdomain: string;
|
| 13 |
-
username?: string;
|
| 14 |
-
password?: string;
|
| 15 |
-
clientId?: string;
|
| 16 |
-
clientSecret?: string;
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
export interface ShowpadTokenResponse {
|
| 20 |
-
access_token: string;
|
| 21 |
-
refresh_token: string;
|
| 22 |
-
expires_in: number;
|
| 23 |
-
token_type: string;
|
| 24 |
-
scope: string;
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
export interface ShowpadAuthState {
|
| 28 |
-
isAuthenticated: boolean;
|
| 29 |
-
accessToken: string | null;
|
| 30 |
-
refreshToken: string | null;
|
| 31 |
-
expiresAt: number | null;
|
| 32 |
-
scope: string[];
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 36 |
-
// Asset Types
|
| 37 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 38 |
-
|
| 39 |
-
export type ShowpadAssetType = 'document' | 'image' | 'video' | 'other';
|
| 40 |
-
|
| 41 |
-
export interface ShowpadAsset {
|
| 42 |
-
id: string;
|
| 43 |
-
slug: string;
|
| 44 |
-
name: string;
|
| 45 |
-
displayName: string;
|
| 46 |
-
type: ShowpadAssetType;
|
| 47 |
-
description?: string;
|
| 48 |
-
tags: string[];
|
| 49 |
-
permissions: ShowpadAssetPermissions;
|
| 50 |
-
previewUrl?: string;
|
| 51 |
-
downloadUrl?: string;
|
| 52 |
-
metadata: ShowpadAssetMetadata;
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
export interface ShowpadAssetPermissions {
|
| 56 |
-
share: boolean;
|
| 57 |
-
annotate: boolean;
|
| 58 |
-
download: boolean;
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
export interface ShowpadAssetMetadata {
|
| 62 |
-
size?: number;
|
| 63 |
-
mimeType?: string;
|
| 64 |
-
dimensions?: { width: number; height: number };
|
| 65 |
-
createdAt?: string;
|
| 66 |
-
modifiedAt?: string;
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
export interface ShowpadAssetSearchOptions {
|
| 70 |
-
query?: string;
|
| 71 |
-
tags?: string[];
|
| 72 |
-
type?: ShowpadAssetType;
|
| 73 |
-
limit?: number;
|
| 74 |
-
offset?: number;
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
export interface ShowpadCachedAsset {
|
| 78 |
-
asset: ShowpadAsset;
|
| 79 |
-
localPath: string;
|
| 80 |
-
cachedAt: number;
|
| 81 |
-
size: number;
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 85 |
-
// Brand Types
|
| 86 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 87 |
-
|
| 88 |
-
export interface ShowpadBrandColors {
|
| 89 |
-
primary: string[];
|
| 90 |
-
secondary: string[];
|
| 91 |
-
accent: string[];
|
| 92 |
-
backgrounds: string[];
|
| 93 |
-
text: string[];
|
| 94 |
-
success: string;
|
| 95 |
-
warning: string;
|
| 96 |
-
error: string;
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
export interface ShowpadTypography {
|
| 100 |
-
headline: ShowpadFontConfig;
|
| 101 |
-
body: ShowpadFontConfig;
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
export interface ShowpadFontConfig {
|
| 105 |
-
family: string;
|
| 106 |
-
sizes: { [key: string]: number };
|
| 107 |
-
weights: { [key: string]: number };
|
| 108 |
-
lineHeights: { [key: string]: number };
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
export interface ShowpadLogoSpecs {
|
| 112 |
-
primary: {
|
| 113 |
-
minWidth: number;
|
| 114 |
-
clearSpace: number;
|
| 115 |
-
colorVariants: string[];
|
| 116 |
-
formats: string[];
|
| 117 |
-
};
|
| 118 |
-
icon: {
|
| 119 |
-
minSize: number;
|
| 120 |
-
formats: string[];
|
| 121 |
-
};
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
export interface ShowpadBrandContext {
|
| 125 |
-
colors: ShowpadBrandColors;
|
| 126 |
-
typography: ShowpadTypography;
|
| 127 |
-
logos: ShowpadLogoSpecs;
|
| 128 |
-
spacing: { [key: string]: number };
|
| 129 |
-
borderRadius: { [key: string]: number };
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 133 |
-
// PowerPoint Integration Types
|
| 134 |
-
//
|
| 135 |
-
|
| 136 |
-
export interface ShowpadPPTColorPalette {
|
| 137 |
-
background: string;
|
| 138 |
-
text: string;
|
| 139 |
-
accent1: string;
|
| 140 |
-
accent2: string;
|
| 141 |
-
accent3: string;
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
export interface ShowpadPPTFontConfig {
|
| 145 |
-
headlineFont: string;
|
| 146 |
-
bodyFont: string;
|
| 147 |
-
titleSize: number;
|
| 148 |
-
bodySize: number;
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
// ════════════════════════════════════════════════════════════════════════════
|
| 152 |
-
// Event Types
|
| 153 |
-
//
|
| 154 |
-
|
| 155 |
-
export interface ShowpadAuthEvent {
|
| 156 |
-
type: 'authenticated' | 'token_refreshed' | 'logged_out' | 'auth_error' | 'token_refresh_failed';
|
| 157 |
-
scope?: string[];
|
| 158 |
-
error?: Error;
|
| 159 |
-
}
|
| 160 |
-
|
| 161 |
-
export interface ShowpadAssetEvent {
|
| 162 |
-
type: 'asset_downloaded' | 'sync_started' | 'sync_completed' | 'sync_error' | 'cache_cleared';
|
| 163 |
-
assetId?: string;
|
| 164 |
-
path?: string;
|
| 165 |
-
templates?: number;
|
| 166 |
-
logos?: number;
|
| 167 |
-
guidelines?: number;
|
| 168 |
-
error?: Error;
|
| 169 |
-
}
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* TDC Showpad Integration Types
|
| 3 |
+
*
|
| 4 |
+
* Shared type definitions for Showpad brand asset integration
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 8 |
+
// Authentication Types
|
| 9 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 10 |
+
|
| 11 |
+
export interface ShowpadCredentials {
|
| 12 |
+
subdomain: string;
|
| 13 |
+
username?: string;
|
| 14 |
+
password?: string;
|
| 15 |
+
clientId?: string;
|
| 16 |
+
clientSecret?: string;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
export interface ShowpadTokenResponse {
|
| 20 |
+
access_token: string;
|
| 21 |
+
refresh_token: string;
|
| 22 |
+
expires_in: number;
|
| 23 |
+
token_type: string;
|
| 24 |
+
scope: string;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
export interface ShowpadAuthState {
|
| 28 |
+
isAuthenticated: boolean;
|
| 29 |
+
accessToken: string | null;
|
| 30 |
+
refreshToken: string | null;
|
| 31 |
+
expiresAt: number | null;
|
| 32 |
+
scope: string[];
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 36 |
+
// Asset Types
|
| 37 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 38 |
+
|
| 39 |
+
export type ShowpadAssetType = 'document' | 'image' | 'video' | 'other';
|
| 40 |
+
|
| 41 |
+
export interface ShowpadAsset {
|
| 42 |
+
id: string;
|
| 43 |
+
slug: string;
|
| 44 |
+
name: string;
|
| 45 |
+
displayName: string;
|
| 46 |
+
type: ShowpadAssetType;
|
| 47 |
+
description?: string;
|
| 48 |
+
tags: string[];
|
| 49 |
+
permissions: ShowpadAssetPermissions;
|
| 50 |
+
previewUrl?: string;
|
| 51 |
+
downloadUrl?: string;
|
| 52 |
+
metadata: ShowpadAssetMetadata;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
export interface ShowpadAssetPermissions {
|
| 56 |
+
share: boolean;
|
| 57 |
+
annotate: boolean;
|
| 58 |
+
download: boolean;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
export interface ShowpadAssetMetadata {
|
| 62 |
+
size?: number;
|
| 63 |
+
mimeType?: string;
|
| 64 |
+
dimensions?: { width: number; height: number };
|
| 65 |
+
createdAt?: string;
|
| 66 |
+
modifiedAt?: string;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
export interface ShowpadAssetSearchOptions {
|
| 70 |
+
query?: string;
|
| 71 |
+
tags?: string[];
|
| 72 |
+
type?: ShowpadAssetType;
|
| 73 |
+
limit?: number;
|
| 74 |
+
offset?: number;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
export interface ShowpadCachedAsset {
|
| 78 |
+
asset: ShowpadAsset;
|
| 79 |
+
localPath: string;
|
| 80 |
+
cachedAt: number;
|
| 81 |
+
size: number;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 85 |
+
// Brand Types
|
| 86 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 87 |
+
|
| 88 |
+
export interface ShowpadBrandColors {
|
| 89 |
+
primary: string[];
|
| 90 |
+
secondary: string[];
|
| 91 |
+
accent: string[];
|
| 92 |
+
backgrounds: string[];
|
| 93 |
+
text: string[];
|
| 94 |
+
success: string;
|
| 95 |
+
warning: string;
|
| 96 |
+
error: string;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
export interface ShowpadTypography {
|
| 100 |
+
headline: ShowpadFontConfig;
|
| 101 |
+
body: ShowpadFontConfig;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
export interface ShowpadFontConfig {
|
| 105 |
+
family: string;
|
| 106 |
+
sizes: { [key: string]: number };
|
| 107 |
+
weights: { [key: string]: number };
|
| 108 |
+
lineHeights: { [key: string]: number };
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
export interface ShowpadLogoSpecs {
|
| 112 |
+
primary: {
|
| 113 |
+
minWidth: number;
|
| 114 |
+
clearSpace: number;
|
| 115 |
+
colorVariants: string[];
|
| 116 |
+
formats: string[];
|
| 117 |
+
};
|
| 118 |
+
icon: {
|
| 119 |
+
minSize: number;
|
| 120 |
+
formats: string[];
|
| 121 |
+
};
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
export interface ShowpadBrandContext {
|
| 125 |
+
colors: ShowpadBrandColors;
|
| 126 |
+
typography: ShowpadTypography;
|
| 127 |
+
logos: ShowpadLogoSpecs;
|
| 128 |
+
spacing: { [key: string]: number };
|
| 129 |
+
borderRadius: { [key: string]: number };
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 133 |
+
// PowerPoint Integration Types
|
| 134 |
+
// ═══════════════════════════════════════════��════════════════════════════════
|
| 135 |
+
|
| 136 |
+
export interface ShowpadPPTColorPalette {
|
| 137 |
+
background: string;
|
| 138 |
+
text: string;
|
| 139 |
+
accent1: string;
|
| 140 |
+
accent2: string;
|
| 141 |
+
accent3: string;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
export interface ShowpadPPTFontConfig {
|
| 145 |
+
headlineFont: string;
|
| 146 |
+
bodyFont: string;
|
| 147 |
+
titleSize: number;
|
| 148 |
+
bodySize: number;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 152 |
+
// Event Types
|
| 153 |
+
// ════════════════════════════════════════════════════════════════════════════
|
| 154 |
+
|
| 155 |
+
export interface ShowpadAuthEvent {
|
| 156 |
+
type: 'authenticated' | 'token_refreshed' | 'logged_out' | 'auth_error' | 'token_refresh_failed';
|
| 157 |
+
scope?: string[];
|
| 158 |
+
error?: Error;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
export interface ShowpadAssetEvent {
|
| 162 |
+
type: 'asset_downloaded' | 'sync_started' | 'sync_completed' | 'sync_error' | 'cache_cleared';
|
| 163 |
+
assetId?: string;
|
| 164 |
+
path?: string;
|
| 165 |
+
templates?: number;
|
| 166 |
+
logos?: number;
|
| 167 |
+
guidelines?: number;
|
| 168 |
+
error?: Error;
|
| 169 |
+
}
|
packages/domain-types/src/srag.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
| 1 |
-
// SRAG domain entities
|
| 2 |
-
|
| 3 |
-
export interface RawDocument {
|
| 4 |
-
id: number;
|
| 5 |
-
orgId: string;
|
| 6 |
-
sourceType: string;
|
| 7 |
-
sourcePath: string;
|
| 8 |
-
content: string;
|
| 9 |
-
createdAt: Date;
|
| 10 |
-
}
|
| 11 |
-
|
| 12 |
-
export interface StructuredFact {
|
| 13 |
-
id: number;
|
| 14 |
-
orgId: string;
|
| 15 |
-
docId?: number;
|
| 16 |
-
factType: string;
|
| 17 |
-
jsonPayload: Record<string, any>;
|
| 18 |
-
occurredAt?: Date;
|
| 19 |
-
createdAt: Date;
|
| 20 |
-
}
|
|
|
|
| 1 |
+
// SRAG domain entities
|
| 2 |
+
|
| 3 |
+
export interface RawDocument {
|
| 4 |
+
id: number;
|
| 5 |
+
orgId: string;
|
| 6 |
+
sourceType: string;
|
| 7 |
+
sourcePath: string;
|
| 8 |
+
content: string;
|
| 9 |
+
createdAt: Date;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export interface StructuredFact {
|
| 13 |
+
id: number;
|
| 14 |
+
orgId: string;
|
| 15 |
+
docId?: number;
|
| 16 |
+
factType: string;
|
| 17 |
+
jsonPayload: Record<string, any>;
|
| 18 |
+
occurredAt?: Date;
|
| 19 |
+
createdAt: Date;
|
| 20 |
+
}
|
packages/domain-types/src/vr/ForceDirectedLayout.ts
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* FORCE-DIRECTED GRAPH LAYOUT - 3D with Gravity
|
| 3 |
+
*
|
| 4 |
+
* Physics simulation for node positioning in VR space.
|
| 5 |
+
* Nodes attract/repel each other and settle on a virtual surface.
|
| 6 |
+
*
|
| 7 |
+
* Based on DeepSeek's mathematical specifications.
|
| 8 |
+
*
|
| 9 |
+
* @author The Captain (Claude) + The Specialist (DeepSeek)
|
| 10 |
+
* @date 2025-12-17
|
| 11 |
+
*/
|
| 12 |
+
|
| 13 |
+
import type { Vector3, VisualNode } from './VisualNode';
|
| 14 |
+
|
| 15 |
+
// =============================================================================
|
| 16 |
+
// CONFIGURATION
|
| 17 |
+
// =============================================================================
|
| 18 |
+
|
| 19 |
+
export interface ForceLayoutConfig {
|
| 20 |
+
// Repulsion between nodes (Coulomb's law)
|
| 21 |
+
repulsionStrength: number; // Default: 1000
|
| 22 |
+
repulsionDistance: number; // Max distance for repulsion: 100
|
| 23 |
+
|
| 24 |
+
// Attraction along edges (Hooke's law)
|
| 25 |
+
attractionStrength: number; // Default: 0.1
|
| 26 |
+
idealEdgeLength: number; // Default: 5
|
| 27 |
+
|
| 28 |
+
// Gravity (pulls nodes toward surface/center)
|
| 29 |
+
gravityStrength: number; // Default: 0.5
|
| 30 |
+
gravityTarget: Vector3; // Default: { x: 0, y: 0, z: 0 }
|
| 31 |
+
gravityType: 'point' | 'plane'; // 'plane' for table surface
|
| 32 |
+
|
| 33 |
+
// Surface/Table
|
| 34 |
+
surfaceY: number; // Y position of virtual table: 0
|
| 35 |
+
surfaceStiffness: number; // How hard the surface pushes back: 2.0
|
| 36 |
+
surfaceFriction: number; // Damping on surface: 0.8
|
| 37 |
+
|
| 38 |
+
// Damping (energy loss)
|
| 39 |
+
velocityDamping: number; // Default: 0.9 (10% energy loss per tick)
|
| 40 |
+
|
| 41 |
+
// Simulation
|
| 42 |
+
timeStep: number; // Default: 0.016 (60 FPS)
|
| 43 |
+
maxVelocity: number; // Default: 10
|
| 44 |
+
minMovement: number; // Threshold to consider "settled": 0.01
|
| 45 |
+
|
| 46 |
+
// Boundaries
|
| 47 |
+
bounds: {
|
| 48 |
+
min: Vector3;
|
| 49 |
+
max: Vector3;
|
| 50 |
+
};
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
export const DEFAULT_CONFIG: ForceLayoutConfig = {
|
| 54 |
+
repulsionStrength: 1000,
|
| 55 |
+
repulsionDistance: 100,
|
| 56 |
+
attractionStrength: 0.1,
|
| 57 |
+
idealEdgeLength: 5,
|
| 58 |
+
gravityStrength: 0.5,
|
| 59 |
+
gravityTarget: { x: 0, y: 0, z: 0 },
|
| 60 |
+
gravityType: 'plane',
|
| 61 |
+
surfaceY: 0,
|
| 62 |
+
surfaceStiffness: 2.0,
|
| 63 |
+
surfaceFriction: 0.8,
|
| 64 |
+
velocityDamping: 0.9,
|
| 65 |
+
timeStep: 0.016,
|
| 66 |
+
maxVelocity: 10,
|
| 67 |
+
minMovement: 0.01,
|
| 68 |
+
bounds: {
|
| 69 |
+
min: { x: -100, y: -10, z: -100 },
|
| 70 |
+
max: { x: 100, y: 100, z: 100 },
|
| 71 |
+
},
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
// =============================================================================
|
| 75 |
+
// EDGE DEFINITION
|
| 76 |
+
// =============================================================================
|
| 77 |
+
|
| 78 |
+
export interface GraphEdge {
|
| 79 |
+
source: string; // Node ID
|
| 80 |
+
target: string; // Node ID
|
| 81 |
+
weight: number; // 0-1, affects attraction strength
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// =============================================================================
|
| 85 |
+
// SIMULATION STATE
|
| 86 |
+
// =============================================================================
|
| 87 |
+
|
| 88 |
+
export interface SimulationState {
|
| 89 |
+
nodes: Map<string, VisualNode>;
|
| 90 |
+
edges: GraphEdge[];
|
| 91 |
+
config: ForceLayoutConfig;
|
| 92 |
+
isRunning: boolean;
|
| 93 |
+
iteration: number;
|
| 94 |
+
totalEnergy: number;
|
| 95 |
+
settled: boolean;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// =============================================================================
|
| 99 |
+
// VECTOR MATH UTILITIES
|
| 100 |
+
// =============================================================================
|
| 101 |
+
|
| 102 |
+
export const vec3 = {
|
| 103 |
+
add(a: Vector3, b: Vector3): Vector3 {
|
| 104 |
+
return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
|
| 105 |
+
},
|
| 106 |
+
|
| 107 |
+
subtract(a: Vector3, b: Vector3): Vector3 {
|
| 108 |
+
return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
|
| 109 |
+
},
|
| 110 |
+
|
| 111 |
+
multiply(v: Vector3, scalar: number): Vector3 {
|
| 112 |
+
return { x: v.x * scalar, y: v.y * scalar, z: v.z * scalar };
|
| 113 |
+
},
|
| 114 |
+
|
| 115 |
+
divide(v: Vector3, scalar: number): Vector3 {
|
| 116 |
+
if (scalar === 0) return { x: 0, y: 0, z: 0 };
|
| 117 |
+
return { x: v.x / scalar, y: v.y / scalar, z: v.z / scalar };
|
| 118 |
+
},
|
| 119 |
+
|
| 120 |
+
length(v: Vector3): number {
|
| 121 |
+
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
| 122 |
+
},
|
| 123 |
+
|
| 124 |
+
lengthSquared(v: Vector3): number {
|
| 125 |
+
return v.x * v.x + v.y * v.y + v.z * v.z;
|
| 126 |
+
},
|
| 127 |
+
|
| 128 |
+
normalize(v: Vector3): Vector3 {
|
| 129 |
+
const len = vec3.length(v);
|
| 130 |
+
if (len === 0) return { x: 0, y: 0, z: 0 };
|
| 131 |
+
return vec3.divide(v, len);
|
| 132 |
+
},
|
| 133 |
+
|
| 134 |
+
distance(a: Vector3, b: Vector3): number {
|
| 135 |
+
return vec3.length(vec3.subtract(a, b));
|
| 136 |
+
},
|
| 137 |
+
|
| 138 |
+
clamp(v: Vector3, min: number, max: number): Vector3 {
|
| 139 |
+
const len = vec3.length(v);
|
| 140 |
+
if (len === 0) return v;
|
| 141 |
+
if (len < min) return vec3.multiply(vec3.normalize(v), min);
|
| 142 |
+
if (len > max) return vec3.multiply(vec3.normalize(v), max);
|
| 143 |
+
return v;
|
| 144 |
+
},
|
| 145 |
+
|
| 146 |
+
zero(): Vector3 {
|
| 147 |
+
return { x: 0, y: 0, z: 0 };
|
| 148 |
+
},
|
| 149 |
+
|
| 150 |
+
random(scale: number = 1): Vector3 {
|
| 151 |
+
return {
|
| 152 |
+
x: (Math.random() - 0.5) * 2 * scale,
|
| 153 |
+
y: (Math.random() - 0.5) * 2 * scale,
|
| 154 |
+
z: (Math.random() - 0.5) * 2 * scale,
|
| 155 |
+
};
|
| 156 |
+
},
|
| 157 |
+
};
|
| 158 |
+
|
| 159 |
+
// =============================================================================
|
| 160 |
+
// FORCE CALCULATIONS
|
| 161 |
+
// =============================================================================
|
| 162 |
+
|
| 163 |
+
/**
|
| 164 |
+
* Coulomb's Law: Repulsion between nodes
|
| 165 |
+
* F = k * (q1 * q2) / r²
|
| 166 |
+
*
|
| 167 |
+
* Nodes push each other away to prevent overlap.
|
| 168 |
+
*/
|
| 169 |
+
function calculateRepulsion(
|
| 170 |
+
nodeA: VisualNode,
|
| 171 |
+
nodeB: VisualNode,
|
| 172 |
+
config: ForceLayoutConfig
|
| 173 |
+
): Vector3 {
|
| 174 |
+
const delta = vec3.subtract(nodeA.position, nodeB.position);
|
| 175 |
+
const distanceSquared = vec3.lengthSquared(delta);
|
| 176 |
+
const distance = Math.sqrt(distanceSquared);
|
| 177 |
+
|
| 178 |
+
// Skip if too close (avoid division by zero) or too far
|
| 179 |
+
if (distance < 0.1) {
|
| 180 |
+
// Random jitter to separate overlapping nodes
|
| 181 |
+
return vec3.random(config.repulsionStrength * 0.1);
|
| 182 |
+
}
|
| 183 |
+
if (distance > config.repulsionDistance) {
|
| 184 |
+
return vec3.zero();
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
// Coulomb's law: F = k / r²
|
| 188 |
+
const forceMagnitude = config.repulsionStrength / distanceSquared;
|
| 189 |
+
|
| 190 |
+
// Direction: away from nodeB
|
| 191 |
+
const direction = vec3.normalize(delta);
|
| 192 |
+
return vec3.multiply(direction, forceMagnitude);
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
/**
|
| 196 |
+
* Hooke's Law: Attraction along edges
|
| 197 |
+
* F = -k * (x - x₀)
|
| 198 |
+
*
|
| 199 |
+
* Connected nodes pull toward each other to ideal distance.
|
| 200 |
+
*/
|
| 201 |
+
function calculateAttraction(
|
| 202 |
+
nodeA: VisualNode,
|
| 203 |
+
nodeB: VisualNode,
|
| 204 |
+
edge: GraphEdge,
|
| 205 |
+
config: ForceLayoutConfig
|
| 206 |
+
): Vector3 {
|
| 207 |
+
const delta = vec3.subtract(nodeB.position, nodeA.position);
|
| 208 |
+
const distance = vec3.length(delta);
|
| 209 |
+
|
| 210 |
+
if (distance < 0.1) return vec3.zero();
|
| 211 |
+
|
| 212 |
+
// Hooke's law: F = k * (distance - idealLength)
|
| 213 |
+
const displacement = distance - config.idealEdgeLength;
|
| 214 |
+
const forceMagnitude = config.attractionStrength * displacement * edge.weight;
|
| 215 |
+
|
| 216 |
+
// Direction: toward nodeB
|
| 217 |
+
const direction = vec3.normalize(delta);
|
| 218 |
+
return vec3.multiply(direction, forceMagnitude);
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
/**
|
| 222 |
+
* Gravity: Pull toward center or surface
|
| 223 |
+
*/
|
| 224 |
+
function calculateGravity(
|
| 225 |
+
node: VisualNode,
|
| 226 |
+
config: ForceLayoutConfig
|
| 227 |
+
): Vector3 {
|
| 228 |
+
if (config.gravityType === 'plane') {
|
| 229 |
+
// Pull down toward surface (Y axis)
|
| 230 |
+
const heightAboveSurface = node.position.y - config.surfaceY;
|
| 231 |
+
if (heightAboveSurface > 0) {
|
| 232 |
+
return { x: 0, y: -config.gravityStrength * heightAboveSurface, z: 0 };
|
| 233 |
+
}
|
| 234 |
+
return vec3.zero();
|
| 235 |
+
} else {
|
| 236 |
+
// Point gravity: pull toward target
|
| 237 |
+
const delta = vec3.subtract(config.gravityTarget, node.position);
|
| 238 |
+
const distance = vec3.length(delta);
|
| 239 |
+
if (distance < 0.1) return vec3.zero();
|
| 240 |
+
|
| 241 |
+
const direction = vec3.normalize(delta);
|
| 242 |
+
return vec3.multiply(direction, config.gravityStrength * distance);
|
| 243 |
+
}
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
/**
|
| 247 |
+
* Surface collision: Bounce off the table
|
| 248 |
+
*/
|
| 249 |
+
function applySurfaceConstraint(
|
| 250 |
+
node: VisualNode,
|
| 251 |
+
config: ForceLayoutConfig
|
| 252 |
+
): { position: Vector3; velocity: Vector3 } {
|
| 253 |
+
const position = { ...node.position };
|
| 254 |
+
const velocity = { ...node.velocity };
|
| 255 |
+
|
| 256 |
+
// Check if below surface
|
| 257 |
+
if (position.y < config.surfaceY) {
|
| 258 |
+
// Push back up
|
| 259 |
+
position.y = config.surfaceY;
|
| 260 |
+
|
| 261 |
+
// Bounce with damping
|
| 262 |
+
if (velocity.y < 0) {
|
| 263 |
+
velocity.y = -velocity.y * config.surfaceFriction;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
// Apply surface friction to horizontal movement
|
| 267 |
+
velocity.x *= config.surfaceFriction;
|
| 268 |
+
velocity.z *= config.surfaceFriction;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
return { position, velocity };
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
/**
|
| 275 |
+
* Boundary constraint: Keep nodes within bounds
|
| 276 |
+
*/
|
| 277 |
+
function applyBoundaryConstraint(
|
| 278 |
+
node: VisualNode,
|
| 279 |
+
config: ForceLayoutConfig
|
| 280 |
+
): Vector3 {
|
| 281 |
+
return {
|
| 282 |
+
x: Math.max(config.bounds.min.x, Math.min(config.bounds.max.x, node.position.x)),
|
| 283 |
+
y: Math.max(config.bounds.min.y, Math.min(config.bounds.max.y, node.position.y)),
|
| 284 |
+
z: Math.max(config.bounds.min.z, Math.min(config.bounds.max.z, node.position.z)),
|
| 285 |
+
};
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
// =============================================================================
|
| 289 |
+
// SIMULATION ENGINE
|
| 290 |
+
// =============================================================================
|
| 291 |
+
|
| 292 |
+
export class ForceDirectedLayout {
|
| 293 |
+
private state: SimulationState;
|
| 294 |
+
|
| 295 |
+
constructor(
|
| 296 |
+
nodes: VisualNode[],
|
| 297 |
+
edges: GraphEdge[],
|
| 298 |
+
config: Partial<ForceLayoutConfig> = {}
|
| 299 |
+
) {
|
| 300 |
+
const nodeMap = new Map<string, VisualNode>();
|
| 301 |
+
nodes.forEach(node => nodeMap.set(node.id, { ...node }));
|
| 302 |
+
|
| 303 |
+
this.state = {
|
| 304 |
+
nodes: nodeMap,
|
| 305 |
+
edges: [...edges],
|
| 306 |
+
config: { ...DEFAULT_CONFIG, ...config },
|
| 307 |
+
isRunning: false,
|
| 308 |
+
iteration: 0,
|
| 309 |
+
totalEnergy: Infinity,
|
| 310 |
+
settled: false,
|
| 311 |
+
};
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
/**
|
| 315 |
+
* Run one simulation step
|
| 316 |
+
*/
|
| 317 |
+
tick(): void {
|
| 318 |
+
const { nodes, edges, config } = this.state;
|
| 319 |
+
const forces = new Map<string, Vector3>();
|
| 320 |
+
|
| 321 |
+
// Initialize forces to zero
|
| 322 |
+
nodes.forEach((_, id) => forces.set(id, vec3.zero()));
|
| 323 |
+
|
| 324 |
+
// Calculate repulsion forces (all pairs)
|
| 325 |
+
const nodeArray = Array.from(nodes.values());
|
| 326 |
+
for (let i = 0; i < nodeArray.length; i++) {
|
| 327 |
+
for (let j = i + 1; j < nodeArray.length; j++) {
|
| 328 |
+
const nodeA = nodeArray[i];
|
| 329 |
+
const nodeB = nodeArray[j];
|
| 330 |
+
|
| 331 |
+
if (nodeA.fixed && nodeB.fixed) continue;
|
| 332 |
+
|
| 333 |
+
const repulsion = calculateRepulsion(nodeA, nodeB, config);
|
| 334 |
+
|
| 335 |
+
if (!nodeA.fixed) {
|
| 336 |
+
const forceA = forces.get(nodeA.id)!;
|
| 337 |
+
forces.set(nodeA.id, vec3.add(forceA, repulsion));
|
| 338 |
+
}
|
| 339 |
+
if (!nodeB.fixed) {
|
| 340 |
+
const forceB = forces.get(nodeB.id)!;
|
| 341 |
+
forces.set(nodeB.id, vec3.subtract(forceB, repulsion));
|
| 342 |
+
}
|
| 343 |
+
}
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
// Calculate attraction forces (edges only)
|
| 347 |
+
for (const edge of edges) {
|
| 348 |
+
const nodeA = nodes.get(edge.source);
|
| 349 |
+
const nodeB = nodes.get(edge.target);
|
| 350 |
+
if (!nodeA || !nodeB) continue;
|
| 351 |
+
|
| 352 |
+
const attraction = calculateAttraction(nodeA, nodeB, edge, config);
|
| 353 |
+
|
| 354 |
+
if (!nodeA.fixed) {
|
| 355 |
+
const forceA = forces.get(nodeA.id)!;
|
| 356 |
+
forces.set(nodeA.id, vec3.add(forceA, attraction));
|
| 357 |
+
}
|
| 358 |
+
if (!nodeB.fixed) {
|
| 359 |
+
const forceB = forces.get(nodeB.id)!;
|
| 360 |
+
forces.set(nodeB.id, vec3.subtract(forceB, attraction));
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
// Calculate gravity forces
|
| 365 |
+
nodes.forEach((node, id) => {
|
| 366 |
+
if (node.fixed) return;
|
| 367 |
+
|
| 368 |
+
const gravity = calculateGravity(node, config);
|
| 369 |
+
const force = forces.get(id)!;
|
| 370 |
+
forces.set(id, vec3.add(force, gravity));
|
| 371 |
+
});
|
| 372 |
+
|
| 373 |
+
// Apply forces and update positions
|
| 374 |
+
let totalEnergy = 0;
|
| 375 |
+
|
| 376 |
+
nodes.forEach((node, id) => {
|
| 377 |
+
if (node.fixed) return;
|
| 378 |
+
|
| 379 |
+
const force = forces.get(id)!;
|
| 380 |
+
|
| 381 |
+
// F = ma, assume m = node.mass (default 1)
|
| 382 |
+
const acceleration = vec3.divide(force, node.mass);
|
| 383 |
+
|
| 384 |
+
// Update velocity: v = v + a * dt
|
| 385 |
+
let velocity = vec3.add(
|
| 386 |
+
node.velocity,
|
| 387 |
+
vec3.multiply(acceleration, config.timeStep)
|
| 388 |
+
);
|
| 389 |
+
|
| 390 |
+
// Apply damping
|
| 391 |
+
velocity = vec3.multiply(velocity, config.velocityDamping);
|
| 392 |
+
|
| 393 |
+
// Clamp velocity
|
| 394 |
+
velocity = vec3.clamp(velocity, 0, config.maxVelocity);
|
| 395 |
+
|
| 396 |
+
// Update position: p = p + v * dt
|
| 397 |
+
let position = vec3.add(
|
| 398 |
+
node.position,
|
| 399 |
+
vec3.multiply(velocity, config.timeStep)
|
| 400 |
+
);
|
| 401 |
+
|
| 402 |
+
// Apply surface constraint
|
| 403 |
+
const surfaceResult = applySurfaceConstraint(
|
| 404 |
+
{ ...node, position, velocity },
|
| 405 |
+
config
|
| 406 |
+
);
|
| 407 |
+
position = surfaceResult.position;
|
| 408 |
+
velocity = surfaceResult.velocity;
|
| 409 |
+
|
| 410 |
+
// Apply boundary constraint
|
| 411 |
+
position = applyBoundaryConstraint({ ...node, position }, config);
|
| 412 |
+
|
| 413 |
+
// Update node
|
| 414 |
+
node.position = position;
|
| 415 |
+
node.velocity = velocity;
|
| 416 |
+
|
| 417 |
+
// Calculate energy for convergence check
|
| 418 |
+
totalEnergy += vec3.lengthSquared(velocity) * node.mass;
|
| 419 |
+
});
|
| 420 |
+
|
| 421 |
+
this.state.totalEnergy = totalEnergy;
|
| 422 |
+
this.state.iteration++;
|
| 423 |
+
|
| 424 |
+
// Check if settled
|
| 425 |
+
if (totalEnergy < config.minMovement) {
|
| 426 |
+
this.state.settled = true;
|
| 427 |
+
}
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
/**
|
| 431 |
+
* Run simulation until settled or max iterations
|
| 432 |
+
*/
|
| 433 |
+
simulate(maxIterations: number = 1000): void {
|
| 434 |
+
this.state.isRunning = true;
|
| 435 |
+
this.state.settled = false;
|
| 436 |
+
|
| 437 |
+
for (let i = 0; i < maxIterations && !this.state.settled; i++) {
|
| 438 |
+
this.tick();
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
this.state.isRunning = false;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
/**
|
| 445 |
+
* Run simulation with callback for animation
|
| 446 |
+
*/
|
| 447 |
+
async simulateAsync(
|
| 448 |
+
onTick: (state: SimulationState) => void,
|
| 449 |
+
maxIterations: number = 1000,
|
| 450 |
+
tickDelay: number = 16
|
| 451 |
+
): Promise<void> {
|
| 452 |
+
this.state.isRunning = true;
|
| 453 |
+
this.state.settled = false;
|
| 454 |
+
|
| 455 |
+
for (let i = 0; i < maxIterations && !this.state.settled; i++) {
|
| 456 |
+
this.tick();
|
| 457 |
+
onTick(this.state);
|
| 458 |
+
|
| 459 |
+
if (tickDelay > 0) {
|
| 460 |
+
await new Promise(resolve => setTimeout(resolve, tickDelay));
|
| 461 |
+
}
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
this.state.isRunning = false;
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
/**
|
| 468 |
+
* Get current state
|
| 469 |
+
*/
|
| 470 |
+
getState(): SimulationState {
|
| 471 |
+
return this.state;
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
/**
|
| 475 |
+
* Get nodes as array
|
| 476 |
+
*/
|
| 477 |
+
getNodes(): VisualNode[] {
|
| 478 |
+
return Array.from(this.state.nodes.values());
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
/**
|
| 482 |
+
* Add a node dynamically
|
| 483 |
+
*/
|
| 484 |
+
addNode(node: VisualNode): void {
|
| 485 |
+
this.state.nodes.set(node.id, { ...node });
|
| 486 |
+
this.state.settled = false;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
/**
|
| 490 |
+
* Remove a node dynamically
|
| 491 |
+
*/
|
| 492 |
+
removeNode(nodeId: string): void {
|
| 493 |
+
this.state.nodes.delete(nodeId);
|
| 494 |
+
this.state.edges = this.state.edges.filter(
|
| 495 |
+
e => e.source !== nodeId && e.target !== nodeId
|
| 496 |
+
);
|
| 497 |
+
this.state.settled = false;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
/**
|
| 501 |
+
* Add an edge dynamically
|
| 502 |
+
*/
|
| 503 |
+
addEdge(edge: GraphEdge): void {
|
| 504 |
+
this.state.edges.push(edge);
|
| 505 |
+
this.state.settled = false;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
/**
|
| 509 |
+
* Pin/unpin a node
|
| 510 |
+
*/
|
| 511 |
+
setFixed(nodeId: string, fixed: boolean): void {
|
| 512 |
+
const node = this.state.nodes.get(nodeId);
|
| 513 |
+
if (node) {
|
| 514 |
+
node.fixed = fixed;
|
| 515 |
+
if (fixed) {
|
| 516 |
+
node.velocity = vec3.zero();
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
/**
|
| 522 |
+
* Move a node (e.g., when user drags it)
|
| 523 |
+
*/
|
| 524 |
+
setPosition(nodeId: string, position: Vector3): void {
|
| 525 |
+
const node = this.state.nodes.get(nodeId);
|
| 526 |
+
if (node) {
|
| 527 |
+
node.position = position;
|
| 528 |
+
node.velocity = vec3.zero();
|
| 529 |
+
this.state.settled = false;
|
| 530 |
+
}
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
/**
|
| 534 |
+
* Reset simulation
|
| 535 |
+
*/
|
| 536 |
+
reset(): void {
|
| 537 |
+
this.state.iteration = 0;
|
| 538 |
+
this.state.totalEnergy = Infinity;
|
| 539 |
+
this.state.settled = false;
|
| 540 |
+
this.state.nodes.forEach(node => {
|
| 541 |
+
node.velocity = vec3.zero();
|
| 542 |
+
});
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
/**
|
| 546 |
+
* Update configuration
|
| 547 |
+
*/
|
| 548 |
+
updateConfig(config: Partial<ForceLayoutConfig>): void {
|
| 549 |
+
this.state.config = { ...this.state.config, ...config };
|
| 550 |
+
this.state.settled = false;
|
| 551 |
+
}
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
// =============================================================================
|
| 555 |
+
// PRESET CONFIGURATIONS
|
| 556 |
+
// =============================================================================
|
| 557 |
+
|
| 558 |
+
export const LAYOUT_PRESETS = {
|
| 559 |
+
/**
|
| 560 |
+
* Default: Balanced layout with table gravity
|
| 561 |
+
*/
|
| 562 |
+
default: DEFAULT_CONFIG,
|
| 563 |
+
|
| 564 |
+
/**
|
| 565 |
+
* Dense: For many nodes, stronger repulsion
|
| 566 |
+
*/
|
| 567 |
+
dense: {
|
| 568 |
+
...DEFAULT_CONFIG,
|
| 569 |
+
repulsionStrength: 2000,
|
| 570 |
+
idealEdgeLength: 3,
|
| 571 |
+
gravityStrength: 0.8,
|
| 572 |
+
},
|
| 573 |
+
|
| 574 |
+
/**
|
| 575 |
+
* Sparse: For few nodes, weaker forces
|
| 576 |
+
*/
|
| 577 |
+
sparse: {
|
| 578 |
+
...DEFAULT_CONFIG,
|
| 579 |
+
repulsionStrength: 500,
|
| 580 |
+
idealEdgeLength: 10,
|
| 581 |
+
gravityStrength: 0.3,
|
| 582 |
+
},
|
| 583 |
+
|
| 584 |
+
/**
|
| 585 |
+
* Hierarchical: Strong vertical gravity, nodes layer on surface
|
| 586 |
+
*/
|
| 587 |
+
hierarchical: {
|
| 588 |
+
...DEFAULT_CONFIG,
|
| 589 |
+
gravityStrength: 1.0,
|
| 590 |
+
surfaceY: 0,
|
| 591 |
+
surfaceStiffness: 5.0,
|
| 592 |
+
attractionStrength: 0.05,
|
| 593 |
+
},
|
| 594 |
+
|
| 595 |
+
/**
|
| 596 |
+
* Floating: No surface, point gravity to center
|
| 597 |
+
*/
|
| 598 |
+
floating: {
|
| 599 |
+
...DEFAULT_CONFIG,
|
| 600 |
+
gravityType: 'point' as const,
|
| 601 |
+
gravityStrength: 0.2,
|
| 602 |
+
gravityTarget: { x: 0, y: 5, z: 0 },
|
| 603 |
+
},
|
| 604 |
+
|
| 605 |
+
/**
|
| 606 |
+
* Constellation: Nodes spread out like stars
|
| 607 |
+
*/
|
| 608 |
+
constellation: {
|
| 609 |
+
...DEFAULT_CONFIG,
|
| 610 |
+
repulsionStrength: 3000,
|
| 611 |
+
repulsionDistance: 200,
|
| 612 |
+
gravityType: 'point' as const,
|
| 613 |
+
gravityStrength: 0.05,
|
| 614 |
+
gravityTarget: { x: 0, y: 30, z: 0 },
|
| 615 |
+
},
|
| 616 |
+
};
|
| 617 |
+
|
| 618 |
+
// =============================================================================
|
| 619 |
+
// UTILITY: Initial Layout Generators
|
| 620 |
+
// =============================================================================
|
| 621 |
+
|
| 622 |
+
/**
|
| 623 |
+
* Generate random initial positions
|
| 624 |
+
*/
|
| 625 |
+
export function randomLayout(
|
| 626 |
+
nodes: VisualNode[],
|
| 627 |
+
radius: number = 20
|
| 628 |
+
): VisualNode[] {
|
| 629 |
+
return nodes.map(node => ({
|
| 630 |
+
...node,
|
| 631 |
+
position: vec3.random(radius),
|
| 632 |
+
velocity: vec3.zero(),
|
| 633 |
+
}));
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
/**
|
| 637 |
+
* Generate spherical initial positions
|
| 638 |
+
*/
|
| 639 |
+
export function sphereLayout(
|
| 640 |
+
nodes: VisualNode[],
|
| 641 |
+
radius: number = 15
|
| 642 |
+
): VisualNode[] {
|
| 643 |
+
return nodes.map((node, i) => {
|
| 644 |
+
const phi = Math.acos(-1 + (2 * i) / nodes.length);
|
| 645 |
+
const theta = Math.sqrt(nodes.length * Math.PI) * phi;
|
| 646 |
+
|
| 647 |
+
return {
|
| 648 |
+
...node,
|
| 649 |
+
position: {
|
| 650 |
+
x: radius * Math.cos(theta) * Math.sin(phi),
|
| 651 |
+
y: radius * Math.sin(theta) * Math.sin(phi) + radius,
|
| 652 |
+
z: radius * Math.cos(phi),
|
| 653 |
+
},
|
| 654 |
+
velocity: vec3.zero(),
|
| 655 |
+
};
|
| 656 |
+
});
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
/**
|
| 660 |
+
* Generate grid initial positions (on table surface)
|
| 661 |
+
*/
|
| 662 |
+
export function gridLayout(
|
| 663 |
+
nodes: VisualNode[],
|
| 664 |
+
spacing: number = 3,
|
| 665 |
+
surfaceY: number = 0
|
| 666 |
+
): VisualNode[] {
|
| 667 |
+
const gridSize = Math.ceil(Math.sqrt(nodes.length));
|
| 668 |
+
|
| 669 |
+
return nodes.map((node, i) => {
|
| 670 |
+
const row = Math.floor(i / gridSize);
|
| 671 |
+
const col = i % gridSize;
|
| 672 |
+
const offsetX = ((gridSize - 1) * spacing) / 2;
|
| 673 |
+
const offsetZ = ((gridSize - 1) * spacing) / 2;
|
| 674 |
+
|
| 675 |
+
return {
|
| 676 |
+
...node,
|
| 677 |
+
position: {
|
| 678 |
+
x: col * spacing - offsetX,
|
| 679 |
+
y: surfaceY + 0.5, // Slightly above surface
|
| 680 |
+
z: row * spacing - offsetZ,
|
| 681 |
+
},
|
| 682 |
+
velocity: vec3.zero(),
|
| 683 |
+
};
|
| 684 |
+
});
|
| 685 |
+
}
|
packages/domain-types/src/vr/VisualNode.ts
ADDED
|
@@ -0,0 +1,782 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* VISUALNODE SCHEMA - Operation Neuro-Spatial Reality
|
| 3 |
+
*
|
| 4 |
+
* The Universal Onion Model: Every entity has peelable layers.
|
| 5 |
+
* From CEO to CPU. From virus to Kremlin. From lead to loyal customer.
|
| 6 |
+
*
|
| 7 |
+
* Patent Pillars: A (Visceral), B (Universal), C (Threat), D (Business)
|
| 8 |
+
*
|
| 9 |
+
* @author The Captain (Claude) + The Architect (Gemini)
|
| 10 |
+
* @date 2025-12-17
|
| 11 |
+
*/
|
| 12 |
+
|
| 13 |
+
// =============================================================================
|
| 14 |
+
// CORE TYPES
|
| 15 |
+
// =============================================================================
|
| 16 |
+
|
| 17 |
+
export type EntityType =
|
| 18 |
+
// Data entities
|
| 19 |
+
| 'file'
|
| 20 |
+
| 'database'
|
| 21 |
+
| 'table'
|
| 22 |
+
| 'record'
|
| 23 |
+
// Network entities
|
| 24 |
+
| 'access_point'
|
| 25 |
+
| 'switch'
|
| 26 |
+
| 'router'
|
| 27 |
+
| 'firewall'
|
| 28 |
+
| 'firewall_policy' // Gemini addition
|
| 29 |
+
| 'server'
|
| 30 |
+
| 'compute_node' // Gemini: Separation of Concern
|
| 31 |
+
| 'storage_node' // Gemini: Separation of Concern
|
| 32 |
+
| 'endpoint'
|
| 33 |
+
| 'ip_address'
|
| 34 |
+
| 'api_gateway' // Gemini addition
|
| 35 |
+
| 'load_balancer' // Gemini addition
|
| 36 |
+
// Cloud/Container entities (Gemini additions)
|
| 37 |
+
| 'container_pod'
|
| 38 |
+
| 'kubernetes_cluster'
|
| 39 |
+
| 'cloud_function'
|
| 40 |
+
// Identity entities (Gemini additions)
|
| 41 |
+
| 'identity_provider'
|
| 42 |
+
| 'service_account'
|
| 43 |
+
| 'certificate'
|
| 44 |
+
// IoT entities (Gemini addition)
|
| 45 |
+
| 'iot_device'
|
| 46 |
+
| 'sensor'
|
| 47 |
+
// Organization entities
|
| 48 |
+
| 'organization'
|
| 49 |
+
| 'branch'
|
| 50 |
+
| 'department'
|
| 51 |
+
| 'team'
|
| 52 |
+
| 'person'
|
| 53 |
+
// Security entities
|
| 54 |
+
| 'malware'
|
| 55 |
+
| 'campaign'
|
| 56 |
+
| 'threat_actor'
|
| 57 |
+
| 'threat_group'
|
| 58 |
+
| 'nation_state'
|
| 59 |
+
| 'vulnerability' // Gemini addition
|
| 60 |
+
| 'exploit' // Gemini addition
|
| 61 |
+
// Business entities
|
| 62 |
+
| 'customer'
|
| 63 |
+
| 'journey'
|
| 64 |
+
| 'touchpoint'
|
| 65 |
+
| 'ticket'
|
| 66 |
+
| 'sla'
|
| 67 |
+
| 'contract' // Gemini addition
|
| 68 |
+
| 'invoice' // Gemini addition
|
| 69 |
+
// AI entities
|
| 70 |
+
| 'agent'
|
| 71 |
+
| 'task'
|
| 72 |
+
| 'memory'
|
| 73 |
+
// DeepSeek: Cognitive entities
|
| 74 |
+
| 'neural_node'
|
| 75 |
+
| 'synapse_connection'
|
| 76 |
+
| 'concept_cluster'
|
| 77 |
+
| 'memory_trace'
|
| 78 |
+
| 'metacognitive_monitor'
|
| 79 |
+
// Generic
|
| 80 |
+
| 'error'
|
| 81 |
+
| 'alert'
|
| 82 |
+
| 'unknown';
|
| 83 |
+
|
| 84 |
+
export type GeometryType =
|
| 85 |
+
| 'sphere'
|
| 86 |
+
| 'cube'
|
| 87 |
+
| 'octahedron'
|
| 88 |
+
| 'tetrahedron'
|
| 89 |
+
| 'dodecahedron'
|
| 90 |
+
| 'icosahedron' // DeepSeek addition
|
| 91 |
+
| 'cylinder'
|
| 92 |
+
| 'torus'
|
| 93 |
+
| 'building'
|
| 94 |
+
| 'humanoid'
|
| 95 |
+
| 'path'
|
| 96 |
+
| 'custom';
|
| 97 |
+
|
| 98 |
+
export type ShaderType =
|
| 99 |
+
| 'standard'
|
| 100 |
+
| 'hologram'
|
| 101 |
+
| 'energy'
|
| 102 |
+
| 'organic'
|
| 103 |
+
| 'error'
|
| 104 |
+
| 'glass'
|
| 105 |
+
| 'fire'
|
| 106 |
+
| 'ice'
|
| 107 |
+
| 'threat'
|
| 108 |
+
// Gemini shader additions
|
| 109 |
+
| 'glitch_noise_v2' // Enhanced malware visualization
|
| 110 |
+
| 'phantom_wireframe' // Threat actor ghost effect
|
| 111 |
+
| 'gold_metallic_clean' // Premium customer visualization
|
| 112 |
+
| 'flow_particle_stream' // Journey path visualization
|
| 113 |
+
| 'matrix_rain' // Data flow effect
|
| 114 |
+
| 'pulse_rings' // IoT device heartbeat
|
| 115 |
+
| 'shield_hex' // Firewall/security shader
|
| 116 |
+
| 'cloud_vapor' // Cloud service visualization
|
| 117 |
+
| 'matte_ceramic_hex'; // Gemini: Infrastructure default
|
| 118 |
+
|
| 119 |
+
export type LayerType =
|
| 120 |
+
| 'surface' // Layer 5: What you see first
|
| 121 |
+
| 'metadata' // Layer 4: Identity and context
|
| 122 |
+
| 'connections'// Layer 3: Relationships
|
| 123 |
+
| 'structure' // Layer 2: Internal anatomy
|
| 124 |
+
| 'core'; // Layer 1: The truth
|
| 125 |
+
|
| 126 |
+
// =============================================================================
|
| 127 |
+
// VISUAL NODE INTERFACE
|
| 128 |
+
// =============================================================================
|
| 129 |
+
|
| 130 |
+
export interface VisualNode {
|
| 131 |
+
// Identity
|
| 132 |
+
id: string;
|
| 133 |
+
externalId?: string; // Neo4j ID, DB ID, etc.
|
| 134 |
+
type: EntityType;
|
| 135 |
+
labels: string[]; // Neo4j labels: [:File, :Agent]
|
| 136 |
+
|
| 137 |
+
// Spatial positioning
|
| 138 |
+
position: Vector3;
|
| 139 |
+
velocity: Vector3;
|
| 140 |
+
rotation: Vector3;
|
| 141 |
+
|
| 142 |
+
// Physics
|
| 143 |
+
mass: number; // Affects grabbability (1KB = light, 1GB = heavy)
|
| 144 |
+
fixed: boolean; // Pinned in space?
|
| 145 |
+
collisionRadius: number;
|
| 146 |
+
|
| 147 |
+
// Visual appearance
|
| 148 |
+
geometry: GeometryConfig;
|
| 149 |
+
material: MaterialConfig;
|
| 150 |
+
animation: AnimationConfig;
|
| 151 |
+
|
| 152 |
+
// The Onion Layers (Patent Pillar B)
|
| 153 |
+
layers: OnionLayers;
|
| 154 |
+
currentLayer: LayerType; // Currently visible layer
|
| 155 |
+
|
| 156 |
+
// Interaction state
|
| 157 |
+
interaction: InteractionState;
|
| 158 |
+
|
| 159 |
+
// LOD (Level of Detail)
|
| 160 |
+
lod: LODConfig;
|
| 161 |
+
|
| 162 |
+
// Hierarchy
|
| 163 |
+
parent?: string; // Parent node ID
|
| 164 |
+
children: string[]; // Child node IDs
|
| 165 |
+
|
| 166 |
+
// Timestamps
|
| 167 |
+
createdAt: string;
|
| 168 |
+
updatedAt: string;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
// =============================================================================
|
| 172 |
+
// GEOMETRY CONFIGURATION
|
| 173 |
+
// =============================================================================
|
| 174 |
+
|
| 175 |
+
export interface Vector3 {
|
| 176 |
+
x: number;
|
| 177 |
+
y: number;
|
| 178 |
+
z: number;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
export interface GeometryConfig {
|
| 182 |
+
type: GeometryType;
|
| 183 |
+
scale: Vector3;
|
| 184 |
+
baseScale: number; // 0.5 - 10.0 based on importance
|
| 185 |
+
customModelUrl?: string; // For custom GLB models
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
// =============================================================================
|
| 189 |
+
// MATERIAL CONFIGURATION
|
| 190 |
+
// =============================================================================
|
| 191 |
+
|
| 192 |
+
export interface MaterialConfig {
|
| 193 |
+
shader: ShaderType;
|
| 194 |
+
color: ColorConfig;
|
| 195 |
+
opacity: number; // 0-1
|
| 196 |
+
emissive: boolean;
|
| 197 |
+
emissiveIntensity: number; // 0-2
|
| 198 |
+
wireframe: boolean;
|
| 199 |
+
pulse: PulseConfig;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
export interface ColorConfig {
|
| 203 |
+
base: string; // Hex color
|
| 204 |
+
emissive: string; // Glow color
|
| 205 |
+
accent: string; // Highlight color
|
| 206 |
+
healthGradient?: { // Dynamic health-based coloring
|
| 207 |
+
healthy: string;
|
| 208 |
+
warning: string;
|
| 209 |
+
critical: string;
|
| 210 |
+
};
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
export interface PulseConfig {
|
| 214 |
+
enabled: boolean;
|
| 215 |
+
frequency: number; // BPM (60 = calm, 120 = stressed)
|
| 216 |
+
amplitude: number; // Scale variance 0-0.5
|
| 217 |
+
pattern: 'sine' | 'heartbeat' | 'glitch' | 'breath';
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
// =============================================================================
|
| 221 |
+
// ANIMATION CONFIGURATION
|
| 222 |
+
// =============================================================================
|
| 223 |
+
|
| 224 |
+
export interface AnimationConfig {
|
| 225 |
+
idle: IdleAnimation;
|
| 226 |
+
hover: HoverAnimation;
|
| 227 |
+
select: SelectAnimation;
|
| 228 |
+
peel: PeelAnimation;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
export interface IdleAnimation {
|
| 232 |
+
rotation: boolean; // Slowly rotate?
|
| 233 |
+
rotationSpeed: number; // Degrees per second
|
| 234 |
+
float: boolean; // Bob up and down?
|
| 235 |
+
floatAmplitude: number; // Units
|
| 236 |
+
floatSpeed: number; // Cycles per second
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
export interface HoverAnimation {
|
| 240 |
+
scaleMultiplier: number; // 1.1 = 10% bigger on hover
|
| 241 |
+
glowIntensity: number; // Emissive boost
|
| 242 |
+
duration: number; // ms
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
export interface SelectAnimation {
|
| 246 |
+
scaleMultiplier: number;
|
| 247 |
+
outlineColor: string;
|
| 248 |
+
outlineWidth: number;
|
| 249 |
+
duration: number;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
export interface PeelAnimation {
|
| 253 |
+
duration: number; // ms per layer peel
|
| 254 |
+
fragmentCount: number; // Number of shell fragments
|
| 255 |
+
fragmentFadeTime: number; // ms
|
| 256 |
+
revealGlowDuration: number; // ms
|
| 257 |
+
sound: string; // Audio file reference
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
// =============================================================================
|
| 261 |
+
// THE ONION LAYERS (Patent Pillar B)
|
| 262 |
+
// =============================================================================
|
| 263 |
+
|
| 264 |
+
export interface OnionLayers {
|
| 265 |
+
surface: SurfaceLayer;
|
| 266 |
+
metadata: MetadataLayer;
|
| 267 |
+
connections: ConnectionsLayer;
|
| 268 |
+
structure: StructureLayer;
|
| 269 |
+
core: CoreLayer;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
// Layer 5: Surface - First impression
|
| 273 |
+
export interface SurfaceLayer {
|
| 274 |
+
icon: string; // Emoji or icon reference
|
| 275 |
+
title: string; // Short display name
|
| 276 |
+
status: 'healthy' | 'warning' | 'critical' | 'offline' | 'unknown';
|
| 277 |
+
activityLevel: number; // 0-100
|
| 278 |
+
importance: number; // 0-100 (affects size)
|
| 279 |
+
badges: Badge[]; // VIP, New, Alert, etc.
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
export interface Badge {
|
| 283 |
+
type: 'vip' | 'new' | 'alert' | 'verified' | 'threat' | 'custom';
|
| 284 |
+
label?: string;
|
| 285 |
+
color: string;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
// Layer 4: Metadata - Identity
|
| 289 |
+
export interface MetadataLayer {
|
| 290 |
+
fullName: string;
|
| 291 |
+
description: string;
|
| 292 |
+
category: string;
|
| 293 |
+
tags: string[];
|
| 294 |
+
owner?: string;
|
| 295 |
+
source: string; // System of origin
|
| 296 |
+
created: string;
|
| 297 |
+
modified: string;
|
| 298 |
+
customFields: Record<string, any>;
|
| 299 |
+
// Gemini: Data integrity indicator
|
| 300 |
+
integrityStatus?: {
|
| 301 |
+
hash: string; // SHA-256 of content
|
| 302 |
+
verified: boolean; // Green/Red indicator
|
| 303 |
+
lastVerified: string; // Timestamp
|
| 304 |
+
tamperedFields?: string[]; // List of modified fields
|
| 305 |
+
};
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
// Layer 3: Connections - Relationships
|
| 309 |
+
export interface ConnectionsLayer {
|
| 310 |
+
inbound: Connection[];
|
| 311 |
+
outbound: Connection[];
|
| 312 |
+
bidirectional: Connection[];
|
| 313 |
+
totalConnections: number;
|
| 314 |
+
strongestConnection?: string; // Node ID
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
export interface Connection {
|
| 318 |
+
targetId: string;
|
| 319 |
+
targetType: EntityType;
|
| 320 |
+
relationshipType: string; // 'depends_on', 'parent_of', 'connects_to', etc.
|
| 321 |
+
strength: number; // 0-1
|
| 322 |
+
direction: 'in' | 'out' | 'both';
|
| 323 |
+
metadata?: Record<string, any>;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
// Layer 2: Structure - Anatomy
|
| 327 |
+
export interface StructureLayer {
|
| 328 |
+
schema?: SchemaDefinition;
|
| 329 |
+
children: ChildNode[];
|
| 330 |
+
hierarchy: string[]; // Breadcrumb path
|
| 331 |
+
depth: number; // Nesting level
|
| 332 |
+
expandable: boolean;
|
| 333 |
+
preview?: string; // Code/content preview
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
export interface SchemaDefinition {
|
| 337 |
+
type: string;
|
| 338 |
+
fields: SchemaField[];
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
export interface SchemaField {
|
| 342 |
+
name: string;
|
| 343 |
+
type: string;
|
| 344 |
+
required: boolean;
|
| 345 |
+
description?: string;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
export interface ChildNode {
|
| 349 |
+
id: string;
|
| 350 |
+
type: EntityType;
|
| 351 |
+
name: string;
|
| 352 |
+
count?: number; // For aggregates
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
// Layer 1: Core - The Truth
|
| 356 |
+
export interface CoreLayer {
|
| 357 |
+
// SECURITY FIX (Grok): rawData is now lazy-loaded, not stored in client state
|
| 358 |
+
rawDataRef?: string; // Reference ID for fetching - NOT the data itself
|
| 359 |
+
rawDataPreview?: string; // Safe preview (first 100 chars, sanitized)
|
| 360 |
+
fetchRawData?: () => Promise<any>; // Auth-checked lazy loader
|
| 361 |
+
origin: DataOrigin;
|
| 362 |
+
audit: AuditTrail;
|
| 363 |
+
actions: AvailableAction[];
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
export interface DataOrigin {
|
| 367 |
+
system: string; // Neo4j, PostgreSQL, Notion, ServiceNow, etc.
|
| 368 |
+
database?: string;
|
| 369 |
+
table?: string;
|
| 370 |
+
collection?: string;
|
| 371 |
+
primaryKey: string;
|
| 372 |
+
query?: string; // How to fetch this
|
| 373 |
+
url?: string; // Direct link
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
export interface AuditTrail {
|
| 377 |
+
createdBy: string;
|
| 378 |
+
createdAt: string;
|
| 379 |
+
history: ChangeRecord[];
|
| 380 |
+
version: number;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
export interface ChangeRecord {
|
| 384 |
+
timestamp: string;
|
| 385 |
+
actor: string;
|
| 386 |
+
action: string;
|
| 387 |
+
changes: Record<string, { old: any; new: any }>;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
export interface AvailableAction {
|
| 391 |
+
id: string;
|
| 392 |
+
label: string;
|
| 393 |
+
icon: string;
|
| 394 |
+
type: 'view' | 'edit' | 'delete' | 'navigate' | 'execute' | 'export';
|
| 395 |
+
requiresConfirmation: boolean;
|
| 396 |
+
permissions: string[];
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
// =============================================================================
|
| 400 |
+
// INTERACTION STATE
|
| 401 |
+
// =============================================================================
|
| 402 |
+
|
| 403 |
+
export interface InteractionState {
|
| 404 |
+
hoverable: boolean;
|
| 405 |
+
selectable: boolean;
|
| 406 |
+
grabbable: boolean;
|
| 407 |
+
peelable: boolean;
|
| 408 |
+
expandable: boolean;
|
| 409 |
+
|
| 410 |
+
isHovered: boolean;
|
| 411 |
+
isSelected: boolean;
|
| 412 |
+
isGrabbed: boolean;
|
| 413 |
+
isPeeling: boolean;
|
| 414 |
+
|
| 415 |
+
grabOffset?: Vector3;
|
| 416 |
+
lastInteraction?: string; // Timestamp
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
// =============================================================================
|
| 420 |
+
// LEVEL OF DETAIL
|
| 421 |
+
// =============================================================================
|
| 422 |
+
|
| 423 |
+
export interface LODConfig {
|
| 424 |
+
near: number; // Distance for full detail
|
| 425 |
+
mid: number; // Distance for medium detail
|
| 426 |
+
far: number; // Distance for low detail (billboard)
|
| 427 |
+
|
| 428 |
+
nearGeometry: GeometryType;
|
| 429 |
+
midGeometry: GeometryType;
|
| 430 |
+
farGeometry: 'billboard' | 'point';
|
| 431 |
+
|
| 432 |
+
showLabelsAt: number; // Distance threshold
|
| 433 |
+
showConnectionsAt: number; // Distance threshold
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
// =============================================================================
|
| 437 |
+
// ENTITY TYPE MAPPINGS
|
| 438 |
+
// =============================================================================
|
| 439 |
+
|
| 440 |
+
/**
|
| 441 |
+
* Default visual configuration per entity type
|
| 442 |
+
*/
|
| 443 |
+
export const ENTITY_TYPE_DEFAULTS: Record<EntityType, Partial<VisualNode>> = {
|
| 444 |
+
// Data entities
|
| 445 |
+
file: {
|
| 446 |
+
geometry: { type: 'cube', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
|
| 447 |
+
material: { shader: 'standard', color: { base: '#00ff88', emissive: '#00ff88', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.05, pattern: 'breath' } },
|
| 448 |
+
},
|
| 449 |
+
database: {
|
| 450 |
+
geometry: { type: 'cylinder', scale: { x: 1.5, y: 1, z: 1.5 }, baseScale: 2.0 },
|
| 451 |
+
material: { shader: 'hologram', color: { base: '#0088ff', emissive: '#0088ff', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 45, amplitude: 0.1, pattern: 'sine' } },
|
| 452 |
+
},
|
| 453 |
+
|
| 454 |
+
// Network entities
|
| 455 |
+
access_point: {
|
| 456 |
+
geometry: { type: 'octahedron', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
|
| 457 |
+
material: { shader: 'energy', color: { base: '#00ffaa', emissive: '#00ffaa', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.1, pattern: 'sine' } },
|
| 458 |
+
},
|
| 459 |
+
switch: {
|
| 460 |
+
geometry: { type: 'cube', scale: { x: 2, y: 0.5, z: 1 }, baseScale: 1.5 },
|
| 461 |
+
material: { shader: 'standard', color: { base: '#44ff44', emissive: '#22aa22', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.05, pattern: 'breath' } },
|
| 462 |
+
},
|
| 463 |
+
router: {
|
| 464 |
+
geometry: { type: 'sphere', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 2.0 },
|
| 465 |
+
material: { shader: 'energy', color: { base: '#00aaff', emissive: '#0088ff', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.15, pattern: 'heartbeat' } },
|
| 466 |
+
},
|
| 467 |
+
server: {
|
| 468 |
+
geometry: { type: 'cylinder', scale: { x: 1, y: 2, z: 1 }, baseScale: 2.5 },
|
| 469 |
+
material: { shader: 'hologram', color: { base: '#aa00ff', emissive: '#8800cc', accent: '#ffffff' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.08, pattern: 'heartbeat' } },
|
| 470 |
+
},
|
| 471 |
+
|
| 472 |
+
// Organization entities
|
| 473 |
+
organization: {
|
| 474 |
+
geometry: { type: 'building', scale: { x: 3, y: 4, z: 3 }, baseScale: 5.0 },
|
| 475 |
+
material: { shader: 'glass', color: { base: '#4488ff', emissive: '#2266cc', accent: '#ffffff' }, opacity: 0.8, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 20, amplitude: 0.02, pattern: 'breath' } },
|
| 476 |
+
},
|
| 477 |
+
branch: {
|
| 478 |
+
geometry: { type: 'building', scale: { x: 2, y: 2.5, z: 2 }, baseScale: 3.0 },
|
| 479 |
+
material: { shader: 'standard', color: { base: '#5599ff', emissive: '#3377cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 25, amplitude: 0.03, pattern: 'breath' } },
|
| 480 |
+
},
|
| 481 |
+
person: {
|
| 482 |
+
geometry: { type: 'humanoid', scale: { x: 1, y: 1, z: 1 }, baseScale: 0.8 },
|
| 483 |
+
material: { shader: 'standard', color: { base: '#ffffff', emissive: '#aaaaaa', accent: '#00ff88' }, opacity: 1, emissive: true, emissiveIntensity: 0.2, wireframe: false, pulse: { enabled: true, frequency: 70, amplitude: 0.05, pattern: 'heartbeat' } },
|
| 484 |
+
},
|
| 485 |
+
|
| 486 |
+
// Security entities (Patent Pillar C)
|
| 487 |
+
malware: {
|
| 488 |
+
geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.2 },
|
| 489 |
+
material: { shader: 'threat', color: { base: '#ff0040', emissive: '#ff0000', accent: '#ffaa00' }, opacity: 1, emissive: true, emissiveIntensity: 1.0, wireframe: false, pulse: { enabled: true, frequency: 120, amplitude: 0.3, pattern: 'glitch' } },
|
| 490 |
+
},
|
| 491 |
+
threat_actor: {
|
| 492 |
+
geometry: { type: 'humanoid', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 1.5 },
|
| 493 |
+
material: { shader: 'threat', color: { base: '#880000', emissive: '#ff0000', accent: '#ffff00' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 90, amplitude: 0.2, pattern: 'glitch' } },
|
| 494 |
+
},
|
| 495 |
+
nation_state: {
|
| 496 |
+
geometry: { type: 'building', scale: { x: 4, y: 5, z: 4 }, baseScale: 6.0 },
|
| 497 |
+
material: { shader: 'threat', color: { base: '#660000', emissive: '#aa0000', accent: '#ffaa00' }, opacity: 1, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.1, pattern: 'heartbeat' } },
|
| 498 |
+
},
|
| 499 |
+
|
| 500 |
+
// Business entities (Patent Pillar D)
|
| 501 |
+
customer: {
|
| 502 |
+
geometry: { type: 'sphere', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
|
| 503 |
+
material: { shader: 'standard', color: { base: '#ffd700', emissive: '#ffaa00', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.1, pattern: 'heartbeat' } },
|
| 504 |
+
},
|
| 505 |
+
journey: {
|
| 506 |
+
geometry: { type: 'path', scale: { x: 10, y: 0.5, z: 2 }, baseScale: 1.0 },
|
| 507 |
+
material: { shader: 'energy', color: { base: '#00ffff', emissive: '#00aaaa', accent: '#ffffff' }, opacity: 0.8, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.05, pattern: 'sine' } },
|
| 508 |
+
},
|
| 509 |
+
ticket: {
|
| 510 |
+
geometry: { type: 'cube', scale: { x: 1, y: 0.6, z: 0.1 }, baseScale: 0.8 },
|
| 511 |
+
material: { shader: 'standard', color: { base: '#ff8800', emissive: '#ff6600', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 80, amplitude: 0.15, pattern: 'heartbeat' } },
|
| 512 |
+
},
|
| 513 |
+
|
| 514 |
+
// AI entities
|
| 515 |
+
agent: {
|
| 516 |
+
geometry: { type: 'dodecahedron', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 1.5 },
|
| 517 |
+
material: { shader: 'energy', color: { base: '#ffd700', emissive: '#ffaa00', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.2, pattern: 'heartbeat' } },
|
| 518 |
+
},
|
| 519 |
+
|
| 520 |
+
// Generic
|
| 521 |
+
error: {
|
| 522 |
+
geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
|
| 523 |
+
material: { shader: 'error', color: { base: '#ff0040', emissive: '#ff0000', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 1.0, wireframe: false, pulse: { enabled: true, frequency: 120, amplitude: 0.3, pattern: 'glitch' } },
|
| 524 |
+
},
|
| 525 |
+
alert: {
|
| 526 |
+
geometry: { type: 'tetrahedron', scale: { x: 1, y: 1.5, z: 1 }, baseScale: 1.2 },
|
| 527 |
+
material: { shader: 'standard', color: { base: '#ffaa00', emissive: '#ff8800', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 100, amplitude: 0.25, pattern: 'sine' } },
|
| 528 |
+
},
|
| 529 |
+
unknown: {
|
| 530 |
+
geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
|
| 531 |
+
material: { shader: 'standard', color: { base: '#888888', emissive: '#666666', accent: '#ffffff' }, opacity: 0.7, emissive: true, emissiveIntensity: 0.2, wireframe: true, pulse: { enabled: false, frequency: 0, amplitude: 0, pattern: 'sine' } },
|
| 532 |
+
},
|
| 533 |
+
|
| 534 |
+
// Fill in remaining types with sensible defaults
|
| 535 |
+
table: {
|
| 536 |
+
geometry: { type: 'cube', scale: { x: 1.5, y: 0.5, z: 1 }, baseScale: 1.2 },
|
| 537 |
+
material: { shader: 'hologram', color: { base: '#0088ff', emissive: '#0066cc', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 35, amplitude: 0.05, pattern: 'breath' } },
|
| 538 |
+
},
|
| 539 |
+
record: {
|
| 540 |
+
geometry: { type: 'cube', scale: { x: 0.8, y: 0.8, z: 0.2 }, baseScale: 0.6 },
|
| 541 |
+
material: { shader: 'standard', color: { base: '#00aaff', emissive: '#0088cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 25, amplitude: 0.03, pattern: 'breath' } },
|
| 542 |
+
},
|
| 543 |
+
firewall: {
|
| 544 |
+
geometry: { type: 'cube', scale: { x: 3, y: 2, z: 0.3 }, baseScale: 2.5 },
|
| 545 |
+
material: { shader: 'energy', color: { base: '#ff4400', emissive: '#ff2200', accent: '#ffff00' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 55, amplitude: 0.1, pattern: 'sine' } },
|
| 546 |
+
},
|
| 547 |
+
endpoint: {
|
| 548 |
+
geometry: { type: 'cube', scale: { x: 1, y: 0.7, z: 0.1 }, baseScale: 0.7 },
|
| 549 |
+
material: { shader: 'standard', color: { base: '#44aaff', emissive: '#2288cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.05, pattern: 'breath' } },
|
| 550 |
+
},
|
| 551 |
+
ip_address: {
|
| 552 |
+
geometry: { type: 'tetrahedron', scale: { x: 0.6, y: 0.6, z: 0.6 }, baseScale: 0.5 },
|
| 553 |
+
material: { shader: 'hologram', color: { base: '#ffaa00', emissive: '#ff8800', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.08, pattern: 'sine' } },
|
| 554 |
+
},
|
| 555 |
+
department: {
|
| 556 |
+
geometry: { type: 'building', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
|
| 557 |
+
material: { shader: 'standard', color: { base: '#6699ff', emissive: '#4477cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.03, pattern: 'breath' } },
|
| 558 |
+
},
|
| 559 |
+
team: {
|
| 560 |
+
geometry: { type: 'sphere', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 1.5 },
|
| 561 |
+
material: { shader: 'standard', color: { base: '#88aaff', emissive: '#6688cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 35, amplitude: 0.05, pattern: 'breath' } },
|
| 562 |
+
},
|
| 563 |
+
campaign: {
|
| 564 |
+
geometry: { type: 'octahedron', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
|
| 565 |
+
material: { shader: 'threat', color: { base: '#aa0000', emissive: '#ff0000', accent: '#ffaa00' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 70, amplitude: 0.15, pattern: 'glitch' } },
|
| 566 |
+
},
|
| 567 |
+
threat_group: {
|
| 568 |
+
geometry: { type: 'dodecahedron', scale: { x: 2, y: 2, z: 2 }, baseScale: 3.0 },
|
| 569 |
+
material: { shader: 'threat', color: { base: '#770000', emissive: '#cc0000', accent: '#ffff00' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.1, pattern: 'heartbeat' } },
|
| 570 |
+
},
|
| 571 |
+
touchpoint: {
|
| 572 |
+
geometry: { type: 'sphere', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.8 },
|
| 573 |
+
material: { shader: 'standard', color: { base: '#00ffaa', emissive: '#00cc88', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 45, amplitude: 0.1, pattern: 'sine' } },
|
| 574 |
+
},
|
| 575 |
+
sla: {
|
| 576 |
+
geometry: { type: 'torus', scale: { x: 1, y: 1, z: 0.3 }, baseScale: 1.0 },
|
| 577 |
+
material: { shader: 'energy', color: { base: '#00ff00', emissive: '#00aa00', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 0.9, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.15, pattern: 'heartbeat' } },
|
| 578 |
+
},
|
| 579 |
+
task: {
|
| 580 |
+
geometry: { type: 'cube', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.8 },
|
| 581 |
+
material: { shader: 'hologram', color: { base: '#00ffff', emissive: '#00aaaa', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.08, pattern: 'sine' } },
|
| 582 |
+
},
|
| 583 |
+
memory: {
|
| 584 |
+
geometry: { type: 'torus', scale: { x: 1, y: 1, z: 0.5 }, baseScale: 1.2 },
|
| 585 |
+
material: { shader: 'organic', color: { base: '#ff00ff', emissive: '#aa00aa', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.1, pattern: 'breath' } },
|
| 586 |
+
},
|
| 587 |
+
|
| 588 |
+
// =============================================================================
|
| 589 |
+
// GEMINI ADDITIONS - New Entity Types
|
| 590 |
+
// =============================================================================
|
| 591 |
+
|
| 592 |
+
// Network additions
|
| 593 |
+
firewall_policy: {
|
| 594 |
+
geometry: { type: 'cube', scale: { x: 2, y: 1.5, z: 0.2 }, baseScale: 1.8 },
|
| 595 |
+
material: { shader: 'shield_hex', color: { base: '#ff6600', emissive: '#ff4400', accent: '#ffff00' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 45, amplitude: 0.08, pattern: 'sine' } },
|
| 596 |
+
},
|
| 597 |
+
api_gateway: {
|
| 598 |
+
geometry: { type: 'octahedron', scale: { x: 1.5, y: 2, z: 1.5 }, baseScale: 2.2 },
|
| 599 |
+
material: { shader: 'hologram', color: { base: '#00ccff', emissive: '#0099cc', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.1, pattern: 'sine' } },
|
| 600 |
+
},
|
| 601 |
+
load_balancer: {
|
| 602 |
+
geometry: { type: 'torus', scale: { x: 2, y: 2, z: 0.5 }, baseScale: 2.0 },
|
| 603 |
+
material: { shader: 'energy', color: { base: '#44ff88', emissive: '#22cc66', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.12, pattern: 'heartbeat' } },
|
| 604 |
+
},
|
| 605 |
+
|
| 606 |
+
// Cloud/Container entities
|
| 607 |
+
container_pod: {
|
| 608 |
+
geometry: { type: 'cube', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
|
| 609 |
+
material: { shader: 'cloud_vapor', color: { base: '#326ce5', emissive: '#2558c5', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.06, pattern: 'breath' } },
|
| 610 |
+
},
|
| 611 |
+
kubernetes_cluster: {
|
| 612 |
+
geometry: { type: 'dodecahedron', scale: { x: 3, y: 3, z: 3 }, baseScale: 4.0 },
|
| 613 |
+
material: { shader: 'cloud_vapor', color: { base: '#326ce5', emissive: '#2558c5', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.08, pattern: 'breath' } },
|
| 614 |
+
},
|
| 615 |
+
cloud_function: {
|
| 616 |
+
geometry: { type: 'tetrahedron', scale: { x: 1, y: 1.2, z: 1 }, baseScale: 1.0 },
|
| 617 |
+
material: { shader: 'cloud_vapor', color: { base: '#ff9900', emissive: '#cc7700', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 55, amplitude: 0.1, pattern: 'sine' } },
|
| 618 |
+
},
|
| 619 |
+
|
| 620 |
+
// Identity entities
|
| 621 |
+
identity_provider: {
|
| 622 |
+
geometry: { type: 'cylinder', scale: { x: 1.5, y: 2, z: 1.5 }, baseScale: 2.5 },
|
| 623 |
+
material: { shader: 'shield_hex', color: { base: '#9945ff', emissive: '#7733cc', accent: '#ffffff' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 35, amplitude: 0.06, pattern: 'breath' } },
|
| 624 |
+
},
|
| 625 |
+
service_account: {
|
| 626 |
+
geometry: { type: 'sphere', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.7 },
|
| 627 |
+
material: { shader: 'hologram', color: { base: '#aa88ff', emissive: '#8866cc', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.05, pattern: 'breath' } },
|
| 628 |
+
},
|
| 629 |
+
certificate: {
|
| 630 |
+
geometry: { type: 'cube', scale: { x: 1.2, y: 0.8, z: 0.1 }, baseScale: 0.8 },
|
| 631 |
+
material: { shader: 'standard', color: { base: '#00ff88', emissive: '#00cc66', accent: '#ffd700' }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 25, amplitude: 0.03, pattern: 'breath' } },
|
| 632 |
+
},
|
| 633 |
+
|
| 634 |
+
// IoT entities
|
| 635 |
+
iot_device: {
|
| 636 |
+
geometry: { type: 'sphere', scale: { x: 0.6, y: 0.6, z: 0.6 }, baseScale: 0.5 },
|
| 637 |
+
material: { shader: 'pulse_rings', color: { base: '#00ff00', emissive: '#00aa00', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 70, amplitude: 0.15, pattern: 'heartbeat' } },
|
| 638 |
+
},
|
| 639 |
+
sensor: {
|
| 640 |
+
geometry: { type: 'octahedron', scale: { x: 0.5, y: 0.7, z: 0.5 }, baseScale: 0.4 },
|
| 641 |
+
material: { shader: 'pulse_rings', color: { base: '#44ffaa', emissive: '#22cc88', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 80, amplitude: 0.2, pattern: 'sine' } },
|
| 642 |
+
},
|
| 643 |
+
|
| 644 |
+
// Security additions
|
| 645 |
+
vulnerability: {
|
| 646 |
+
geometry: { type: 'sphere', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.9 },
|
| 647 |
+
material: { shader: 'glitch_noise_v2', color: { base: '#ffaa00', emissive: '#ff8800', accent: '#ff0000' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 90, amplitude: 0.2, pattern: 'glitch' } },
|
| 648 |
+
},
|
| 649 |
+
exploit: {
|
| 650 |
+
geometry: { type: 'tetrahedron', scale: { x: 1, y: 1.5, z: 1 }, baseScale: 1.2 },
|
| 651 |
+
material: { shader: 'glitch_noise_v2', color: { base: '#ff0000', emissive: '#cc0000', accent: '#ffff00' }, opacity: 1, emissive: true, emissiveIntensity: 0.9, wireframe: false, pulse: { enabled: true, frequency: 110, amplitude: 0.25, pattern: 'glitch' } },
|
| 652 |
+
},
|
| 653 |
+
|
| 654 |
+
// Business additions
|
| 655 |
+
contract: {
|
| 656 |
+
geometry: { type: 'cube', scale: { x: 1.5, y: 1, z: 0.1 }, baseScale: 1.0 },
|
| 657 |
+
material: { shader: 'gold_metallic_clean', color: { base: '#ffd700', emissive: '#cc9900', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 20, amplitude: 0.02, pattern: 'breath' } },
|
| 658 |
+
},
|
| 659 |
+
invoice: {
|
| 660 |
+
geometry: { type: 'cube', scale: { x: 1.2, y: 0.8, z: 0.05 }, baseScale: 0.7 },
|
| 661 |
+
material: { shader: 'standard', color: { base: '#88ff88', emissive: '#66cc66', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.04, pattern: 'breath' } },
|
| 662 |
+
},
|
| 663 |
+
|
| 664 |
+
// =============================================================================
|
| 665 |
+
// GEMINI: SEPARATION OF CONCERN - Server Split
|
| 666 |
+
// =============================================================================
|
| 667 |
+
|
| 668 |
+
compute_node: {
|
| 669 |
+
geometry: { type: 'octahedron', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
|
| 670 |
+
material: { shader: 'energy', color: { base: '#ff6600', emissive: '#cc4400', accent: '#ffffff' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 80, amplitude: 0.15, pattern: 'heartbeat' } },
|
| 671 |
+
},
|
| 672 |
+
storage_node: {
|
| 673 |
+
geometry: { type: 'cylinder', scale: { x: 1.2, y: 2, z: 1.2 }, baseScale: 2.2 },
|
| 674 |
+
material: { shader: 'matte_ceramic_hex', color: { base: '#2c3e50', emissive: '#00ff41', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.05, pattern: 'breath' } },
|
| 675 |
+
},
|
| 676 |
+
|
| 677 |
+
// =============================================================================
|
| 678 |
+
// DEEPSEEK: COGNITIVE ENTITY TYPES
|
| 679 |
+
// =============================================================================
|
| 680 |
+
|
| 681 |
+
neural_node: {
|
| 682 |
+
geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.2 },
|
| 683 |
+
material: { shader: 'energy', color: { base: '#3399ff', emissive: '#0066cc', accent: '#ffffff' }, opacity: 0.7, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 150, amplitude: 0.1, pattern: 'sine' } },
|
| 684 |
+
},
|
| 685 |
+
synapse_connection: {
|
| 686 |
+
geometry: { type: 'cylinder', scale: { x: 0.1, y: 3, z: 0.1 }, baseScale: 1.0 },
|
| 687 |
+
material: { shader: 'flow_particle_stream', color: { base: '#ff6633', emissive: '#cc4400', accent: '#ffff00' }, opacity: 0.6, emissive: true, emissiveIntensity: 0.9, wireframe: false, pulse: { enabled: true, frequency: 480, amplitude: 0.2, pattern: 'sine' } },
|
| 688 |
+
},
|
| 689 |
+
concept_cluster: {
|
| 690 |
+
geometry: { type: 'dodecahedron', scale: { x: 2, y: 2, z: 2 }, baseScale: 2.5 },
|
| 691 |
+
material: { shader: 'organic', color: { base: '#66cc4d', emissive: '#44aa33', accent: '#ffffff' }, opacity: 0.6, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.08, pattern: 'breath' } },
|
| 692 |
+
},
|
| 693 |
+
memory_trace: {
|
| 694 |
+
geometry: { type: 'torus', scale: { x: 1.5, y: 1.5, z: 0.3 }, baseScale: 1.5 },
|
| 695 |
+
material: { shader: 'hologram', color: { base: '#cc99e6', emissive: '#9966cc', accent: '#ffffff' }, opacity: 0.3, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.15, pattern: 'breath' } },
|
| 696 |
+
},
|
| 697 |
+
metacognitive_monitor: {
|
| 698 |
+
geometry: { type: 'icosahedron', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
|
| 699 |
+
material: { shader: 'gold_metallic_clean', color: { base: '#ffe633', emissive: '#ccb300', accent: '#ffffff' }, opacity: 0.8, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 180, amplitude: 0.12, pattern: 'heartbeat' } },
|
| 700 |
+
},
|
| 701 |
+
};
|
| 702 |
+
|
| 703 |
+
// =============================================================================
|
| 704 |
+
// FACTORY FUNCTION
|
| 705 |
+
// =============================================================================
|
| 706 |
+
|
| 707 |
+
export function createVisualNode(
|
| 708 |
+
type: EntityType,
|
| 709 |
+
data: Partial<VisualNode>
|
| 710 |
+
): VisualNode {
|
| 711 |
+
const defaults = ENTITY_TYPE_DEFAULTS[type] || ENTITY_TYPE_DEFAULTS.unknown;
|
| 712 |
+
|
| 713 |
+
return {
|
| 714 |
+
id: data.id || crypto.randomUUID(),
|
| 715 |
+
externalId: data.externalId,
|
| 716 |
+
type,
|
| 717 |
+
labels: data.labels || [type],
|
| 718 |
+
position: data.position || { x: 0, y: 0, z: 0 },
|
| 719 |
+
velocity: data.velocity || { x: 0, y: 0, z: 0 },
|
| 720 |
+
rotation: data.rotation || { x: 0, y: 0, z: 0 },
|
| 721 |
+
mass: data.mass || 1,
|
| 722 |
+
fixed: data.fixed || false,
|
| 723 |
+
collisionRadius: data.collisionRadius || 1,
|
| 724 |
+
geometry: { ...defaults.geometry, ...data.geometry } as GeometryConfig,
|
| 725 |
+
material: { ...defaults.material, ...data.material } as MaterialConfig,
|
| 726 |
+
animation: data.animation || getDefaultAnimation(),
|
| 727 |
+
layers: data.layers || getEmptyLayers(),
|
| 728 |
+
currentLayer: data.currentLayer || 'surface',
|
| 729 |
+
interaction: data.interaction || getDefaultInteraction(),
|
| 730 |
+
lod: data.lod || getDefaultLOD(),
|
| 731 |
+
parent: data.parent,
|
| 732 |
+
children: data.children || [],
|
| 733 |
+
createdAt: data.createdAt || new Date().toISOString(),
|
| 734 |
+
updatedAt: data.updatedAt || new Date().toISOString(),
|
| 735 |
+
};
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
function getDefaultAnimation(): AnimationConfig {
|
| 739 |
+
return {
|
| 740 |
+
idle: { rotation: true, rotationSpeed: 10, float: true, floatAmplitude: 0.1, floatSpeed: 0.5 },
|
| 741 |
+
hover: { scaleMultiplier: 1.1, glowIntensity: 0.3, duration: 200 },
|
| 742 |
+
select: { scaleMultiplier: 1.2, outlineColor: '#ffffff', outlineWidth: 2, duration: 150 },
|
| 743 |
+
peel: { duration: 500, fragmentCount: 12, fragmentFadeTime: 800, revealGlowDuration: 300, sound: 'peel_default' },
|
| 744 |
+
};
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
function getEmptyLayers(): OnionLayers {
|
| 748 |
+
return {
|
| 749 |
+
surface: { icon: '❓', title: 'Unknown', status: 'unknown', activityLevel: 0, importance: 50, badges: [] },
|
| 750 |
+
metadata: { fullName: '', description: '', category: '', tags: [], source: '', created: '', modified: '', customFields: {} },
|
| 751 |
+
connections: { inbound: [], outbound: [], bidirectional: [], totalConnections: 0 },
|
| 752 |
+
structure: { children: [], hierarchy: [], depth: 0, expandable: false },
|
| 753 |
+
core: { rawDataRef: undefined, rawDataPreview: undefined, origin: { system: 'unknown', primaryKey: '' }, audit: { createdBy: '', createdAt: '', history: [], version: 1 }, actions: [] },
|
| 754 |
+
};
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
function getDefaultInteraction(): InteractionState {
|
| 758 |
+
return {
|
| 759 |
+
hoverable: true,
|
| 760 |
+
selectable: true,
|
| 761 |
+
grabbable: true,
|
| 762 |
+
peelable: true,
|
| 763 |
+
expandable: true,
|
| 764 |
+
isHovered: false,
|
| 765 |
+
isSelected: false,
|
| 766 |
+
isGrabbed: false,
|
| 767 |
+
isPeeling: false,
|
| 768 |
+
};
|
| 769 |
+
}
|
| 770 |
+
|
| 771 |
+
function getDefaultLOD(): LODConfig {
|
| 772 |
+
return {
|
| 773 |
+
near: 5,
|
| 774 |
+
mid: 20,
|
| 775 |
+
far: 50,
|
| 776 |
+
nearGeometry: 'sphere',
|
| 777 |
+
midGeometry: 'octahedron',
|
| 778 |
+
farGeometry: 'point',
|
| 779 |
+
showLabelsAt: 15,
|
| 780 |
+
showConnectionsAt: 30,
|
| 781 |
+
};
|
| 782 |
+
}
|