diff --git a/packages/api-client/package.json b/packages/api-client/package.json new file mode 100644 index 0000000..b6465a9 --- /dev/null +++ b/packages/api-client/package.json @@ -0,0 +1,21 @@ +{ + "name": "@infiplot/api-client", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@infiplot/types": "workspace:*" + }, + "devDependencies": { + "typescript": "^6.0.3" + } +} diff --git a/packages/api-client/src/index.ts b/packages/api-client/src/index.ts new file mode 100644 index 0000000..cd7bba9 --- /dev/null +++ b/packages/api-client/src/index.ts @@ -0,0 +1,36 @@ +import type { SceneRequest, SceneResponse } from "@infiplot/types"; + +export interface InfiplotApiClientOptions { + baseUrl: string; + fetcher?: typeof fetch; +} + +export class InfiplotApiClient { + private readonly baseUrl: string; + private readonly fetcher: typeof fetch; + + constructor(options: InfiplotApiClientOptions) { + this.baseUrl = options.baseUrl.replace(/\/$/, ""); + this.fetcher = options.fetcher ?? fetch; + } + + async createScene(request: SceneRequest): Promise { + return this.post("/api/scene", request); + } + + private async post(path: string, body: unknown): Promise { + const response = await this.fetcher(`${this.baseUrl}${path}`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + throw new Error(`Infiplot API request failed: ${response.status}`); + } + + return response.json() as Promise; + } +} diff --git a/packages/api-client/tsconfig.json b/packages/api-client/tsconfig.json new file mode 100644 index 0000000..03e79e5 --- /dev/null +++ b/packages/api-client/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM"], + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..cb01457 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,21 @@ +{ + "name": "@infiplot/core", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@infiplot/types": "workspace:*" + }, + "devDependencies": { + "typescript": "^6.0.3" + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..fc339f8 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,12 @@ +import type { Locale } from "@infiplot/types"; + +const DEFAULT_LOCALE: Locale = "zh-CN"; +const SUPPORTED_LOCALES = new Set(["en", "ja", "zh-CN"]); + +export function normalizeLocale(locale: string | undefined): Locale { + if (locale && SUPPORTED_LOCALES.has(locale as Locale)) { + return locale as Locale; + } + + return DEFAULT_LOCALE; +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..0d7100c --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 0000000..c058fa2 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,18 @@ +{ + "name": "@infiplot/types", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "typescript": "^6.0.3" + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 0000000..5d54b28 --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,33 @@ +export type Locale = "en" | "ja" | "zh-CN"; + +export type ApiResult = + | { ok: true; data: TData } + | { ok: false; error: TError }; + +export interface ApiError { + code: string; + message: string; + details?: unknown; +} + +export interface StorySummary { + id: string; + title: string; + description?: string; + locale: Locale; + coverImageUrl?: string; + updatedAt?: string; +} + +export interface SceneRequest { + storyId?: string; + prompt: string; + locale?: Locale; +} + +export interface SceneResponse { + sceneId: string; + text: string; + imageUrl?: string; + audioUrl?: string; +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 0000000..0d7100c --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..e1b1d1d --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,25 @@ +{ + "name": "@infiplot/ui", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": { + "react-native": "./src/index.native.tsx", + "types": "./src/index.ts", + "default": "./src/index.web.tsx" + } + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "peerDependencies": { + "react": ">=19", + "react-native": ">=0.85" + }, + "devDependencies": { + "@types/react": "~19.2.2", + "react-native": "0.85.3", + "typescript": "^6.0.3" + } +} diff --git a/packages/ui/src/index.native.tsx b/packages/ui/src/index.native.tsx new file mode 100644 index 0000000..5aa5f62 --- /dev/null +++ b/packages/ui/src/index.native.tsx @@ -0,0 +1,2 @@ +export type { SurfaceProps } from "./surface.types"; +export { Surface } from "./surface.native"; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts new file mode 100644 index 0000000..2d67646 --- /dev/null +++ b/packages/ui/src/index.ts @@ -0,0 +1 @@ +export type { SurfaceProps } from "./surface.types"; diff --git a/packages/ui/src/index.web.tsx b/packages/ui/src/index.web.tsx new file mode 100644 index 0000000..76fb500 --- /dev/null +++ b/packages/ui/src/index.web.tsx @@ -0,0 +1,2 @@ +export type { SurfaceProps } from "./surface.types"; +export { Surface } from "./surface.web"; diff --git a/packages/ui/src/surface.native.tsx b/packages/ui/src/surface.native.tsx new file mode 100644 index 0000000..397150b --- /dev/null +++ b/packages/ui/src/surface.native.tsx @@ -0,0 +1,16 @@ +import { View } from "react-native"; +import type { ViewStyle } from "react-native"; +import type { SurfaceProps } from "./surface.types"; + +const styles: Record, ViewStyle> = { + default: { + backgroundColor: "#ffffff" + }, + muted: { + backgroundColor: "#f4f6f8" + } +}; + +export function Surface({ children, tone = "default" }: SurfaceProps) { + return {children}; +} diff --git a/packages/ui/src/surface.types.ts b/packages/ui/src/surface.types.ts new file mode 100644 index 0000000..055a9d3 --- /dev/null +++ b/packages/ui/src/surface.types.ts @@ -0,0 +1,6 @@ +import type { ReactNode } from "react"; + +export interface SurfaceProps { + children: ReactNode; + tone?: "default" | "muted"; +} diff --git a/packages/ui/src/surface.web.tsx b/packages/ui/src/surface.web.tsx new file mode 100644 index 0000000..b33907b --- /dev/null +++ b/packages/ui/src/surface.web.tsx @@ -0,0 +1,17 @@ +import type { CSSProperties } from "react"; +import type { SurfaceProps } from "./surface.types"; + +const styles: Record, CSSProperties> = { + default: { + backgroundColor: "#ffffff", + color: "#111827" + }, + muted: { + backgroundColor: "#f4f6f8", + color: "#111827" + } +}; + +export function Surface({ children, tone = "default" }: SurfaceProps) { + return
{children}
; +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 0000000..63dc7a8 --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +}