Dashboard: - Add Studio page with drag-drop model upload and Claude chat - Add intake system for study creation workflow - Improve session manager and context builder - Add intake API routes and frontend components Optimization Engine: - Add CLI module for command-line operations - Add intake module for study preprocessing - Add validation module with gate checks - Improve Zernike extractor documentation - Update spec models with better validation - Enhance solve_simulation robustness Documentation: - Add ATOMIZER_STUDIO.md planning doc - Add ATOMIZER_UX_SYSTEM.md for UX patterns - Update extractor library docs - Add study-readme-generator skill Tools: - Add test scripts for extraction validation - Add Zernike recentering test Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
412 lines
10 KiB
TypeScript
412 lines
10 KiB
TypeScript
/**
|
|
* Intake API Client
|
|
*
|
|
* API client methods for the study intake workflow.
|
|
*/
|
|
|
|
import {
|
|
CreateInboxRequest,
|
|
CreateInboxResponse,
|
|
IntrospectRequest,
|
|
IntrospectResponse,
|
|
ListInboxResponse,
|
|
ListTopicsResponse,
|
|
InboxStudyDetail,
|
|
GenerateReadmeResponse,
|
|
FinalizeRequest,
|
|
FinalizeResponse,
|
|
UploadFilesResponse,
|
|
} from '../types/intake';
|
|
|
|
const API_BASE = '/api';
|
|
|
|
/**
|
|
* Intake API client for study creation workflow.
|
|
*/
|
|
export const intakeApi = {
|
|
/**
|
|
* Create a new inbox study folder with initial spec.
|
|
*/
|
|
async createInbox(request: CreateInboxRequest): Promise<CreateInboxResponse> {
|
|
const response = await fetch(`${API_BASE}/intake/create`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to create inbox study');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Run NX introspection on an inbox study.
|
|
*/
|
|
async introspect(request: IntrospectRequest): Promise<IntrospectResponse> {
|
|
const response = await fetch(`${API_BASE}/intake/introspect`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Introspection failed');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* List all studies in the inbox.
|
|
*/
|
|
async listInbox(): Promise<ListInboxResponse> {
|
|
const response = await fetch(`${API_BASE}/intake/list`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch inbox studies');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* List existing topic folders.
|
|
*/
|
|
async listTopics(): Promise<ListTopicsResponse> {
|
|
const response = await fetch(`${API_BASE}/intake/topics`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch topics');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Get detailed information about an inbox study.
|
|
*/
|
|
async getInboxStudy(studyName: string): Promise<InboxStudyDetail> {
|
|
const response = await fetch(`${API_BASE}/intake/${encodeURIComponent(studyName)}`);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to fetch inbox study');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Delete an inbox study.
|
|
*/
|
|
async deleteInboxStudy(studyName: string): Promise<{ success: boolean; deleted: string }> {
|
|
const response = await fetch(`${API_BASE}/intake/${encodeURIComponent(studyName)}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to delete inbox study');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Generate README for an inbox study using Claude AI.
|
|
*/
|
|
async generateReadme(studyName: string): Promise<GenerateReadmeResponse> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/readme`,
|
|
{ method: 'POST' }
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'README generation failed');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Finalize an inbox study and move to studies directory.
|
|
*/
|
|
async finalize(studyName: string, request: FinalizeRequest): Promise<FinalizeResponse> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/finalize`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(request),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Finalization failed');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Upload model files to an inbox study.
|
|
*/
|
|
async uploadFiles(studyName: string, files: File[]): Promise<UploadFilesResponse> {
|
|
const formData = new FormData();
|
|
files.forEach((file) => {
|
|
formData.append('files', file);
|
|
});
|
|
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/upload`,
|
|
{
|
|
method: 'POST',
|
|
body: formData,
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'File upload failed');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Upload context files to an inbox study.
|
|
* Context files help Claude understand optimization goals.
|
|
*/
|
|
async uploadContextFiles(studyName: string, files: File[]): Promise<UploadFilesResponse> {
|
|
const formData = new FormData();
|
|
files.forEach((file) => {
|
|
formData.append('files', file);
|
|
});
|
|
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/context`,
|
|
{
|
|
method: 'POST',
|
|
body: formData,
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Context file upload failed');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* List context files for an inbox study.
|
|
*/
|
|
async listContextFiles(studyName: string): Promise<{
|
|
study_name: string;
|
|
context_files: Array<{ name: string; path: string; size: number; extension: string }>;
|
|
total: number;
|
|
}> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/context`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to list context files');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Delete a context file from an inbox study.
|
|
*/
|
|
async deleteContextFile(studyName: string, filename: string): Promise<{ success: boolean; deleted: string }> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/context/${encodeURIComponent(filename)}`,
|
|
{ method: 'DELETE' }
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to delete context file');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Create design variables from selected expressions.
|
|
*/
|
|
async createDesignVariables(
|
|
studyName: string,
|
|
expressionNames: string[],
|
|
options?: { autoBounds?: boolean; boundFactor?: number }
|
|
): Promise<{
|
|
success: boolean;
|
|
study_name: string;
|
|
created: Array<{
|
|
id: string;
|
|
name: string;
|
|
expression_name: string;
|
|
bounds_min: number;
|
|
bounds_max: number;
|
|
baseline: number;
|
|
units: string | null;
|
|
}>;
|
|
total_created: number;
|
|
}> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/design-variables`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
expression_names: expressionNames,
|
|
auto_bounds: options?.autoBounds ?? true,
|
|
bound_factor: options?.boundFactor ?? 0.5,
|
|
}),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to create design variables');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
// ===========================================================================
|
|
// Studio Endpoints (Atomizer Studio - Unified Creation Environment)
|
|
// ===========================================================================
|
|
|
|
/**
|
|
* Create an anonymous draft study for Studio workflow.
|
|
* Returns a temporary draft_id that can be renamed during finalization.
|
|
*/
|
|
async createDraft(): Promise<{
|
|
success: boolean;
|
|
draft_id: string;
|
|
inbox_path: string;
|
|
spec_path: string;
|
|
status: string;
|
|
}> {
|
|
const response = await fetch(`${API_BASE}/intake/draft`, {
|
|
method: 'POST',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to create draft');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Get extracted text content from context files.
|
|
* Used for AI context injection.
|
|
*/
|
|
async getContextContent(studyName: string): Promise<{
|
|
success: boolean;
|
|
study_name: string;
|
|
content: string;
|
|
files_read: Array<{
|
|
name: string;
|
|
extension: string;
|
|
size: number;
|
|
status: string;
|
|
characters?: number;
|
|
error?: string;
|
|
}>;
|
|
total_characters: number;
|
|
}> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/context/content`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to get context content');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Finalize a Studio draft with rename support.
|
|
* Enhanced version that supports renaming draft_xxx to proper names.
|
|
*/
|
|
async finalizeStudio(
|
|
studyName: string,
|
|
request: {
|
|
topic: string;
|
|
newName?: string;
|
|
runBaseline?: boolean;
|
|
}
|
|
): Promise<{
|
|
success: boolean;
|
|
original_name: string;
|
|
final_name: string;
|
|
final_path: string;
|
|
status: string;
|
|
baseline_success: boolean | null;
|
|
readme_generated: boolean;
|
|
}> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/finalize/studio`,
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
topic: request.topic,
|
|
new_name: request.newName,
|
|
run_baseline: request.runBaseline ?? false,
|
|
}),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Studio finalization failed');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* Get complete draft information for Studio UI.
|
|
* Convenience endpoint that returns everything the Studio needs.
|
|
*/
|
|
async getStudioDraft(studyName: string): Promise<{
|
|
success: boolean;
|
|
draft_id: string;
|
|
spec: Record<string, unknown>;
|
|
model_files: string[];
|
|
context_files: string[];
|
|
introspection_available: boolean;
|
|
design_variable_count: number;
|
|
objective_count: number;
|
|
}> {
|
|
const response = await fetch(
|
|
`${API_BASE}/intake/${encodeURIComponent(studyName)}/studio`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to get studio draft');
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
};
|
|
|
|
export default intakeApi;
|