- Added a new function to synchronize session metrics with activity log entries, ensuring data consistency. - Updated the create and update activity endpoints to call the synchronization function after inserting or modifying activity logs. - Introduced a set of allowed keys for activity log payloads to streamline data handling in the frontend. - Improved data coercion logic for various data types in the frontend to ensure accurate data submission.
668 lines
32 KiB
JavaScript
668 lines
32 KiB
JavaScript
import { getToken } from '../context/AuthContext'
|
||
|
||
let _profileId = null
|
||
export function setProfileId(id) { _profileId = id }
|
||
|
||
const BASE = '/api'
|
||
|
||
/**
|
||
* FastAPI-Fehler: `detail` kann String, Objekt oder Validierungs-Array sein.
|
||
*/
|
||
export function formatFastApiDetail(detail, fallback = '') {
|
||
if (detail == null || detail === '') {
|
||
return fallback || 'Anfrage fehlgeschlagen'
|
||
}
|
||
if (typeof detail === 'string') {
|
||
return detail
|
||
}
|
||
if (Array.isArray(detail)) {
|
||
const parts = detail.map((e) => {
|
||
if (typeof e === 'string') return e
|
||
if (e && typeof e === 'object') {
|
||
const loc = Array.isArray(e.loc) ? e.loc.filter((x) => x != null && x !== '').join('.') : ''
|
||
const msg = e.msg || e.message || ''
|
||
if (loc && msg) return `${loc}: ${msg}`
|
||
return msg || loc || ''
|
||
}
|
||
return String(e)
|
||
}).filter(Boolean)
|
||
return parts.length ? parts.join(' · ') : fallback || 'Validierungsfehler'
|
||
}
|
||
if (typeof detail === 'object') {
|
||
if (Array.isArray(detail.errors) && detail.errors.length > 0) {
|
||
const parts = detail.errors
|
||
.map((e) => {
|
||
if (e && typeof e === 'object') return e.message || e.msg || ''
|
||
return String(e)
|
||
})
|
||
.filter(Boolean)
|
||
if (parts.length) return parts.join(' · ')
|
||
}
|
||
if (typeof detail.msg === 'string') return detail.msg
|
||
if (typeof detail.message === 'string') return detail.message
|
||
}
|
||
return fallback || 'Anfrage fehlgeschlagen'
|
||
}
|
||
|
||
function hdrs(extra={}) {
|
||
const h = {...extra}
|
||
if (_profileId) h['X-Profile-Id'] = _profileId
|
||
const token = getToken()
|
||
if (token) h['X-Auth-Token'] = token
|
||
return h
|
||
}
|
||
|
||
async function req(path, opts={}) {
|
||
const res = await fetch(BASE+path, {...opts, headers:hdrs(opts.headers||{})})
|
||
if (!res.ok) {
|
||
const errText = await res.text()
|
||
let parsed = null
|
||
try {
|
||
parsed = JSON.parse(errText)
|
||
} catch {
|
||
throw new Error(errText.trim() || `HTTP ${res.status}`)
|
||
}
|
||
throw new Error(formatFastApiDetail(parsed.detail, errText.trim() || `HTTP ${res.status}`))
|
||
}
|
||
return res.json()
|
||
}
|
||
const json=(d)=>({method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(d)})
|
||
const jput=(d)=>({method:'PUT', headers:{'Content-Type':'application/json'},body:JSON.stringify(d)})
|
||
|
||
export const api = {
|
||
// Profiles
|
||
getActiveProfile: () => req('/profile'),
|
||
listProfiles: () => req('/profiles'),
|
||
createProfile: (d) => req('/profiles', json(d)),
|
||
updateProfile: (id,d) => req(`/profiles/${id}`, jput(d)),
|
||
deleteProfile: (id) => req(`/profiles/${id}`, {method:'DELETE'}),
|
||
getProfile: () => req('/profile'),
|
||
updateActiveProfile:(d)=> req('/profile', jput(d)),
|
||
|
||
// App-Bereich: Dashboard-Lab (Layout JSON, Issue #65) + Widget-Katalog
|
||
getAppWidgetsCatalog: () => req('/app/widgets/catalog'),
|
||
getAppDashboardLayout: () => req('/app/dashboard-layout'),
|
||
putAppDashboardLayout: (layout) => req('/app/dashboard-layout', jput(layout)),
|
||
resetAppDashboardLayout: () => req('/app/dashboard-layout/reset', { method: 'POST' }),
|
||
|
||
adminGetWidgetsCatalogFull: () => req('/admin/widgets/catalog-full'),
|
||
adminGetDashboardProductDefault: () => req('/admin/dashboard-product-default'),
|
||
adminPutDashboardProductDefault: (layout) =>
|
||
req('/admin/dashboard-product-default', jput(layout)),
|
||
adminDeleteDashboardProductDefault: () =>
|
||
req('/admin/dashboard-product-default', { method: 'DELETE' }),
|
||
|
||
adminGetWidgetFeatureAssignments: () => req('/admin/widget-feature-assignments'),
|
||
adminPutWidgetFeatureAssignment: (widgetId, body) =>
|
||
req(`/admin/widget-feature-assignments/${encodeURIComponent(widgetId)}`, jput(body)),
|
||
|
||
// Persönliche Referenzwerte (Profil, historisch)
|
||
listReferenceValueTypes: () => req('/reference-value-types'),
|
||
listReferenceValueMetaEnums: () => req('/reference-value-meta/enums'),
|
||
listProfileReferenceValues: (typeKey) =>
|
||
req(`/profile-reference-values?type_key=${encodeURIComponent(typeKey)}`),
|
||
listProfileReferenceValuesSummary: () => req('/profile-reference-values/summary'),
|
||
createProfileReferenceValue: (d) => req('/profile-reference-values', json(d)),
|
||
updateProfileReferenceValue: (id, d) => req(`/profile-reference-values/${id}`, jput(d)),
|
||
deleteProfileReferenceValue: (id) => req(`/profile-reference-values/${id}`, { method: 'DELETE' }),
|
||
|
||
// Admin: Referenzwert-Typen (Katalog)
|
||
adminListReferenceValueTypes: () => req('/admin/reference-value-types'),
|
||
adminCreateReferenceValueType: (d) => req('/admin/reference-value-types', json(d)),
|
||
adminUpdateReferenceValueType: (id, d) => req(`/admin/reference-value-types/${id}`, jput(d)),
|
||
adminDeleteReferenceValueType: (id) => req(`/admin/reference-value-types/${id}`, { method: 'DELETE' }),
|
||
adminReorderReferenceValueTypes: (orderedIds) =>
|
||
req('/admin/reference-value-types/reorder', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ordered_ids: orderedIds }),
|
||
}),
|
||
|
||
// Weight
|
||
listWeight: (l=365) => req(`/weight?limit=${l}`),
|
||
upsertWeight: (date,weight,note='') => req('/weight',json({date,weight,note})),
|
||
updateWeight: (id,date,weight,note='') => req(`/weight/${id}`,jput({date,weight,note})),
|
||
deleteWeight: (id) => req(`/weight/${id}`,{method:'DELETE'}),
|
||
weightStats: () => req('/weight/stats'),
|
||
|
||
// Circumferences
|
||
listCirc: (l=100) => req(`/circumferences?limit=${l}`),
|
||
upsertCirc: (d) => req('/circumferences',json(d)),
|
||
updateCirc: (id,d) => req(`/circumferences/${id}`,jput(d)),
|
||
deleteCirc: (id) => req(`/circumferences/${id}`,{method:'DELETE'}),
|
||
|
||
// Caliper
|
||
listCaliper: (l=100) => req(`/caliper?limit=${l}`),
|
||
upsertCaliper: (d) => req('/caliper',json(d)),
|
||
updateCaliper: (id,d) => req(`/caliper/${id}`,jput(d)),
|
||
deleteCaliper: (id) => req(`/caliper/${id}`,{method:'DELETE'}),
|
||
|
||
// Activity
|
||
/** @param {number} [limit=200] @param {number} [days] nur Einträge ab HEUTE−days (Kalendertage), backend-filtert */
|
||
listActivity: (limit=200, days)=> {
|
||
const q = new URLSearchParams({ limit: String(limit) })
|
||
if (days != null && days !== '') q.set('days', String(days))
|
||
return req(`/activity?${q}`)
|
||
},
|
||
createActivity: (d) => req('/activity',json(d)),
|
||
updateActivity: (id,d) => req(`/activity/${id}`,jput(d)),
|
||
deleteActivity: (id) => req(`/activity/${id}`,{method:'DELETE'}),
|
||
activityStats: () => req('/activity/stats'),
|
||
listUncategorizedActivities: () => req('/activity/uncategorized'),
|
||
bulkCategorizeActivities: (d) => req('/activity/bulk-categorize', json(d)),
|
||
importActivityCsv: async(file)=>{
|
||
const fd=new FormData();fd.append('file',file)
|
||
const r=await fetch(`${BASE}/activity/import-csv`,{method:'POST',body:fd,headers:hdrs()})
|
||
const d=await r.json();if(!r.ok)throw new Error(formatFastApiDetail(d.detail, JSON.stringify(d)));return d
|
||
},
|
||
|
||
// Photos
|
||
uploadPhoto: (file,date='')=>{
|
||
const fd=new FormData();fd.append('file',file);fd.append('date',date)
|
||
return fetch(`${BASE}/photos`,{method:'POST',body:fd,headers:hdrs()}).then(r=>r.json())
|
||
},
|
||
listPhotos: () => req('/photos'),
|
||
photoUrl: (pid) => {
|
||
const token = getToken()
|
||
return `${BASE}/photos/${pid}${token ? `?token=${token}` : ''}`
|
||
},
|
||
|
||
// Nutrition
|
||
importCsv: async(file)=>{
|
||
const fd=new FormData();fd.append('file',file)
|
||
const r=await fetch(`${BASE}/nutrition/import-csv`,{method:'POST',body:fd,headers:hdrs()})
|
||
const d=await r.json();if(!r.ok)throw new Error(formatFastApiDetail(d.detail, JSON.stringify(d)));return d
|
||
},
|
||
listNutrition: (l=365) => req(`/nutrition?limit=${l}`),
|
||
nutritionCorrelations: () => req('/nutrition/correlations'),
|
||
nutritionWeekly: (w=16) => req(`/nutrition/weekly?weeks=${w}`),
|
||
nutritionImportHistory: () => req('/nutrition/import-history'),
|
||
getNutritionByDate: (date) => req(`/nutrition/by-date/${date}`),
|
||
createNutrition: (date,kcal,protein,fat,carbs) => req(`/nutrition?date=${date}&kcal=${kcal}&protein_g=${protein}&fat_g=${fat}&carbs_g=${carbs}`,{method:'POST'}),
|
||
updateNutrition: (id,kcal,protein,fat,carbs) => req(`/nutrition/${id}?kcal=${kcal}&protein_g=${protein}&fat_g=${fat}&carbs_g=${carbs}`,{method:'PUT'}),
|
||
deleteNutrition: (id) => req(`/nutrition/${id}`,{method:'DELETE'}),
|
||
|
||
// Stats & AI
|
||
getStats: () => req('/stats'),
|
||
insightTrend: () => req('/insights/trend',{method:'POST'}),
|
||
listPrompts: () => req('/prompts'),
|
||
runInsight: (slug) => req(`/insights/run/${slug}`,{method:'POST'}),
|
||
insightPipeline: () => req('/insights/pipeline',{method:'POST'}),
|
||
listInsights: () => req('/insights'),
|
||
latestInsights: () => req('/insights/latest'),
|
||
deleteInsight: (id) => req(`/insights/${id}`, {method:'DELETE'}),
|
||
exportZip: async () => {
|
||
const res = await fetch(`${BASE}/export/zip`, {headers: hdrs()})
|
||
if (!res.ok) throw new Error('Export failed')
|
||
const blob = await res.blob()
|
||
const url = window.URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `mitai-export-${new Date().toISOString().split('T')[0]}.zip`
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
window.URL.revokeObjectURL(url)
|
||
},
|
||
exportJson: async () => {
|
||
const res = await fetch(`${BASE}/export/json`, {headers: hdrs()})
|
||
if (!res.ok) throw new Error('Export failed')
|
||
const blob = await res.blob()
|
||
const url = window.URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `mitai-export-${new Date().toISOString().split('T')[0]}.json`
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
window.URL.revokeObjectURL(url)
|
||
},
|
||
exportCsv: async () => {
|
||
const res = await fetch(`${BASE}/export/csv`, {headers: hdrs()})
|
||
if (!res.ok) throw new Error('Export failed')
|
||
const blob = await res.blob()
|
||
const url = window.URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `mitai-export-${new Date().toISOString().split('T')[0]}.csv`
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
document.body.removeChild(a)
|
||
window.URL.revokeObjectURL(url)
|
||
},
|
||
|
||
// Admin
|
||
adminListProfiles: () => req('/admin/profiles'),
|
||
adminCreateProfile: (d) => req('/admin/profiles',json(d)),
|
||
adminDeleteProfile: (id) => req(`/admin/profiles/${id}`,{method:'DELETE'}),
|
||
adminSetPermissions: (id,d) => req(`/admin/profiles/${id}/permissions`,jput(d)),
|
||
changePin: (pin) => req('/auth/pin',json({pin})),
|
||
register: (name,email,password) => req('/auth/register',json({name,email,password})),
|
||
verifyEmail: (token) => req(`/auth/verify/${token}`),
|
||
resendVerification: (email) => req('/auth/resend-verification',json({email})),
|
||
|
||
// v9c Subscription System
|
||
// User-facing
|
||
getMySubscription: () => req('/subscription/me'),
|
||
getMyUsage: () => req('/subscription/usage'),
|
||
getMyLimits: () => req('/subscription/limits'),
|
||
redeemCoupon: (code) => req('/coupons/redeem',json({code})),
|
||
getFeatureUsage: () => req('/features/usage'), // Phase 3: Usage overview
|
||
|
||
// Admin: Features
|
||
listFeatures: () => req('/features'),
|
||
createFeature: (d) => req('/features',json(d)),
|
||
updateFeature: (id,d) => req(`/features/${id}`,jput(d)),
|
||
deleteFeature: (id) => req(`/features/${id}`,{method:'DELETE'}),
|
||
|
||
// Admin: Tiers
|
||
listTiers: () => req('/tiers'),
|
||
createTier: (d) => req('/tiers',json(d)),
|
||
updateTier: (id,d) => req(`/tiers/${id}`,jput(d)),
|
||
deleteTier: (id) => req(`/tiers/${id}`,{method:'DELETE'}),
|
||
|
||
// Admin: Tier Limits (Matrix)
|
||
getTierLimitsMatrix: () => req('/tier-limits'),
|
||
updateTierLimit: (d) => req('/tier-limits',jput(d)),
|
||
updateTierLimitsBatch:(updates) => req('/tier-limits/batch',jput({updates})),
|
||
|
||
// Admin: User Restrictions
|
||
listUserRestrictions: (pid) => req(`/user-restrictions${pid?'?profile_id='+pid:''}`),
|
||
createUserRestriction:(d) => req('/user-restrictions',json(d)),
|
||
updateUserRestriction:(id,d) => req(`/user-restrictions/${id}`,jput(d)),
|
||
deleteUserRestriction:(id) => req(`/user-restrictions/${id}`,{method:'DELETE'}),
|
||
|
||
// Admin: Coupons
|
||
listCoupons: () => req('/coupons'),
|
||
createCoupon: (d) => req('/coupons',json(d)),
|
||
updateCoupon: (id,d) => req(`/coupons/${id}`,jput(d)),
|
||
deleteCoupon: (id) => req(`/coupons/${id}`,{method:'DELETE'}),
|
||
getCouponRedemptions: (id) => req(`/coupons/${id}/redemptions`),
|
||
|
||
// Admin: Access Grants
|
||
listAccessGrants: (pid,active)=> req(`/access-grants${pid?'?profile_id='+pid:''}${active?'&active_only=true':''}`),
|
||
createAccessGrant: (d) => req('/access-grants',json(d)),
|
||
updateAccessGrant: (id,d) => req(`/access-grants/${id}`,jput(d)),
|
||
revokeAccessGrant: (id) => req(`/access-grants/${id}`,{method:'DELETE'}),
|
||
|
||
// v9d: Training Types
|
||
listTrainingTypes: () => req('/training-types'), // Grouped by category
|
||
listTrainingTypesFlat:() => req('/training-types/flat'), // Flat list
|
||
getTrainingCategories:() => req('/training-types/categories'), // Category metadata
|
||
|
||
// Admin: Training Types (v9d Phase 1b)
|
||
adminListTrainingTypes: () => req('/admin/training-types'),
|
||
adminGetTrainingType: (id) => req(`/admin/training-types/${id}`),
|
||
adminCreateTrainingType: (d) => req('/admin/training-types', json(d)),
|
||
adminUpdateTrainingType: (id,d) => req(`/admin/training-types/${id}`, jput(d)),
|
||
adminDeleteTrainingType: (id) => req(`/admin/training-types/${id}`, {method:'DELETE'}),
|
||
getAbilitiesTaxonomy: () => req('/admin/training-types/taxonomy/abilities'),
|
||
|
||
// Admin: Training Type Profiles (Phase 2 #15)
|
||
getProfileStats: () => req('/admin/training-types/profiles/stats'),
|
||
getProfileTemplates: () => req('/admin/training-types/profiles/templates'),
|
||
getProfileTemplate: (key) => req(`/admin/training-types/profiles/templates/${key}`),
|
||
applyProfileTemplate: (id,templateKey) => req(`/admin/training-types/${id}/profile/apply-template`, json({template_key: templateKey})),
|
||
getTrainingParameters: () => req('/evaluation/parameters'),
|
||
batchEvaluateActivities: () => req('/evaluation/batch', {method:'POST'}),
|
||
|
||
// Admin: Activity Type Mappings (v9d Phase 1b - Learnable System)
|
||
adminListActivityMappings: (profileId, globalOnly) => req(`/admin/activity-mappings${profileId?'?profile_id='+profileId:''}${globalOnly?'?global_only=true':''}`),
|
||
adminGetActivityMapping: (id) => req(`/admin/activity-mappings/${id}`),
|
||
adminCreateActivityMapping: (d) => req('/admin/activity-mappings', json(d)),
|
||
adminUpdateActivityMapping: (id,d) => req(`/admin/activity-mappings/${id}`, jput(d)),
|
||
adminDeleteActivityMapping: (id) => req(`/admin/activity-mappings/${id}`, {method:'DELETE'}),
|
||
adminGetMappingCoverage: () => req('/admin/activity-mappings/stats/coverage'),
|
||
|
||
// Admin: Training session metrics (EAV) & attribute profiles (Migration 054)
|
||
adminListTrainingParameters: (includeInactive = false) =>
|
||
req(`/admin/training-parameters${includeInactive ? '?include_inactive=true' : ''}`),
|
||
adminCreateTrainingParameter: (d) => req('/admin/training-parameters', json(d)),
|
||
adminUpdateTrainingParameter: (id, d) => req(`/admin/training-parameters/${id}`, jput(d)),
|
||
adminDeleteTrainingParameter: (id) =>
|
||
req(`/admin/training-parameters/${id}`, { method: 'DELETE' }),
|
||
adminListTrainingCategoryParameters: (category = '') =>
|
||
req(
|
||
`/admin/training-category-parameters${category ? `?category=${encodeURIComponent(category)}` : ''}`,
|
||
),
|
||
adminAddTrainingCategoryParameter: (d) => req('/admin/training-category-parameters', json(d)),
|
||
adminUpdateTrainingCategoryParameter: (id, d) =>
|
||
req(`/admin/training-category-parameters/${id}`, jput(d)),
|
||
adminDeleteTrainingCategoryParameter: (id) =>
|
||
req(`/admin/training-category-parameters/${id}`, { method: 'DELETE' }),
|
||
adminListTrainingTypeParameters: (trainingTypeId) =>
|
||
req(`/admin/training-type-parameters?training_type_id=${encodeURIComponent(trainingTypeId)}`),
|
||
adminAddTrainingTypeParameter: (d) => req('/admin/training-type-parameters', json(d)),
|
||
adminUpdateTrainingTypeParameter: (id, d) =>
|
||
req(`/admin/training-type-parameters/${id}`, jput(d)),
|
||
adminDeleteTrainingTypeParameter: (id) =>
|
||
req(`/admin/training-type-parameters/${id}`, { method: 'DELETE' }),
|
||
|
||
getActivitySession: (id) => req(`/activity/${encodeURIComponent(id)}`),
|
||
putActivityMetrics: (id, body) =>
|
||
req(`/activity/${encodeURIComponent(id)}/metrics`, jput(body)),
|
||
|
||
// Sleep Module (v9d Phase 2b)
|
||
listSleep: (l=90) => req(`/sleep?limit=${l}`),
|
||
getSleepByDate: (date) => req(`/sleep/by-date/${date}`),
|
||
createSleep: (d) => req('/sleep', json(d)),
|
||
updateSleep: (id,d) => req(`/sleep/${id}`, jput(d)),
|
||
deleteSleep: (id) => req(`/sleep/${id}`, {method:'DELETE'}),
|
||
getSleepStats: (days=7) => req(`/sleep/stats?days=${days}`),
|
||
getSleepDebt: (days=14) => req(`/sleep/debt?days=${days}`),
|
||
getSleepTrend: (days=30) => req(`/sleep/trend?days=${days}`),
|
||
getSleepPhases: (days=30) => req(`/sleep/phases?days=${days}`),
|
||
|
||
// Sleep Import (v9d Phase 2c)
|
||
importAppleHealthSleep: (file) => {
|
||
const fd = new FormData()
|
||
fd.append('file', file)
|
||
return req('/sleep/import/apple-health', {method:'POST', body:fd})
|
||
},
|
||
|
||
// Rest Days (v9d Phase 2a)
|
||
listRestDays: (l=90) => req(`/rest-days?limit=${l}`),
|
||
createRestDay: (d) => req('/rest-days', json(d)),
|
||
getRestDay: (id) => req(`/rest-days/${id}`),
|
||
updateRestDay: (id,d) => req(`/rest-days/${id}`, jput(d)),
|
||
deleteRestDay: (id) => req(`/rest-days/${id}`, {method:'DELETE'}),
|
||
getRestDaysStats: (weeks=4) => req(`/rest-days/stats?weeks=${weeks}`),
|
||
validateActivity: (date, activityType) => req('/rest-days/validate-activity', json({date, activity_type: activityType})),
|
||
|
||
// Vitals Baseline (v9d Phase 2d Refactored - once daily, morning)
|
||
listBaseline: (l=90) => req(`/vitals/baseline?limit=${l}`),
|
||
getBaselineByDate: (date) => req(`/vitals/baseline/by-date/${date}`),
|
||
createBaseline: (d) => req('/vitals/baseline', json(d)),
|
||
updateBaseline: (id,d) => req(`/vitals/baseline/${id}`, jput(d)),
|
||
deleteBaseline: (id) => req(`/vitals/baseline/${id}`, {method:'DELETE'}),
|
||
getBaselineStats: (days=30) => req(`/vitals/baseline/stats?days=${days}`),
|
||
importBaselineAppleHealth: (file) => {
|
||
const fd = new FormData()
|
||
fd.append('file', file)
|
||
return req('/vitals/baseline/import/apple-health', {method:'POST', body:fd})
|
||
},
|
||
|
||
// Blood Pressure (v9d Phase 2d Refactored - multiple daily, context-aware)
|
||
listBloodPressure: (l=90) => req(`/blood-pressure?limit=${l}`),
|
||
getBPByDate: (date) => req(`/blood-pressure/by-date/${date}`),
|
||
createBloodPressure:(d) => req('/blood-pressure', json(d)),
|
||
updateBloodPressure:(id,d) => req(`/blood-pressure/${id}`, jput(d)),
|
||
deleteBloodPressure:(id) => req(`/blood-pressure/${id}`, {method:'DELETE'}),
|
||
getBPStats: (days=30) => req(`/blood-pressure/stats?days=${days}`),
|
||
importBPOmron: (file) => {
|
||
const fd = new FormData()
|
||
fd.append('file', file)
|
||
return req('/blood-pressure/import/omron', {method:'POST', body:fd})
|
||
},
|
||
|
||
// AI Prompts Management (Issue #28)
|
||
listAdminPrompts: () => req('/prompts'),
|
||
getPrompt: (id) => req(`/prompts/${id}`),
|
||
createPrompt: (d) => req('/prompts', json(d)),
|
||
updatePrompt: (id,d) => req(`/prompts/${id}`, jput(d)),
|
||
deletePrompt: (id) => req(`/prompts/${id}`, {method:'DELETE'}),
|
||
duplicatePrompt: (id) => req(`/prompts/${id}/duplicate`, json({})),
|
||
reorderPrompts: (order) => req('/prompts/reorder', jput(order)),
|
||
previewPrompt: (tpl) => req('/prompts/preview', json({template:tpl})),
|
||
generatePrompt: (d) => req('/prompts/generate', json(d)),
|
||
optimizePrompt: (id) => req(`/prompts/${id}/optimize`, json({})),
|
||
listPlaceholders: () => req('/prompts/placeholders'),
|
||
resetPromptToDefault: (id) => req(`/prompts/${id}/reset-to-default`, json({})),
|
||
|
||
// Pipeline Configs Management (Issue #28 Phase 2)
|
||
listPipelineConfigs: () => req('/prompts/pipeline-configs'),
|
||
createPipelineConfig: (d) => req('/prompts/pipeline-configs', json(d)),
|
||
updatePipelineConfig: (id,d) => req(`/prompts/pipeline-configs/${id}`, jput(d)),
|
||
deletePipelineConfig: (id) => req(`/prompts/pipeline-configs/${id}`, {method:'DELETE'}),
|
||
setDefaultPipelineConfig: (id) => req(`/prompts/pipeline-configs/${id}/set-default`, json({})),
|
||
|
||
// Pipeline Execution (Issue #28 Phase 2)
|
||
executePipeline: (configId=null) => req('/insights/pipeline' + (configId ? `?config_id=${configId}` : ''), json({})),
|
||
|
||
// Unified Prompt System (Issue #28 Phase 2)
|
||
executeUnifiedPrompt: (slug, modules=null, timeframes=null, debug=false, save=false) => {
|
||
const params = new URLSearchParams({ prompt_slug: slug })
|
||
if (debug) params.append('debug', 'true')
|
||
if (save) params.append('save', 'true')
|
||
const body = {}
|
||
if (modules) body.modules = modules
|
||
if (timeframes) body.timeframes = timeframes
|
||
return req('/prompts/execute?' + params, json(body))
|
||
},
|
||
|
||
// NEW: SSE-based execution for long-running workflows
|
||
executeUnifiedPromptStream: (slug, modules=null, timeframes=null, debug=false, save=false, onProgress=null) => {
|
||
const params = new URLSearchParams({ prompt_slug: slug })
|
||
if (debug) params.append('debug', 'true')
|
||
if (save) params.append('save', 'true')
|
||
|
||
// TODO: Security improvement - use session cookie instead of token in URL
|
||
// For now, send token as query param since EventSource doesn't support custom headers
|
||
const token = localStorage.getItem('token')
|
||
if (token) params.append('token', token)
|
||
|
||
if (modules) {
|
||
Object.entries(modules).forEach(([k, v]) => params.append(`modules[${k}]`, v))
|
||
}
|
||
if (timeframes) {
|
||
Object.entries(timeframes).forEach(([k, v]) => params.append(`timeframes[${k}]`, v))
|
||
}
|
||
|
||
// Return a Promise that resolves with final result
|
||
return new Promise((resolve, reject) => {
|
||
const url = `${BASE_URL}/prompts/execute-stream?${params}`
|
||
|
||
const eventSource = new EventSource(url)
|
||
|
||
let finalResult = null
|
||
|
||
eventSource.onmessage = (event) => {
|
||
try {
|
||
const data = JSON.parse(event.data)
|
||
|
||
// Call progress callback if provided
|
||
if (onProgress) {
|
||
onProgress(data)
|
||
}
|
||
|
||
// Check for final result
|
||
if (data.type === 'execution_complete') {
|
||
// Transform SSE result to match regular execute format
|
||
finalResult = {
|
||
type: 'workflow',
|
||
execution_id: data.execution_id,
|
||
status: data.status,
|
||
aggregated_result: data.aggregated_result,
|
||
debug: {
|
||
node_states: [] // TODO: collect from progress events if needed
|
||
}
|
||
}
|
||
eventSource.close()
|
||
resolve(finalResult)
|
||
} else if (data.type === 'execution_failed') {
|
||
eventSource.close()
|
||
reject(new Error(data.error || 'Workflow execution failed'))
|
||
}
|
||
} catch (e) {
|
||
console.error('Error parsing SSE event:', e)
|
||
}
|
||
}
|
||
|
||
eventSource.onerror = (error) => {
|
||
console.error('SSE connection error:', error)
|
||
eventSource.close()
|
||
reject(new Error('Connection to server lost'))
|
||
}
|
||
})
|
||
},
|
||
|
||
// Workflow Execution (Part 2: Frontend Execute Integration)
|
||
executeWorkflow: (slug, variables=null, debug=true, save=false) => {
|
||
const params = new URLSearchParams({ prompt_slug: slug })
|
||
if (debug) params.append('debug', 'true')
|
||
if (save) params.append('save', 'true')
|
||
const body = variables ? { variables } : {}
|
||
return req('/prompts/execute?' + params, json(body))
|
||
},
|
||
|
||
createUnifiedPrompt: (d) => req('/prompts/unified', json(d)),
|
||
updateUnifiedPrompt: (id,d) => req(`/prompts/unified/${id}`, jput(d)),
|
||
|
||
// Batch Import/Export
|
||
exportAllPrompts: () => req('/prompts/export-all'),
|
||
importPrompts: (data, overwrite=false) => {
|
||
const params = new URLSearchParams()
|
||
if (overwrite) params.append('overwrite', 'true')
|
||
return req(`/prompts/import?${params}`, json(data))
|
||
},
|
||
|
||
// Placeholder Export
|
||
exportPlaceholderValues: () => req('/prompts/placeholders/export-values'),
|
||
|
||
// v9e: Goals System (Strategic + Tactical)
|
||
getGoalMode: () => req('/goals/mode'),
|
||
updateGoalMode: (mode) => req('/goals/mode', jput({goal_mode: mode})),
|
||
|
||
// Focus Areas (v2.0)
|
||
getFocusAreas: () => req('/goals/focus-areas'),
|
||
updateFocusAreas: (d) => req('/goals/focus-areas', jput(d)),
|
||
|
||
listGoals: () => req('/goals/list'),
|
||
listGoalsGrouped: () => req('/goals/grouped'),
|
||
createGoal: (d) => req('/goals/create', json(d)),
|
||
updateGoal: (id,d) => req(`/goals/${id}`, jput(d)),
|
||
deleteGoal: (id) => req(`/goals/${id}`, {method:'DELETE'}),
|
||
|
||
// Goal Progress (v2.1)
|
||
listGoalProgress: (id) => req(`/goals/${id}/progress`),
|
||
createGoalProgress: (id,d) => req(`/goals/${id}/progress`, json(d)),
|
||
deleteGoalProgress: (gid,pid) => req(`/goals/${gid}/progress/${pid}`, {method:'DELETE'}),
|
||
|
||
// Goal Type Definitions (Phase 1.5)
|
||
listGoalTypeDefinitions: () => req('/goals/goal-types'),
|
||
createGoalType: (d) => req('/goals/goal-types', json(d)),
|
||
updateGoalType: (id,d) => req(`/goals/goal-types/${id}`, jput(d)),
|
||
deleteGoalType: (id) => req(`/goals/goal-types/${id}`, {method:'DELETE'}),
|
||
getSchemaInfo: () => req('/goals/schema-info'),
|
||
|
||
// Training Phases
|
||
listTrainingPhases: () => req('/goals/phases'),
|
||
createTrainingPhase: (d) => req('/goals/phases', json(d)),
|
||
updatePhaseStatus: (id,status) => req(`/goals/phases/${id}/status?status=${status}`, jput({})),
|
||
|
||
// Fitness Tests
|
||
listFitnessTests: () => req('/goals/tests'),
|
||
createFitnessTest: (d) => req('/goals/tests', json(d)),
|
||
|
||
// Focus Areas (v2.0)
|
||
listFocusAreaDefinitions: (includeInactive=false) => req(`/focus-areas/definitions?include_inactive=${includeInactive}`),
|
||
createFocusAreaDefinition: (d) => req('/focus-areas/definitions', json(d)),
|
||
updateFocusAreaDefinition: (id,d) => req(`/focus-areas/definitions/${id}`, jput(d)),
|
||
deleteFocusAreaDefinition: (id) => req(`/focus-areas/definitions/${id}`, {method:'DELETE'}),
|
||
getUserFocusPreferences: () => req('/focus-areas/user-preferences'),
|
||
updateUserFocusPreferences: (d) => req('/focus-areas/user-preferences', jput(d)),
|
||
getFocusAreaStats: () => req('/focus-areas/stats'),
|
||
listFocusAreaUsageTypes: () => req('/focus-areas/usage-types'),
|
||
setFocusAreaUsageTypes: (id, usageTypeKeys) =>
|
||
req(`/focus-areas/definitions/${id}/usage-types`, jput({ usage_type_keys: usageTypeKeys })),
|
||
|
||
// Chart Endpoints (Phase 0c - Phase 1: Nutrition + Recovery)
|
||
// Nutrition Charts (E1-E5)
|
||
getEnergyBalanceChart: (days=28) => req(`/charts/energy-balance?days=${days}`),
|
||
getProteinAdequacyChart: (days=28) => req(`/charts/protein-adequacy?days=${days}`),
|
||
getNutritionConsistencyChart: (days=28) => req(`/charts/nutrition-consistency?days=${days}`),
|
||
getWeeklyMacroDistributionChart: (weeks=12) => req(`/charts/weekly-macro-distribution?weeks=${weeks}`),
|
||
getNutritionAdherenceScore: (days=28) => req(`/charts/nutrition-adherence-score?days=${days}`),
|
||
getEnergyAvailabilityWarning: (days=14) => req(`/charts/energy-availability-warning?days=${days}`),
|
||
|
||
// Recovery Charts (R1-R5)
|
||
getRecoveryScoreChart: (days=28) => req(`/charts/recovery-score?days=${days}`),
|
||
getHrvRhrBaselineChart: (days=28) => req(`/charts/hrv-rhr-baseline?days=${days}`),
|
||
getSleepDurationQualityChart: (days=28) => req(`/charts/sleep-duration-quality?days=${days}`),
|
||
getSleepDebtChart: (days=28) => req(`/charts/sleep-debt?days=${days}`),
|
||
getVitalSignsMatrixChart: (days=7) => req(`/charts/vital-signs-matrix?days=${days}`),
|
||
|
||
// Placeholder Metadata Export (v1.0)
|
||
exportPlaceholdersExtendedJson: () => req('/prompts/placeholders/export-values-extended'),
|
||
|
||
// Universal CSV Import (Issue #21)
|
||
getCsvModules: () => req('/csv/modules'),
|
||
getCsvLimits: () => req('/csv/limits'),
|
||
getCsvMappings: (module = null) =>
|
||
req(module ? `/csv/mappings?module=${encodeURIComponent(module)}` : '/csv/mappings'),
|
||
copyCsvMapping: (mappingId, body = null) =>
|
||
req(`/csv/mappings/${mappingId}/copy`, body ? json(body) : { method: 'POST' }),
|
||
/** Universal-CSV (Issue #21): Zielmodul steckt in der Vorlage; nur file + mapping_id */
|
||
/** Import-Diagnose: keine Datenbank-Schreibung, erste Zeilen + Mapping-Auflösung */
|
||
diagnoseUniversalCsv: async (file, mappingId, module = null) => {
|
||
const fd = new FormData()
|
||
fd.append('file', file)
|
||
fd.append('mapping_id', String(mappingId))
|
||
if (module) fd.append('module', module)
|
||
const res = await fetch(BASE + '/csv/import-diagnose', { method: 'POST', headers: hdrs(), body: fd })
|
||
if (!res.ok) {
|
||
const errText = await res.text()
|
||
let parsed = null
|
||
try {
|
||
parsed = JSON.parse(errText)
|
||
} catch { /* ignore */ }
|
||
throw new Error(formatFastApiDetail(parsed?.detail, errText.trim() || `HTTP ${res.status}`))
|
||
}
|
||
return res.json()
|
||
},
|
||
importUniversalCsv: async (file, mappingId) => {
|
||
const fd = new FormData()
|
||
fd.append('file', file)
|
||
fd.append('mapping_id', String(mappingId))
|
||
const res = await fetch(BASE + '/csv/import', { method: 'POST', headers: hdrs(), body: fd })
|
||
if (!res.ok) {
|
||
const errText = await res.text()
|
||
let parsed = null
|
||
try {
|
||
parsed = JSON.parse(errText)
|
||
} catch { /* ignore */ }
|
||
throw new Error(formatFastApiDetail(parsed?.detail, errText.trim() || `HTTP ${res.status}`))
|
||
}
|
||
return res.json()
|
||
},
|
||
/** optional module = nur diese Vorlagen-Gruppe (Abwärtskompatibilität); ohne = globale Erkennung */
|
||
analyzeCsv: async (file, module = null, delimiter = null) => {
|
||
const fd = new FormData()
|
||
fd.append('file', file)
|
||
if (module) fd.append('module', module)
|
||
if (delimiter) fd.append('delimiter', delimiter)
|
||
const res = await fetch(BASE + '/csv/analyze', { method: 'POST', headers: hdrs(), body: fd })
|
||
if (!res.ok) {
|
||
const errText = await res.text()
|
||
let parsed = null
|
||
try {
|
||
parsed = JSON.parse(errText)
|
||
} catch { /* ignore */ }
|
||
throw new Error(formatFastApiDetail(parsed?.detail, errText.trim() || `HTTP ${res.status}`))
|
||
}
|
||
return res.json()
|
||
},
|
||
adminListCsvTemplates: (module = null) =>
|
||
req(module ? `/admin/csv-templates?module=${encodeURIComponent(module)}` : '/admin/csv-templates'),
|
||
/** CSV für Admin-Editor analysieren (Vorschlags-Mappings, wie Nutzer-Import) */
|
||
adminAnalyzeCsvTemplate: async (file, module, delimiter = null, seedTemplateId = null) => {
|
||
const fd = new FormData()
|
||
fd.append('file', file)
|
||
fd.append('module', module)
|
||
if (delimiter) fd.append('delimiter', delimiter)
|
||
if (seedTemplateId != null && seedTemplateId !== '') fd.append('seed_template_id', String(seedTemplateId))
|
||
const res = await fetch(BASE + '/admin/csv-templates/analyze-upload', { method: 'POST', headers: hdrs(), body: fd })
|
||
const j = await res.json().catch(() => ({}))
|
||
if (!res.ok) throw new Error(j.detail || res.statusText || 'Analyse fehlgeschlagen')
|
||
return j
|
||
},
|
||
adminGetCsvTemplate: (id) => req(`/admin/csv-templates/${id}`),
|
||
/** Formatprüfung (ohne Speichern); body wie Create, import_row_processing optional */
|
||
adminValidateCsvTemplate: (d) => req('/admin/csv-templates/validate', json(d)),
|
||
adminCreateCsvTemplate: (d) => req('/admin/csv-templates', json(d)),
|
||
adminUpdateCsvTemplate: (id, d) => req(`/admin/csv-templates/${id}`, jput(d)),
|
||
adminDeleteCsvTemplate: (id) => req(`/admin/csv-templates/${id}`, { method: 'DELETE' }),
|
||
adminGetCsvImportLimits: () => req('/admin/csv-templates/import-limits'),
|
||
adminPutCsvImportLimits: (d) => req('/admin/csv-templates/import-limits', jput(d)),
|
||
}
|