Skip to content
Merged
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
24 changes: 24 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier'
],
env: {
browser: true,
node: true,
es2021: true
},
settings: {
react: { version: 'detect' }
},
rules: {
'react/react-in-jsx-scope': 'off'
}
};
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm install

- name: Lint
run: npm run lint
continue-on-error: true

- name: Test
run: npm test

- name: Build
run: npm run build

- name: Upload Pages artifact
if: success()
uses: actions/upload-pages-artifact@v1
with:
path: dist

- name: Deploy to GitHub Pages
if: success()
uses: actions/deploy-pages@v1

- name: Deploy to Vercel
if: success() && env.VERCEL_TOKEN != ''
run: |
npx vercel deploy --prod --token $VERCEL_TOKEN --confirm
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"semi": true,
"trailingComma": "all"
}
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# CodePilot

## Project Summary

CodePilot is a comprehensive health management platform that aggregates biometric data, goal tracking, genetic risk insights and personal health events into a single interface. It helps users monitor progress and receive tailored feedback to improve their wellbeing.

## Key Features

- **Health tracking** for metrics, symptoms and medications
- **Personalized insights** powered by machine learning
- **Wearable integrations** to sync data from external devices

## Tech Stack

- **TypeScript** for client and server code
- **Express** API server
- **PostgreSQL** database (via Drizzle ORM)
- **TailwindCSS** styling
- **Vite** build tool

## Getting Started

Install dependencies and launch the development server:

```bash
npm install
npm run dev
```

Use `start-all.sh` to run both the Node.js backend and Streamlit interface together.

## Folder Structure Overview

- `client/` – React frontend powered by Vite
- `server/` – Express API and storage modules
- `components/` – Shared UI components for the Streamlit app
- `docs/` – Additional project documentation
- `tests/` – Test suites and helpers
- `utils/` – Utility scripts and shared helpers

## Contribution Guidelines

1. Create a topic branch from `main`.
2. Follow the coding style in this repository and add unit tests when possible.
3. Submit changes through a pull request and request review.

See `docs/BRANCH_PROTECTION.md` for recommended branch protection settings.

## Future Roadmap

- Expand wearable device support
- Add advanced analytics and alerting
- Improve CI test coverage

17 changes: 3 additions & 14 deletions client/src/components/DigitalTwinSimulation.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import React, { useState, lazy, Suspense } from 'react';
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useQuery, useMutation } from "@tanstack/react-query";
const Line = lazy(() => import('react-chartjs-2').then(m => ({ default: m.Line })));
import { Loader2 } from 'lucide-react';

function ChartFallback() {
return (
<div className="flex justify-center items-center h-48">
<Loader2 className="h-6 w-6 animate-spin text-blue-600" />
</div>
);
}
import { Line } from 'react-chartjs-2';
import {
Brain,
Play,
Expand Down Expand Up @@ -630,9 +621,7 @@ function SimulationResults({ results, onClose }) {
<CardContent className="space-y-6">
{/* Chart */}
<div className="h-64">
<Suspense fallback={<ChartFallback />}>
<Line data={chartData} options={chartOptions} />
</Suspense>
<Line data={chartData} options={chartOptions} />
</div>

{/* Insights */}
Expand Down
28 changes: 7 additions & 21 deletions client/src/components/HealthInsightsCorrelation.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import React, { useState, useEffect, lazy, Suspense } from 'react';
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useQuery } from "@tanstack/react-query";
const Line = lazy(() => import('react-chartjs-2').then(m => ({ default: m.Line })));
const Scatter = lazy(() => import('react-chartjs-2').then(m => ({ default: m.Scatter })));
import { Loader2 } from 'lucide-react';

function ChartFallback() {
return (
<div className="flex justify-center items-center h-48">
<Loader2 className="h-6 w-6 animate-spin text-blue-600" />
</div>
);
}
import { Line, Scatter } from 'react-chartjs-2';
import {
TrendingUp,
TrendingDown,
Expand Down Expand Up @@ -284,8 +274,7 @@ export function HealthInsightsCorrelation() {
<div>
<h3 className="font-semibold mb-3">Correlation Scatter Plot</h3>
<div className="h-64 bg-gray-50 dark:bg-gray-800 rounded-lg flex items-center justify-center">
<Suspense fallback={<ChartFallback />}>
<Scatter
<Scatter
data={{
datasets: [{
label: 'Data Points',
Expand Down Expand Up @@ -322,19 +311,17 @@ export function HealthInsightsCorrelation() {
}
}
}}
/>
</Suspense>
/>
</div>
</div>

{/* Timeline Overlay */}
<div>
<h3 className="font-semibold mb-3">Timeline Comparison</h3>
<div className="h-64 bg-gray-50 dark:bg-gray-800 rounded-lg flex items-center justify-center">
<Suspense fallback={<ChartFallback />}>
<Line
<Line
data={{
labels: visualizationData.timeline?.metric1Data?.map(d =>
labels: visualizationData.timeline?.metric1Data?.map(d =>
new Date(d.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
) || [],
datasets: [
Expand Down Expand Up @@ -385,8 +372,7 @@ export function HealthInsightsCorrelation() {
}
}
}}
/>
</Suspense>
/>
</div>
</div>
</div>
Expand Down
78 changes: 78 additions & 0 deletions client/src/components/HealthTimeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Badge } from "@/components/ui/badge";
import { Activity, Target, Dna, CalendarCheck } from "lucide-react";
import jsPDF from "jspdf";
import html2canvas from "html2canvas";
import { useRef } from "react";

const iconMap: Record<string, JSX.Element> = {
metric: <Activity className="h-4 w-4" />,
goal: <Target className="h-4 w-4" />,
genetic_risk: <Dna className="h-4 w-4" />,
event: <CalendarCheck className="h-4 w-4" />,
};

export default function HealthTimeline() {
const containerRef = useRef<HTMLDivElement>(null);
const { data: events = [], isLoading } = useQuery({
queryKey: ["/api/user/timeline"],
queryFn: () => apiRequest("GET", "/api/user/timeline").then(res => res.json()),
});

const exportPdf = async () => {
if (!containerRef.current) return;
const canvas = await html2canvas(containerRef.current);
const imgData = canvas.toDataURL("image/png");
const pdf = new jsPDF("p", "pt", "a4");
const width = pdf.internal.pageSize.getWidth();
const height = (canvas.height * width) / canvas.width;
pdf.addImage(imgData, "PNG", 0, 0, width, height);
pdf.save("timeline.pdf");
};

return (
<Card>
<CardHeader className="flex items-center justify-between">
<CardTitle>Health Timeline</CardTitle>
<Button variant="outline" size="sm" onClick={exportPdf}>
Export PDF
</Button>
</CardHeader>
<CardContent>
<ScrollArea className="h-96 pr-4" ref={containerRef} id="timeline-container">
{isLoading ? (
<p>Loading...</p>
) : (
<div className="space-y-4">
{events.map((event: any, idx: number) => (
<div key={idx} className="flex items-start space-x-3">
<div className="p-2 bg-muted rounded">
{iconMap[event.type] || <Activity className="h-4 w-4" />}
</div>
<div className="flex-1">
<div className="flex items-center justify-between">
<h4 className="font-medium">{event.title}</h4>
<span className="text-xs text-muted-foreground">
{new Date(event.date).toLocaleDateString()}
</span>
</div>
{event.value && (
<p className="text-sm text-muted-foreground">{String(event.value)}</p>
)}
<Badge variant="outline" className="mt-1 capitalize">
{event.type.replace("_", " ")}
</Badge>
</div>
</div>
))}
</div>
)}
</ScrollArea>
</CardContent>
</Card>
);
}
Loading
Loading