Built the Oracle Tab (#14)

This commit is contained in:
2026-04-11 19:35:45 +05:30
committed by Sagnik
parent 8e1ffe0e43
commit fb656d1443
54 changed files with 10651 additions and 818 deletions

View File

@@ -0,0 +1,130 @@
/**
* useOracleExecution — manages prompt submission and durable execution history.
*/
import { useState, useCallback, useRef } from 'react';
import type { PromptExecution, CanvasComponent, PlacementMode } from '../types/canvas';
import { submitPrompt } from '../lib/oracleApiClient';
export interface ExecutionEntry {
execution: PromptExecution;
componentsCreated: string[];
}
export interface OracleExecutionState {
history: ExecutionEntry[];
inFlight: PromptExecution | null;
lastError: string | null;
submit: (params: {
pageId: string;
branchId: string;
prompt: string;
tenantId: string;
actorId: string;
placementMode?: PlacementMode;
conversationContext?: Array<{ role: 'user' | 'assistant'; content: string }>;
onExecutionCommitted?: (commit: {
headRevision: number;
components: CanvasComponent[];
execution: PromptExecution;
}) => void;
}) => Promise<void>;
clearError: () => void;
}
export function useOracleExecution(): OracleExecutionState {
const [history, setHistory] = useState<ExecutionEntry[]>([]);
const [inFlight, setInFlight] = useState<PromptExecution | null>(null);
const [lastError, setLastError] = useState<string | null>(null);
const requestIdRef = useRef(0);
const submit = useCallback(
async ({
pageId,
branchId,
prompt,
tenantId,
actorId,
placementMode = 'append_after_last_visible_component',
conversationContext = [],
onExecutionCommitted,
}: {
pageId: string;
branchId: string;
prompt: string;
tenantId: string;
actorId: string;
placementMode?: PlacementMode;
conversationContext?: Array<{ role: 'user' | 'assistant'; content: string }>;
onExecutionCommitted?: (commit: {
headRevision: number;
components: CanvasComponent[];
execution: PromptExecution;
}) => void;
}) => {
const clientRequestId = `cli_${Date.now()}_${++requestIdRef.current}`;
const now = new Date().toISOString();
const optimistic: PromptExecution = {
executionId: `pex_${clientRequestId}`,
tenantId,
pageId,
branchId,
actorId,
prompt,
intentClass: 'analytical',
status: 'planning',
modelRuntime: 'oracle_runtime',
semanticModelVersion: 'oracle_semantic_v2026_04_08_01',
warnings: [],
createdAt: now,
};
setInFlight(optimistic);
setLastError(null);
try {
setInFlight((prev) => (prev ? { ...prev, status: 'executing' } : prev));
const response = await submitPrompt(pageId, {
clientRequestId,
branchId,
prompt,
conversationContext,
placementMode,
});
const completed: PromptExecution = {
...optimistic,
executionId: response.executionId,
status: response.status,
summary: response.summary,
warnings: response.warnings,
componentsCreated: response.componentsCreated,
completedAt: new Date().toISOString(),
};
onExecutionCommitted?.({
headRevision: response.headRevision,
components: response.components,
execution: completed,
});
setHistory((prev) => [...prev, { execution: completed, componentsCreated: response.componentsCreated }]);
setInFlight(null);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Prompt execution failed';
const failed: PromptExecution = {
...optimistic,
status: 'failed',
warnings: [msg],
completedAt: new Date().toISOString(),
};
setHistory((prev) => [...prev, { execution: failed, componentsCreated: [] }]);
setInFlight(null);
setLastError(msg);
}
},
[],
);
return { history, inFlight, lastError, submit, clearError: () => setLastError(null) };
}

View File

@@ -0,0 +1,120 @@
/**
* useOraclePage — Page hydration, branch state, component projection
* Owns the canonical page state including revision tracking and optimistic updates.
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import type { CanvasPage, CanvasComponent, OracleWSMessage } from '../types/canvas';
import { fetchCanvasPage, connectPageSocket } from '../lib/oracleApiClient';
export interface OraclePageState {
page: CanvasPage | null;
isLoading: boolean;
error: string | null;
isConnected: boolean;
// Actions
refresh: () => Promise<void>;
optimisticallyAppendComponent: (comp: CanvasComponent) => void;
applyRevision: (headRevision: number, components: CanvasComponent[]) => void;
}
export function useOraclePage(pageId: string | null): OraclePageState {
const [page, setPage] = useState<CanvasPage | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isConnected, setIsConnected] = useState(false);
const disconnectRef = useRef<(() => void) | null>(null);
const load = useCallback(async () => {
if (!pageId) {
setPage(null);
setIsLoading(false);
return;
}
setIsLoading(true);
setError(null);
try {
const data = await fetchCanvasPage(pageId);
setPage(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load page');
} finally {
setIsLoading(false);
}
}, [pageId]);
// Connect WebSocket
useEffect(() => {
if (!pageId) {
setIsConnected(false);
return () => undefined;
}
const disconnect = connectPageSocket(pageId, {
onMessage: (msg: OracleWSMessage) => handleWSMessage(msg),
onReconnect: () => void load(),
onClose: () => setIsConnected(false),
});
disconnectRef.current = disconnect;
setIsConnected(true);
return () => {
disconnect();
disconnectRef.current = null;
};
}, [pageId, load]); // eslint-disable-line react-hooks/exhaustive-deps
// Handle WS messages
function handleWSMessage(msg: OracleWSMessage) {
if (msg.type === 'oracle.page.revision.committed') {
const { headRevision, components } = msg.payload as {
headRevision: number;
components: CanvasComponent[];
};
applyRevision(headRevision, components);
} else if (msg.type === 'oracle.presence.updated') {
setPage((prev) =>
prev
? {
...prev,
presence: {
activeViewers: (msg.payload.activeViewers as number) ?? prev.presence.activeViewers,
activeEditors: (msg.payload.activeEditors as number) ?? prev.presence.activeEditors,
lastPresenceAt: msg.timestamp,
},
}
: prev,
);
}
}
const optimisticallyAppendComponent = useCallback((comp: CanvasComponent) => {
setPage((prev) => {
if (!prev) return prev;
// Prevent duplicate (idempotent insert)
if (prev.components.some((c) => c.componentId === comp.componentId)) return prev;
return { ...prev, components: [...prev.components, comp] };
});
}, []);
const applyRevision = useCallback(
(headRevision: number, components: CanvasComponent[]) => {
setPage((prev) =>
prev ? { ...prev, headRevision, components } : prev,
);
},
[],
);
// Initial load
useEffect(() => {
void load();
}, [load]);
return {
page,
isLoading,
error,
isConnected,
refresh: load,
optimisticallyAppendComponent,
applyRevision,
};
}