const API_CONFIG = { local: { http: "http://localhost:8080", ws: "ws://localhost:8080", }, production: { http: "https://api.makima.jp", ws: "wss://api.makima.jp", }, } as const; type Environment = "local" | "production"; function detectEnvironment(): Environment { // Check if explicitly set via env var const envOverride = import.meta.env.VITE_API_ENV as Environment | undefined; if (envOverride && (envOverride === "local" || envOverride === "production")) { return envOverride; } // Auto-detect based on hostname if (typeof window !== "undefined") { const hostname = window.location.hostname; if (hostname === "localhost" || hostname === "127.0.0.1") { return "local"; } } return "production"; } const env = detectEnvironment(); export const API_BASE = API_CONFIG[env].http; export const WS_BASE = API_CONFIG[env].ws; export const LISTEN_ENDPOINT = `${WS_BASE}/api/v1/listen`; export const FILE_SUBSCRIBE_ENDPOINT = `${WS_BASE}/api/v1/files/subscribe`; export function getEnvironment(): Environment { return env; } // File API types export interface TranscriptEntry { id: string; speaker: string; start: number; end: number; text: string; isFinal: boolean; } // Chart types for visualization export type ChartType = "line" | "bar" | "pie" | "area"; // Body element types for structured content export type BodyElement = | { type: "heading"; level: number; text: string } | { type: "paragraph"; text: string } | { type: "chart"; chartType: ChartType; title?: string; data: Record[]; config?: Record; } | { type: "image"; src: string; alt?: string; caption?: string }; export interface FileSummary { id: string; name: string; description: string | null; transcriptCount: number; duration: number | null; version: number; createdAt: string; updatedAt: string; } export interface FileDetail { id: string; ownerId: string; name: string; description: string | null; transcript: TranscriptEntry[]; location: string | null; summary: string | null; body: BodyElement[]; version: number; createdAt: string; updatedAt: string; } export interface FileListResponse { files: FileSummary[]; total: number; } export interface CreateFileRequest { name?: string; description?: string; transcript: TranscriptEntry[]; location?: string; } export interface UpdateFileRequest { name?: string; description?: string; transcript?: TranscriptEntry[]; summary?: string; body?: BodyElement[]; version?: number; } // Conflict error types export interface ConflictErrorResponse { code: "VERSION_CONFLICT"; message: string; expectedVersion: number; actualVersion: number; } export class VersionConflictError extends Error { expectedVersion: number; actualVersion: number; constructor(conflict: ConflictErrorResponse) { super(conflict.message); this.name = "VersionConflictError"; this.expectedVersion = conflict.expectedVersion; this.actualVersion = conflict.actualVersion; } } // Available LLM models export type LlmModel = "claude-sonnet" | "claude-opus" | "groq"; // Chat API types export interface ChatMessage { role: "user" | "assistant"; content: string; } export interface ChatRequest { message: string; model?: LlmModel; history?: ChatMessage[]; } export interface ToolCallInfo { name: string; result: { success: boolean; message: string; }; } // User question types for interactive LLM tool export interface UserQuestion { id: string; question: string; options: string[]; allowMultiple: boolean; allowCustom: boolean; } export interface UserAnswer { id: string; answers: string[]; } export interface ChatResponse { response: string; toolCalls: ToolCallInfo[]; updatedBody: BodyElement[]; updatedSummary: string | null; pendingQuestions?: UserQuestion[]; } // File API functions export async function listFiles(): Promise { const res = await fetch(`${API_BASE}/api/v1/files`); if (!res.ok) { throw new Error(`Failed to list files: ${res.statusText}`); } return res.json(); } export async function getFile(id: string): Promise { const res = await fetch(`${API_BASE}/api/v1/files/${id}`); if (!res.ok) { throw new Error(`Failed to get file: ${res.statusText}`); } return res.json(); } export async function createFile(data: CreateFileRequest): Promise { const res = await fetch(`${API_BASE}/api/v1/files`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!res.ok) { throw new Error(`Failed to create file: ${res.statusText}`); } return res.json(); } export async function updateFile( id: string, data: UpdateFileRequest ): Promise { const res = await fetch(`${API_BASE}/api/v1/files/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (res.status === 409) { const conflict = (await res.json()) as ConflictErrorResponse; throw new VersionConflictError(conflict); } if (!res.ok) { throw new Error(`Failed to update file: ${res.statusText}`); } return res.json(); } export async function deleteFile(id: string): Promise { const res = await fetch(`${API_BASE}/api/v1/files/${id}`, { method: "DELETE", }); if (!res.ok) { throw new Error(`Failed to delete file: ${res.statusText}`); } } // Chat API function export async function chatWithFile( id: string, message: string, model?: LlmModel, history?: ChatMessage[] ): Promise { const body: ChatRequest = { message }; if (model) { body.model = model; } if (history && history.length > 0) { body.history = history; } const res = await fetch(`${API_BASE}/api/v1/files/${id}/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Chat failed: ${errorText || res.statusText}`); } return res.json(); } // Version history types export type VersionSource = "user" | "llm" | "system"; export interface FileVersion { version: number; name: string; description: string | null; summary: string | null; body: BodyElement[]; source: VersionSource; createdAt: string; changeDescription?: string; } export interface FileVersionSummary { version: number; source: VersionSource; createdAt: string; changeDescription?: string; } export interface FileVersionListResponse { versions: FileVersionSummary[]; total: number; } export interface RestoreVersionRequest { targetVersion: number; } // Version history API functions export async function listFileVersions(fileId: string): Promise { const res = await fetch(`${API_BASE}/api/v1/files/${fileId}/versions`); if (!res.ok) { throw new Error(`Failed to list versions: ${res.statusText}`); } return res.json(); } export async function getFileVersion(fileId: string, version: number): Promise { const res = await fetch(`${API_BASE}/api/v1/files/${fileId}/versions/${version}`); if (!res.ok) { throw new Error(`Failed to get version: ${res.statusText}`); } return res.json(); } export async function restoreFileVersion( fileId: string, targetVersion: number, currentVersion: number ): Promise { const res = await fetch(`${API_BASE}/api/v1/files/${fileId}/versions/restore`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ targetVersion, currentVersion }), }); if (res.status === 409) { const conflict = (await res.json()) as ConflictErrorResponse; throw new VersionConflictError(conflict); } if (!res.ok) { throw new Error(`Failed to restore version: ${res.statusText}`); } return res.json(); } // ============================================================================= // LLM Tool Definitions for Version History // ============================================================================= // These types define the tools available to the LLM for version history access. // The backend should implement handlers for these tools. /** * Tool: read_version * Allows the LLM to read the content of a specific historical version. * This is read-only - it does not modify the document. */ export interface ReadVersionToolInput { version: number; } export interface ReadVersionToolOutput { success: boolean; version: number; body: BodyElement[]; summary: string | null; source: VersionSource; createdAt: string; changeDescription?: string; message: string; } /** * Tool: list_versions * Allows the LLM to list all available versions of the document. */ export interface ListVersionsToolOutput { success: boolean; versions: FileVersionSummary[]; currentVersion: number; message: string; } /** * Tool: restore_version * Allows the LLM to restore the document to a specific historical version. * This creates a new version with the content from the target version. */ export interface RestoreVersionToolInput { targetVersion: number; reason?: string; } export interface RestoreVersionToolOutput { success: boolean; previousVersion: number; newVersion: number; restoredFromVersion: number; message: string; } // LLM Tool type definitions for the backend export type LlmVersionTool = | { name: "read_version"; input: ReadVersionToolInput } | { name: "list_versions"; input: Record } | { name: "restore_version"; input: RestoreVersionToolInput }; export type LlmVersionToolResult = | { name: "read_version"; result: ReadVersionToolOutput } | { name: "list_versions"; result: ListVersionsToolOutput } | { name: "restore_version"; result: RestoreVersionToolOutput };