Skip to content

Restore demo-ready chatbot flow and sync resilience#3

Closed
yassinekolsi wants to merge 7 commits into
masterfrom
youssef
Closed

Restore demo-ready chatbot flow and sync resilience#3
yassinekolsi wants to merge 7 commits into
masterfrom
youssef

Conversation

@yassinekolsi

Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings March 19, 2026 07:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores an AI assistant chat flow for patient records while improving sync/extraction resilience and aligning Gemini model usage across edge functions and the mobile app.

Changes:

  • Adds persistent AI chat storage in Supabase (conversations/messages) with RLS and a new ai-chat-patient Edge Function.
  • Improves offline-to-cloud sync by triggering server-side extraction and syncing structured fields/status back to local storage.
  • Introduces a shared context-selection utility with Jest coverage, plus UI entry points to the assistant.

Reviewed changes

Copilot reviewed 19 out of 21 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
supabase/migrations/004_ai_chat.sql Adds AI chat tables, indexes, and RLS policies.
supabase/functions/ai-chat-patient/index.ts New Edge Function for patient-specific AI chat with optional persistence.
supabase/functions/extract-handwriting/index.ts Switches to image_url and adds URL→storage-path normalization.
supabase/functions/generate-narrative/index.ts Updates Gemini model selection for narrative generation.
services/cloudSync.ts Upserts richer record payloads and triggers server extraction during sync.
services/geminiService.ts Adds retry/backoff logic for Gemini handwriting extraction.
services/patientDataService.ts Aligns Gemini text model and reduces noisy error logging.
services/aiChat.ts Adds client-side API wrapper with Edge → direct Gemini fallback.
lib/types.ts Adds AI chat type definitions.
lib/contextSelector.ts Adds shared context selection utilities.
tests/contextSelector.test.ts Adds unit tests for the context selection utilities.
jest.config.js / jest.setup.ts Configures Jest to run repo tests from __tests__.
hooks/useNetworkState.ts Treats isInternetReachable: null as reachable when connected.
components/ActiveSession.tsx Improves surfaced error details when extraction fails.
app/patient/summary.tsx Adds an “AI” action to open the assistant for the current patient.
app/patient/[id]/index.tsx Updates patient detail screen to link to the assistant.
app/patient/[id]/assistant.tsx Adds the patient assistant chat UI.
app/patient/[id]/_layout.tsx Adds a nested layout for patient routes.
app.json Adds expo-font plugin and reformats platforms list.
package-lock.json Updates lockfile entries related to dev dependency flags and peer packages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +113 to +141
const { data: doctor, error: doctorErr } = await supabaseService
.from('doctors')
.select('id, clinic_id')
.eq('id', user.id)
.single();

const doctorProfile = doctorErr || !doctor
? { id: user.id, clinic_id: patient.clinic_id ?? null }
: doctor;

let persistenceEnabled = true;
const { error: chatTableProbeError } = await supabaseService
.from('ai_conversations')
.select('id')
.limit(1);

if (chatTableProbeError) {
persistenceEnabled = false;
console.warn('[ai-chat-patient] ai_conversations unavailable, using stateless mode:', chatTableProbeError.message);
}

if (!doctor || doctorErr) {
persistenceEnabled = false;
console.warn('[ai-chat-patient] Doctor profile missing, using stateless mode for chat.');
}

if (patient.clinic_id && doctorProfile.clinic_id && patient.clinic_id !== doctorProfile.clinic_id) {
return jsonResponse({ error: 'Access denied: patient belongs to a different clinic' }, 403);
}
Comment on lines +184 to +197
const { data: records } = await supabaseService
.from('records')
.select('id, extracted_data, doctor_corrections, status, created_at, session_id')
.in('session_id',
(await supabaseService
.from('sessions')
.select('id')
.eq('patient_id', patient_id)
).data?.map((s: { id: string }) => s.id) ?? []
)
.in('status', ['approved', 'needs_review'])
.order('created_at', { ascending: false })
.limit(MAX_RECORDS);

Comment thread services/cloudSync.ts Outdated
Comment on lines +254 to +256
await updateRecordExtraction(
record.id,
cloudRecord.extracted_data,
Comment thread services/geminiService.ts Outdated
Comment on lines +82 to +92
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_PRIMARY_MODEL}:generateContent?key=${GEMINI_API_KEY}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
}
);
Comment on lines +66 to +78
function toStoragePath(imageUrl: string): string {
// Preferred format is already a storage path like "doctor/session/record.jpg".
if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://')) {
return imageUrl;
}

// Fallback if a public URL was stored in older rows.
// Expected fragment: /storage/v1/object/public/scan-images/<path>
const marker = '/storage/v1/object/public/scan-images/';
const idx = imageUrl.indexOf(marker);
if (idx === -1) return imageUrl;
return decodeURIComponent(imageUrl.substring(idx + marker.length));
}
TO authenticated
WITH CHECK (
doctor_id = auth.uid()
AND clinic_id IN (SELECT clinic_id FROM doctors WHERE id = auth.uid())
@@ -0,0 +1,336 @@
// @ts-nocheck
Comment thread services/aiChat.ts Outdated
Comment on lines +40 to +47
function scoreRelevance(text: string, queryTokens: string[]): number {
const lower = text.toLowerCase();
let score = 0;
for (const token of queryTokens) {
if (lower.includes(token)) score++;
}
return score;
}
@yassinekolsi

Copy link
Copy Markdown
Member Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copilot AI commented Mar 19, 2026

Copy link
Copy Markdown

@yassinekolsi I've opened a new pull request, #4, to work on those changes. Once the pull request is ready, I'll request review from you.

@yassinekolsi

Copy link
Copy Markdown
Member Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copilot AI commented Mar 19, 2026

Copy link
Copy Markdown

@yassinekolsi I've opened a new pull request, #5, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 5 commits March 19, 2026 15:02
… network retry, toStoragePath, RLS policy, ts-nocheck, shared contextSelector

Co-authored-by: yassinekolsi <196232305+yassinekolsi@users.noreply.github.com>
Co-authored-by: yassinekolsi <196232305+yassinekolsi@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fix security, resilience, and type-safety issues in AI chat and sync pipeline
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants