-
Notifications
You must be signed in to change notification settings - Fork 18
feat(frontend): adapters, services, state and forms engine #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Jatin-8898
merged 2 commits into
blackrock:main
from
maan-iitd2:pr/frontend-services-state
Sep 16, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // HttpChatAdapter — sends messages to the real backend chat API. | ||
| // Implements the same interface as MockChatAdapter. | ||
|
|
||
| /** @typedef {{ ops: object[], reply: string }} ChatResult */ | ||
|
|
||
| export class HttpChatAdapter { | ||
| /** @param {import('./httpClient.js').HttpClient} client */ | ||
| constructor(client) { | ||
| this._client = client; | ||
| } | ||
|
|
||
| /** | ||
| * @param {string} message | ||
| * @param {string[]} columns | ||
| * @param {string} yaml | ||
| * @param {string} interfaceName | ||
| * @param {{role: string, content: string}[]} history recent turns for follow-up context | ||
| * @returns {Promise<ChatResult>} | ||
| */ | ||
| async interpret(message, columns = [], yaml = '', interfaceName = '', history = []) { | ||
| const res = await fetch(`${this._client.baseUrl}/api/chat`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ message, columns, yaml, interface: interfaceName, history }), | ||
| }); | ||
| if (!res.ok) { | ||
| const detail = await res.json().catch(() => ({})); | ||
| throw new Error(detail.detail || `Chat failed (${res.status})`); | ||
| } | ||
| const { ops, reply } = await res.json(); | ||
| return { | ||
| ops: Array.isArray(ops) ? ops : [], | ||
| reply: typeof reply === 'string' ? reply : '', | ||
| }; | ||
| } | ||
|
|
||
| /** Fire-and-forget warmup to pre-load the model on the backend. */ | ||
| warmup() { | ||
| fetch(`${this._client.baseUrl}/api/chat/warmup`, { method: 'POST' }).catch(() => {}); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // InGen Studio — HTTP client | ||
| // | ||
| // Thin fetch wrapper shared by the Http* adapters. Base URL comes from NEXT_PUBLIC_API_BASE_URL | ||
| // (defaults to the local FastAPI wrapper). Surfaces the backend's `detail` message on errors. | ||
|
|
||
| const DEFAULT_BASE = ''; | ||
|
|
||
| export class HttpClient { | ||
| constructor(baseUrl) { | ||
| this.baseUrl = (baseUrl != null && baseUrl !== '' ? baseUrl : DEFAULT_BASE).replace(/\/$/, ''); | ||
| } | ||
|
|
||
| async request(method, path, body) { | ||
| const res = await fetch(`${this.baseUrl}${path}`, { | ||
| method, | ||
| headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined, | ||
| body: body !== undefined ? JSON.stringify(body) : undefined, | ||
| }); | ||
| if (!res.ok) { | ||
| let detail; | ||
| try { detail = (await res.json())?.detail; } catch { /* non-JSON error body */ } | ||
| throw new Error(`HTTP ${res.status} on ${path}${detail ? `: ${detail}` : ''}`); | ||
| } | ||
| if (res.status === 204) return null; | ||
| return res.json(); | ||
| } | ||
|
|
||
| get(path) { return this.request('GET', path); } | ||
| post(path, body) { return this.request('POST', path, body); } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // InGen Studio — HTTP ConfigService adapter | ||
| // | ||
| // Config CRUD has no backend endpoint in scope, so persistence (list/get/create/update/remove) and | ||
| // YAML import/export are inherited unchanged from the localStorage-backed MockConfigAdapter. Only | ||
| // `validate` is routed to the FastAPI wrapper (POST /api/configs/validate), which performs the same | ||
| // schema/cross-reference checks server-side. Same ConfigService interface, so callers don't change. | ||
|
|
||
| import { MockConfigAdapter } from './mockConfigAdapter.js'; | ||
| import { modelToYaml } from '../serializers/index.js'; | ||
|
|
||
| export class HttpConfigAdapter extends MockConfigAdapter { | ||
| constructor(client) { | ||
| super(); | ||
| this.client = client; | ||
| } | ||
|
|
||
| /** @param {import('../models/types.js').ConfigModel} model */ | ||
| async validate(model) { | ||
| const yaml = modelToYaml(model); | ||
| const res = await this.client.post('/api/configs/validate', { yaml }); | ||
| return res.issues ?? []; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // InGen Studio — HTTP HistoryService adapter | ||
| // | ||
| // Run history is owned by the backend: a run is persisted by POST /api/runs at execution time, so | ||
| // `add` is a no-op here (avoids double-writing). `list`/`get` read from the wrapper. `clear` has no | ||
| // backend endpoint in scope and is a no-op (history is server-authoritative in HTTP mode). | ||
|
|
||
| import { HistoryService } from '../services/historyService.js'; | ||
|
|
||
| export class HttpHistoryAdapter extends HistoryService { | ||
| constructor(client) { | ||
| super(); | ||
| this.client = client; | ||
| } | ||
|
|
||
| async list(configId) { | ||
| return this.client.get(`/api/runs/history?config_id=${encodeURIComponent(configId)}`); | ||
| } | ||
|
|
||
| async get(runId) { | ||
| return this.client.get(`/api/runs/${encodeURIComponent(runId)}`); | ||
| } | ||
|
|
||
| async add() { | ||
| // No-op: the backend already persisted the run during POST /api/runs. | ||
| } | ||
|
|
||
| async clear() { | ||
| // No-op: no delete endpoint in scope; history is backend-owned in HTTP mode. | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // InGen Studio — HTTP RunService adapter | ||
| // | ||
| // Serializes the model with the SAME serializer the editor uses, POSTs the YAML to the wrapper | ||
| // (which runs `python -m ingen` and returns a structured RunRecord), then replays the record's | ||
| // logs and stages through `onEvent` so the live console panels (timeline, log stream) populate | ||
| // exactly as they do in mock mode. Returns the identical RunRecord shape — components don't change. | ||
|
|
||
| import { RunService } from '../services/runService.js'; | ||
| import { modelToYaml } from '../serializers/index.js'; | ||
| import { makeId } from '../utils/id.js'; | ||
|
|
||
| export class HttpRunAdapter extends RunService { | ||
| constructor(client) { | ||
| super(); | ||
| this.client = client; | ||
| } | ||
|
|
||
| async simulate(model, overrides = {}, handlers = {}) { | ||
| const { onEvent, isCancelled } = handlers; | ||
| const yaml = modelToYaml(model); | ||
|
|
||
| if (isCancelled?.()) { | ||
| return this._cancelledRecord(model, overrides); | ||
| } | ||
|
|
||
| const record = await this.client.post('/api/runs', { | ||
| yaml, | ||
| configId: model.meta.id, | ||
| configName: model.meta.name, | ||
| run_date: overrides.run_date ?? null, | ||
| interfaces: overrides.interfaces ?? null, | ||
| query_params: overrides.query_params ?? null, | ||
| override_params: overrides.override_params ?? null, | ||
| }); | ||
|
|
||
| // Replay backend results as a stream so the UI behaves identically to mock mode. | ||
| if (onEvent) { | ||
| (record.logs ?? []).forEach((e) => onEvent(e)); | ||
| (record.stages ?? []).forEach((s) => | ||
| onEvent({ type: 'stage', ts: record.startedAt, interface: s.interface, stage: s.stage, status: s.status })); | ||
| } | ||
| return record; | ||
| } | ||
|
|
||
| _cancelledRecord(model, overrides) { | ||
| const ts = new Date().toISOString(); | ||
| return { | ||
| runId: makeId('run_cancelled'), | ||
| configId: model.meta.id, | ||
| configName: model.meta.name, | ||
| status: 'failed', | ||
| startedAt: ts, | ||
| finishedAt: ts, | ||
| durationMs: 0, | ||
| stages: [], | ||
| logs: [{ type: 'log', ts, level: 'warn', message: 'Run cancelled before submission.' }], | ||
| validation: { results: [], summary: { passed: 0, failed: 0, warning: 0, total: 0 } }, | ||
| overrides, | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // InGen Studio — HTTP ValidationService adapter | ||
| // | ||
| // In HTTP mode, real validation RESULTS come from a run (GET /api/runs/{id}/validation, surfaced via | ||
| // the RunRecord). `evaluate` is a pre-run preview of the expectations configured in the model — it | ||
| // enumerates them client-side (no execution), matching the ValidationReport shape. This keeps the | ||
| // ValidationService interface satisfied; the method is not on the HTTP run path (only the mock run | ||
| // adapter calls evaluate internally). | ||
|
|
||
| import { ValidationService } from '../services/validationService.js'; | ||
|
|
||
| export class HttpValidationAdapter extends ValidationService { | ||
| constructor(client) { | ||
| super(); | ||
| this.client = client; // reserved for a future /api/configs/validate-expectations endpoint | ||
| } | ||
|
|
||
| async evaluate(model, interfaceNames) { | ||
| const names = interfaceNames?.length ? interfaceNames : model.interfaceOrder; | ||
| const results = []; | ||
| for (const name of names) { | ||
| const iface = model.interfacesByName[name]; | ||
| if (!iface) continue; | ||
| for (const col of iface.columns ?? []) { | ||
| const column = col.dest_col_name || col.src_col_name || '(unnamed)'; | ||
| for (const v of col.validations ?? []) { | ||
| results.push({ | ||
| interface: name, | ||
| column, | ||
| expectation: v.type, | ||
| severity: v.severity ?? 'warning', | ||
| status: 'passed', // preview only — real status comes from a run | ||
|
Jatin-8898 marked this conversation as resolved.
|
||
| unexpectedCount: 0, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| const summary = { passed: results.length, failed: 0, warning: 0, total: results.length }; | ||
| return { results, summary }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| // InGen Studio — adapter selection (the one swap point) | ||
| // | ||
| // `buildServices(mode)` returns the concrete service set for a given backend mode. Two sets | ||
| // exist: `mock` (localStorage, no server required) and `http` (the FastAPI wrapper). Both | ||
| // implement the same service interfaces, so callers work unchanged against either. | ||
|
|
||
| import { ADAPTER_MODE } from '../models/constants.js'; | ||
| import { MockConfigAdapter } from './mockConfigAdapter.js'; | ||
| import { MockCatalogAdapter } from './mockCatalogAdapter.js'; | ||
| import { MockValidationAdapter } from './mockValidationAdapter.js'; | ||
| import { MockRunAdapter } from './mockRunAdapter.js'; | ||
| import { MockHistoryAdapter } from './mockHistoryAdapter.js'; | ||
| import { HttpClient } from './httpClient.js'; | ||
| import { HttpConfigAdapter } from './httpConfigAdapter.js'; | ||
| import { HttpValidationAdapter } from './httpValidationAdapter.js'; | ||
| import { HttpRunAdapter } from './httpRunAdapter.js'; | ||
| import { HttpHistoryAdapter } from './httpHistoryAdapter.js'; | ||
| import { MockChatAdapter } from './mockChatAdapter.js'; | ||
| import { HttpChatAdapter } from './httpChatAdapter.js'; | ||
|
|
||
| /** | ||
| * @typedef {Object} ServiceSet | ||
| * @property {import('../services/configService.js').ConfigService} config | ||
| * @property {import('../services/catalogService.js').CatalogService} catalog | ||
| * @property {import('../services/validationService.js').ValidationService} validation | ||
| * @property {import('../services/runService.js').RunService} run | ||
| * @property {import('../services/historyService.js').HistoryService} history | ||
| * @property {{ interpret: Function, warmup: Function }} chat | ||
| */ | ||
|
|
||
| /** | ||
| * @param {string} [mode] one of ADAPTER_MODE.*; defaults to MOCK. | ||
| * @returns {ServiceSet} | ||
| */ | ||
| export function buildServices(mode = ADAPTER_MODE.MOCK) { | ||
| switch (mode) { | ||
| case ADAPTER_MODE.MOCK: { | ||
| const validation = new MockValidationAdapter(); | ||
| return { | ||
| config: new MockConfigAdapter(), | ||
| catalog: new MockCatalogAdapter(), | ||
| validation, | ||
| run: new MockRunAdapter(validation), // run reuses the same validation service | ||
| history: new MockHistoryAdapter(), | ||
| chat: new MockChatAdapter(), | ||
| }; | ||
| } | ||
| case ADAPTER_MODE.HTTP: { | ||
| // Base URL of the FastAPI wrapper; configurable via NEXT_PUBLIC_API_BASE_URL. | ||
| const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL; | ||
| const client = new HttpClient(baseUrl); | ||
| return { | ||
| config: new HttpConfigAdapter(client), // CRUD via localStorage; validate → backend | ||
| catalog: new MockCatalogAdapter(), // catalog is static (backend-derived constants) | ||
| validation: new HttpValidationAdapter(client), | ||
| run: new HttpRunAdapter(client), | ||
| history: new HttpHistoryAdapter(client), | ||
| chat: new HttpChatAdapter(client), | ||
| }; | ||
| } | ||
| default: | ||
| throw new Error(`Unknown adapter mode "${mode}"`); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // InGen Studio — Mock CatalogService adapter | ||
| // | ||
| // Returns the static catalog assembled from the backend-derived registries in models/constants.js. | ||
| // Real and final: these values match the backend today, so this adapter survives into production | ||
| // unless the catalog is ever moved server-side. | ||
|
|
||
| import { CatalogService } from '../services/catalogService.js'; | ||
| import { | ||
| SOURCE_TYPES, | ||
| FILE_TYPES, | ||
| PRE_PROCESSOR_TYPES, | ||
| POST_PROCESSOR_TYPES, | ||
| FORMATTER_TYPES, | ||
| VALIDATION_TYPES, | ||
| VALIDATION_SEVERITIES, | ||
| OUTPUT_TYPES, | ||
| INTERPOLATORS, | ||
| } from '../models/constants.js'; | ||
|
|
||
| /** @typedef {import('../services/catalogService.js').Catalog} Catalog */ | ||
|
|
||
| export class MockCatalogAdapter extends CatalogService { | ||
| /** @returns {Promise<Catalog>} */ | ||
| async getCatalog() { | ||
| return { | ||
| sourceTypes: Object.values(SOURCE_TYPES), | ||
| fileTypes: Object.values(FILE_TYPES), | ||
| preProcessors: Object.values(PRE_PROCESSOR_TYPES), | ||
| postProcessors: Object.values(POST_PROCESSOR_TYPES), | ||
| formatters: [...FORMATTER_TYPES], | ||
| validations: { builtin: [...VALIDATION_TYPES.BUILTIN], custom: [...VALIDATION_TYPES.CUSTOM] }, | ||
| validationSeverities: Object.values(VALIDATION_SEVERITIES), | ||
| outputTypes: Object.values(OUTPUT_TYPES), | ||
| interpolators: { static: [...INTERPOLATORS.STATIC], runtime: [...INTERPOLATORS.RUNTIME] }, | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // MockChatAdapter — returns regex-parsed ops without network, matching the ChatService interface. | ||
| // Used when ADAPTER_MODE is MOCK (no backend). | ||
|
|
||
| /** @typedef {{ ops: object[], reply: string }} ChatResult */ | ||
|
|
||
| export class MockChatAdapter { | ||
| /** | ||
| * Interpret a user message as pipeline edit ops. | ||
| * In mock mode, always throws so the caller falls back to its local regex parser. | ||
| * Accepts the same arguments as HttpChatAdapter.interpret | ||
| * (message, columns, yaml, interfaceName, history) but ignores all of them. | ||
| * @returns {Promise<ChatResult>} | ||
| */ | ||
| async interpret() { | ||
| throw new Error('LLM unavailable (mock mode) — using regex fallback'); | ||
| } | ||
|
|
||
| /** No-op in mock mode. */ | ||
| warmup() {} | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.