Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 5 additions & 38 deletions src/features/ai/aiConfigApi.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,8 @@
import type { AiConfig } from './aiConfigTypes'

const API_BASE = import.meta.env.VITE_API_BASE_URL ?? '/api'

async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = localStorage.getItem('phyverse-token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...((options.headers as Record<string, string>) ?? {}),
}
if (token) {
headers.Authorization = `Bearer ${token}`
}

const response = await fetch(`${API_BASE}${path}`, {
...options,
headers,
})

const data = await response.json().catch(() => ({}))

if (!response.ok) {
throw new Error(data.error ?? `Request failed with status ${response.status}`)
}

return data as T
}
import { API_BASE, apiRequest, buildAuthHeaders } from '@/shared/utils/apiClient'

export async function fetchAiConfig(): Promise<{ config: AiConfig | null }> {
return request<{ config: AiConfig | null }>('/ai-config')
return apiRequest<{ config: AiConfig | null }>('/ai-config')
}

export async function saveAiConfig(payload: {
Expand All @@ -36,14 +11,14 @@ export async function saveAiConfig(payload: {
model: string
apiKey: string
}): Promise<{ config: AiConfig }> {
return request<{ config: AiConfig }>('/ai-config', {
return apiRequest<{ config: AiConfig }>('/ai-config', {
method: 'POST',
body: JSON.stringify(payload),
})
}

export async function deleteAiConfig(): Promise<void> {
await request<void>('/ai-config', {
await apiRequest<void>('/ai-config', {
method: 'DELETE',
})
}
Expand All @@ -55,17 +30,9 @@ export async function sendAiChat(payload: {
temperature?: number
max_tokens?: number
}): Promise<Response> {
const token = localStorage.getItem('phyverse-token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (token) {
headers.Authorization = `Bearer ${token}`
}

return fetch(`${API_BASE}/ai/chat`, {
method: 'POST',
headers,
headers: buildAuthHeaders(),
body: JSON.stringify(payload),
})
}
7 changes: 1 addition & 6 deletions src/features/auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react
import { AuthContext } from './AuthContext'
import type { User } from './authTypes'
import { login as loginApi, register as registerApi, fetchCurrentUser } from './authApi'
import { TOKEN_KEY, getStoredToken } from '@/shared/utils/apiClient'

const TOKEN_KEY = 'phyverse-token'
const USER_KEY = 'phyverse-user'

function getStoredToken(): string | null {
if (typeof window === 'undefined') return null
return window.localStorage.getItem(TOKEN_KEY)
}

function getStoredUser(): User | null {
if (typeof window === 'undefined') return null
try {
Expand Down
33 changes: 4 additions & 29 deletions src/features/auth/authApi.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,20 @@
import type { AuthResponse, RegisterData, AuthCredentials, User } from './authTypes'

const API_BASE = import.meta.env.VITE_API_BASE_URL ?? '/api'

async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = localStorage.getItem('phyverse-token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...((options.headers as Record<string, string>) ?? {}),
}
if (token) {
headers.Authorization = `Bearer ${token}`
}

const response = await fetch(`${API_BASE}${path}`, {
...options,
headers,
})

const data = await response.json().catch(() => ({}))

if (!response.ok) {
throw new Error(data.error ?? `Request failed with status ${response.status}`)
}

return data as T
}
import { apiRequest } from '@/shared/utils/apiClient'

export async function register(data: RegisterData): Promise<AuthResponse> {
return request<AuthResponse>('/auth/register', {
return apiRequest<AuthResponse>('/auth/register', {
method: 'POST',
body: JSON.stringify(data),
})
}

export async function login(credentials: AuthCredentials): Promise<AuthResponse> {
return request<AuthResponse>('/auth/login', {
return apiRequest<AuthResponse>('/auth/login', {
method: 'POST',
body: JSON.stringify(credentials),
})
}

export async function fetchCurrentUser(): Promise<{ user: User }> {
return request<{ user: User }>('/auth/me')
return apiRequest<{ user: User }>('/auth/me')
}
37 changes: 6 additions & 31 deletions src/features/cloud/cloudApi.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,12 @@
import type { SceneMetadata, CloudScene } from '@/features/auth/authTypes'

const API_BASE = import.meta.env.VITE_API_BASE_URL ?? '/api'

async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = localStorage.getItem('phyverse-token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...((options.headers as Record<string, string>) ?? {}),
}
if (token) {
headers.Authorization = `Bearer ${token}`
}

const response = await fetch(`${API_BASE}${path}`, {
...options,
headers,
})

const data = await response.json().catch(() => ({}))

if (!response.ok) {
throw new Error(data.error ?? `Request failed with status ${response.status}`)
}

return data as T
}
import { apiRequest } from '@/shared/utils/apiClient'

export async function listScenes(): Promise<{ scenes: SceneMetadata[] }> {
return request<{ scenes: SceneMetadata[] }>('/scenes')
return apiRequest<{ scenes: SceneMetadata[] }>('/scenes')
}

export async function getScene(id: string): Promise<{ scene: CloudScene }> {
return request<{ scene: CloudScene }>(`/scenes/${id}`)
return apiRequest<{ scene: CloudScene }>(`/scenes/${id}`)
}

export async function saveScene(payload: {
Expand All @@ -40,7 +15,7 @@ export async function saveScene(payload: {
data: unknown
isPublic?: boolean
}): Promise<{ scene: SceneMetadata }> {
return request<{ scene: SceneMetadata }>('/scenes', {
return apiRequest<{ scene: SceneMetadata }>('/scenes', {
method: 'POST',
body: JSON.stringify(payload),
})
Expand All @@ -55,14 +30,14 @@ export async function updateScene(
isPublic?: boolean
}
): Promise<{ scene: SceneMetadata }> {
return request<{ scene: SceneMetadata }>(`/scenes/${id}`, {
return apiRequest<{ scene: SceneMetadata }>(`/scenes/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload),
})
}

export async function deleteScene(id: string): Promise<void> {
await request<void>(`/scenes/${id}`, {
await apiRequest<void>(`/scenes/${id}`, {
method: 'DELETE',
})
}
28 changes: 7 additions & 21 deletions src/features/experiments/mechanics/centripetalForce.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { registerExperiment } from '../registry'
import { createJoint } from '@/features/physics/JointFactory'
import { magnitude, magnitudeXZ, distanceXZ } from '@/shared/utils/vectorMath'
import type { ExperimentDefinition } from '@/shared/types/experiment'

interface CentripetalData {
Expand Down Expand Up @@ -127,8 +128,7 @@ const centripetalForceExperiment: ExperimentDefinition = {
collect: (world) => {
const ball = world.getBody('ball')
if (!ball) return 0
const v = ball.rigidBody.linvel()
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
return magnitude(ball.rigidBody.linvel())
},
},
{
Expand All @@ -139,11 +139,7 @@ const centripetalForceExperiment: ExperimentDefinition = {
const pivot = world.getBody('pivot')
const ball = world.getBody('ball')
if (!pivot || !ball) return 0
const pp = pivot.rigidBody.translation()
const bp = ball.rigidBody.translation()
const dx = bp.x - pp.x
const dz = bp.z - pp.z
return Math.sqrt(dx * dx + dz * dz)
return distanceXZ(ball.rigidBody.translation(), pivot.rigidBody.translation())
},
},
{
Expand All @@ -154,13 +150,8 @@ const centripetalForceExperiment: ExperimentDefinition = {
const pivot = world.getBody('pivot')
const ball = world.getBody('ball')
if (!pivot || !ball) return 0
const pp = pivot.rigidBody.translation()
const bp = ball.rigidBody.translation()
const dx = bp.x - pp.x
const dz = bp.z - pp.z
const r = Math.sqrt(dx * dx + dz * dz)
const v = ball.rigidBody.linvel()
const speed = Math.sqrt(v.x * v.x + v.z * v.z)
const r = distanceXZ(ball.rigidBody.translation(), pivot.rigidBody.translation())
const speed = magnitudeXZ(ball.rigidBody.linvel())
if (r < 0.01) return 0
return speed / r
},
Expand All @@ -174,13 +165,8 @@ const centripetalForceExperiment: ExperimentDefinition = {
const ball = world.getBody('ball')
if (!pivot || !ball) return 0
const data = ball.rigidBody.userData as CentripetalData
const pp = pivot.rigidBody.translation()
const bp = ball.rigidBody.translation()
const dx = bp.x - pp.x
const dz = bp.z - pp.z
const r = Math.sqrt(dx * dx + dz * dz)
const v = ball.rigidBody.linvel()
const speed = Math.sqrt(v.x * v.x + v.z * v.z)
const r = distanceXZ(ball.rigidBody.translation(), pivot.rigidBody.translation())
const speed = magnitudeXZ(ball.rigidBody.linvel())
if (r < 0.01) return 0
return (data.mass * speed * speed) / r
},
Expand Down
14 changes: 5 additions & 9 deletions src/features/experiments/mechanics/curvilinearMotion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { registerExperiment } from '../registry'
import { createJoint } from '@/features/physics/JointFactory'
import { magnitude, magnitudeXZ, distanceXZ } from '@/shared/utils/vectorMath'
import type { ExperimentDefinition } from '@/shared/types/experiment'

const curvilinearMotionExperiment: ExperimentDefinition = {
Expand Down Expand Up @@ -112,8 +113,7 @@ const curvilinearMotionExperiment: ExperimentDefinition = {
collect: (world) => {
const ball = world.getBody('ball')
if (!ball) return 0
const v = ball.rigidBody.linvel()
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
return magnitude(ball.rigidBody.linvel())
},
},
{
Expand All @@ -124,11 +124,7 @@ const curvilinearMotionExperiment: ExperimentDefinition = {
const pivot = world.getBody('pivot')
const ball = world.getBody('ball')
if (!pivot || !ball) return 0
const pp = pivot.rigidBody.translation()
const bp = ball.rigidBody.translation()
const dx = bp.x - pp.x
const dz = bp.z - pp.z
return Math.sqrt(dx * dx + dz * dz)
return distanceXZ(ball.rigidBody.translation(), pivot.rigidBody.translation())
},
},
{
Expand All @@ -145,8 +141,8 @@ const curvilinearMotionExperiment: ExperimentDefinition = {
const rx = bp.x - pp.x
const rz = bp.z - pp.z
const dot = rx * v.x + rz * v.z
const rMag = Math.sqrt(rx * rx + rz * rz)
const vMag = Math.sqrt(v.x * v.x + v.z * v.z)
const rMag = magnitudeXZ({ x: rx, z: rz })
const vMag = magnitudeXZ(v)
if (rMag < 0.01 || vMag < 0.01) return 0
const cosAngle = dot / (rMag * vMag)
const clamped = Math.max(-1, Math.min(1, cosAngle))
Expand Down
10 changes: 4 additions & 6 deletions src/features/experiments/mechanics/energyConservation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { registerExperiment } from '../registry'
import { magnitude } from '@/shared/utils/vectorMath'
import type { ExperimentDefinition } from '@/shared/types/experiment'

interface EnergyData {
Expand Down Expand Up @@ -101,8 +102,7 @@ const energyConservationExperiment: ExperimentDefinition = {
collect: (world) => {
const ball = world.getBody('ball')
if (!ball) return 0
const v = ball.rigidBody.linvel()
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
return magnitude(ball.rigidBody.linvel())
},
},
{
Expand All @@ -113,8 +113,7 @@ const energyConservationExperiment: ExperimentDefinition = {
const ball = world.getBody('ball')
if (!ball) return 0
const data = ball.rigidBody.userData as EnergyData
const v = ball.rigidBody.linvel()
const speed = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
const speed = magnitude(ball.rigidBody.linvel())
return 0.5 * data.mass * speed * speed
},
},
Expand All @@ -138,8 +137,7 @@ const energyConservationExperiment: ExperimentDefinition = {
const ball = world.getBody('ball')
if (!ball) return 0
const data = ball.rigidBody.userData as EnergyData
const v = ball.rigidBody.linvel()
const speed = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
const speed = magnitude(ball.rigidBody.linvel())
const h = ball.rigidBody.translation().y
const ek = 0.5 * data.mass * speed * speed
const ep = data.mass * 9.81 * h
Expand Down
4 changes: 2 additions & 2 deletions src/features/experiments/mechanics/freeFall.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { registerExperiment } from '../registry'
import { magnitude } from '@/shared/utils/vectorMath'
import type { ExperimentDefinition } from '@/shared/types/experiment'

const freeFallExperiment: ExperimentDefinition = {
Expand Down Expand Up @@ -104,8 +105,7 @@ const freeFallExperiment: ExperimentDefinition = {
collect: (world) => {
const ball = world.getBody('ball')
if (!ball) return 0
const v = ball.rigidBody.linvel()
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
return magnitude(ball.rigidBody.linvel())
},
},
],
Expand Down
4 changes: 2 additions & 2 deletions src/features/experiments/mechanics/galileoIncline.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { registerExperiment } from '../registry'
import { magnitude } from '@/shared/utils/vectorMath'
import type { ExperimentDefinition } from '@/shared/types/experiment'

const galileoInclineExperiment: ExperimentDefinition = {
Expand Down Expand Up @@ -151,8 +152,7 @@ const galileoInclineExperiment: ExperimentDefinition = {
collect: (world) => {
const ball = world.getBody('ball')
if (!ball) return 0
const v = ball.rigidBody.linvel()
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
return magnitude(ball.rigidBody.linvel())
},
},
{
Expand Down
4 changes: 2 additions & 2 deletions src/features/experiments/mechanics/motionComposition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { registerExperiment } from '../registry'
import { magnitude } from '@/shared/utils/vectorMath'
import type { ExperimentDefinition } from '@/shared/types/experiment'

interface MotionData {
Expand Down Expand Up @@ -138,8 +139,7 @@ const motionCompositionExperiment: ExperimentDefinition = {
collect: (world) => {
const block = world.getBody('wax-block')
if (!block) return 0
const v = block.rigidBody.linvel()
return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
return magnitude(block.rigidBody.linvel())
},
},
{
Expand Down
Loading