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<string, unknown>[];
config?: Record<string, unknown>;
}
| { 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;
};
}
export interface ChatResponse {
response: string;
toolCalls: ToolCallInfo[];
updatedBody: BodyElement[];
updatedSummary: string | null;
}
// File API functions
export async function listFiles(): Promise<FileListResponse> {
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<FileDetail> {
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<FileDetail> {
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<FileDetail> {
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<void> {
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<ChatResponse> {
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<FileVersionListResponse> {
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<FileVersion> {
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<FileDetail> {
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<string, never> }
| { name: "restore_version"; input: RestoreVersionToolInput };
export type LlmVersionToolResult =
| { name: "read_version"; result: ReadVersionToolOutput }
| { name: "list_versions"; result: ListVersionsToolOutput }
| { name: "restore_version"; result: RestoreVersionToolOutput };