From 1151372e5c2c8ea551628cc80aef23183489b64c Mon Sep 17 00:00:00 2001 From: boyang-zhang <497256303zby@gmail.com> Date: Sat, 27 Dec 2025 12:03:18 -0600 Subject: [PATCH] Dev branch improvements and bug fixes (#42) * Add agentic mode support for LlamaParse and fix eligibility extraction - Add agenticMode option to ParseOptions interface and Zod schema - Enable agentic parsing by default for better multi-sheet Excel support - Fix eligibility extraction to gracefully handle documents without eligibility criteria by returning empty array instead of throwing error * Refactor project index selection to single-select with auto-save - Change ProjectIndexSelector from multi-select checkboxes to single-select clickable rows with radio-style indicators - Implement debounced auto-save (800ms) when selection changes - Remove Edit/Save/Cancel buttons in favor of automatic persistence - Add refreshKey prop to ProjectDocuments for efficient refetch on changes - Update DocumentsSection to wire components with onSaveSuccess callback - Simplify Questions page index selector to show index name with Active badge * Fix ESLint configuration and resolve all lint errors - Add eslint and eslint-config-next as dev dependencies - Create .eslintrc.json with next/core-web-vitals preset - Fix 43 unescaped entity errors by replacing quotes and apostrophes with HTML entities across 14 component files - Fix 9 react-hooks/exhaustive-deps warnings by wrapping fetch functions in useCallback with proper dependency arrays * Add Vitest testing infrastructure with 173 unit tests - Configure Vitest with coverage reporting and path aliases - Add test scripts (test, test:run, test:coverage) to package.json - Install vitest, @vitest/coverage-v8, vite-tsconfig-paths, vitest-mock-extended Test coverage includes: - validators: extract-questions, generate-response, llamaparse, multi-step-response - errors: all API error classes with type guard - services: FileValidator (file type/size validation), DefaultResponseService - middleware: apiHandler and withApiHandler request validation Add mock infrastructure for Prisma, OpenAI, and test fixtures * Fix missing agenticMode in LlamaParse processing service Add agentic_mode field to request schema and form data parser to ensure the agenticMode option is properly passed through the parsing pipeline. This resolves TypeScript compilation error where agenticMode was required in LlamaParseOptions but not provided by the processing service. * Fix Add Manually button routing and improve Vercel preview URL handling - Fix 404 error when clicking Add Manually button by using correct route path /projects/{projectId}/questions/create instead of /questions/create?projectId= - Update magic link auth to use VERCEL_URL for preview deployments * Simplify IndexSelector to display-only mode Remove index configuration UI since only single file selection is now supported: - Remove Configure/Hide toggle button and collapsible panel - Remove Select All/Deselect All functionality - Remove checkbox selection grid for indexes - Clean up handleIndexToggle and handleSelectAllIndexes from provider - Keep project card display showing active index name and status --- .eslintrc.json | 3 + app/help/page.tsx | 6 +- app/login/actions.ts | 15 +- app/login/confirmation/page.tsx | 4 +- app/login/page.tsx | 2 +- app/organizations/[orgId]/page.tsx | 18 +- app/organizations/page.tsx | 12 +- .../questions/components/index-selector.tsx | 136 +- .../components/no-questions-available.tsx | 2 +- .../components/questions-provider.tsx | 26 - .../components/questions-section.tsx | 6 +- .../components/ai-suggestions-panel.tsx | 6 +- app/projects/components/documents-section.tsx | 18 +- app/projects/components/project-timeline.tsx | 12 +- components/FileUploader.tsx | 2 +- components/ProcessingModal.tsx | 2 +- components/TestimonialsSection.tsx | 6 +- .../organizations/KnowledgeBaseContent.tsx | 14 +- .../LlamaCloudConnectionDialog.tsx | 2 +- .../organizations/LlamaCloudDocuments.tsx | 22 +- components/organizations/SettingsContent.tsx | 2 +- components/projects/ProjectDocuments.tsx | 35 +- components/projects/ProjectIndexSelector.tsx | 237 +- components/ui/delete-confirmation-dialog.tsx | 2 +- lib/llamaparse-service.ts | 7 +- lib/services/llamaparse-processing-service.ts | 1 + lib/services/openai-question-extractor.ts | 15 +- lib/utils/form-data-parser.ts | 6 +- lib/validators/llamaparse.ts | 2 + package.json | 14 +- pnpm-lock.yaml | 4004 ++++++++++++++++- tests/mocks/fixtures/index.ts | 68 + tests/mocks/openai.ts | 40 + tests/mocks/prisma.ts | 26 + tests/unit/errors/api-errors.test.ts | 216 + tests/unit/middleware/api-handler.test.ts | 298 ++ .../services/default-response-service.test.ts | 161 + tests/unit/services/file-validator.test.ts | 185 + .../unit/validators/extract-questions.test.ts | 201 + .../unit/validators/generate-response.test.ts | 204 + tests/unit/validators/llamaparse.test.ts | 220 + .../validators/multi-step-response.test.ts | 394 ++ vitest.config.ts | 41 + vitest.setup.ts | 22 + 44 files changed, 6316 insertions(+), 399 deletions(-) create mode 100644 .eslintrc.json create mode 100644 tests/mocks/fixtures/index.ts create mode 100644 tests/mocks/openai.ts create mode 100644 tests/mocks/prisma.ts create mode 100644 tests/unit/errors/api-errors.test.ts create mode 100644 tests/unit/middleware/api-handler.test.ts create mode 100644 tests/unit/services/default-response-service.test.ts create mode 100644 tests/unit/services/file-validator.test.ts create mode 100644 tests/unit/validators/extract-questions.test.ts create mode 100644 tests/unit/validators/generate-response.test.ts create mode 100644 tests/unit/validators/llamaparse.test.ts create mode 100644 tests/unit/validators/multi-step-response.test.ts create mode 100644 vitest.config.ts create mode 100644 vitest.setup.ts diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..bffb357a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/app/help/page.tsx b/app/help/page.tsx index 9877c4b5..0fd5d631 100644 --- a/app/help/page.tsx +++ b/app/help/page.tsx @@ -128,7 +128,7 @@ export default function HelpPage() {
- We've sent you a magic link to your email address. Click the link in the email to sign in. + We've sent you a magic link to your email address. Click the link in the email to sign in.
- If you don't see the email, check your spam folder. The link will expire after 24 hours. + If you don't see the email, check your spam folder. The link will expire after 24 hours.
- We'll email you a magic link for a password-free sign in. + We'll email you a magic link for a password-free sign in.
diff --git a/app/organizations/[orgId]/page.tsx b/app/organizations/[orgId]/page.tsx index 6458c1ab..7b3e1394 100644 --- a/app/organizations/[orgId]/page.tsx +++ b/app/organizations/[orgId]/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { Organization } from '@/types/organization'; import { ProjectGrid } from '@/components/projects/ProjectGrid'; @@ -33,19 +33,19 @@ export default function OrganizationPage({ params }: OrganizationPageProps) { handleParams(); }, [params]); - const fetchOrganization = async () => { + const fetchOrganization = useCallback(async () => { if (!organizationId) return; - + try { setIsLoading(true); - + // First fetch the organization by slug const orgResponse = await fetch(`/api/organizations/${organizationId}`); - + if (!orgResponse.ok) { throw new Error('Failed to fetch organization'); } - + const orgData = await orgResponse.json(); setOrganization(orgData); @@ -59,13 +59,13 @@ export default function OrganizationPage({ params }: OrganizationPageProps) { } finally { setIsLoading(false); } - }; + }, [organizationId, toast]); useEffect(() => { if (organizationId) { fetchOrganization(); } - }, [organizationId, toast]); + }, [organizationId, fetchOrganization]); if (isLoading) { return ( @@ -83,7 +83,7 @@ export default function OrganizationPage({ params }: OrganizationPageProps) { return (The organization you're looking for doesn't exist or you don't have access to it.
+The organization you're looking for doesn't exist or you don't have access to it.
diff --git a/app/organizations/page.tsx b/app/organizations/page.tsx index 9e5c20bf..3f219ff9 100644 --- a/app/organizations/page.tsx +++ b/app/organizations/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -63,11 +63,11 @@ export default function OrganizationsPage() { description: "", }); - const fetchOrganizations = async () => { + const fetchOrganizations = useCallback(async () => { try { const response = await fetch("/api/organizations"); const data = await response.json(); - + if (data.success) { setOrganizations(data.data); } else { @@ -79,18 +79,18 @@ export default function OrganizationsPage() { } } catch (error) { toast({ - title: "Error", + title: "Error", description: "Failed to fetch organizations", variant: "destructive", }); } finally { setLoading(false); } - }; + }, [toast]); useEffect(() => { fetchOrganizations(); - }, []); + }, [fetchOrganizations]); const handleCreateOrganization = async () => { try { diff --git a/app/projects/[projectId]/questions/components/index-selector.tsx b/app/projects/[projectId]/questions/components/index-selector.tsx index fca17a05..b87b88c5 100644 --- a/app/projects/[projectId]/questions/components/index-selector.tsx +++ b/app/projects/[projectId]/questions/components/index-selector.tsx @@ -1,12 +1,8 @@ "use client" -import { useState } from "react" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" +import { Card, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" -import { Checkbox } from "@/components/ui/checkbox" -import { AlertCircle, Database, Settings } from "lucide-react" -import { cn } from "@/lib/utils" +import { AlertCircle, Database } from "lucide-react" interface ProjectIndex { id: string; @@ -17,147 +13,51 @@ interface IndexSelectorProps { availableIndexes: ProjectIndex[]; selectedIndexes: SetNo Project Indexes Configured
- This project has no document indexes configured. Go to the project's Documents tab to select indexes from your organization's LlamaCloud connection. + This project has no document indexes configured. Go to the project's Documents tab to select indexes from your organization's LlamaCloud connection.
No Project Indexes Configured
-- This project has no document indexes configured. Go to the project's Documents tab to select indexes from your organization's LlamaCloud connection. -
-- ⚠️ No project indexes selected. AI generation will use default responses. + No project indexes selected. AI generation will use default responses.
)} - - {showIndexSelector && ( -- Note: This selection is temporary for AI generation in this session. - You can only select from indexes that are already configured for this project. - To add or remove project indexes permanently, use the Documents tab. -
-- Select which of this project's configured indexes to use when generating AI answers: -
- -- ✓ AI will use documents from {selectedIndexes.size} selected project {selectedIndexes.size === 1 ? 'index' : 'indexes'} to generate answers. -
-- Our technical architecture is specifically designed to meet Velocity Labs' requirements for a scalable, + Our technical architecture is specifically designed to meet Velocity Labs' requirements for a scalable, secure, and maintainable healthcare solution. The architecture leverages industry best practices and our extensive experience in HIPAA-compliant systems:
@@ -194,7 +194,7 @@ export function AISuggestionsPanel({ questionId }: AISuggestionsPanelProps) {1. User Interface: A responsive web application built with React, featuring an intuitive interface designed specifically for healthcare professionals. The UI incorporates Velocity - Labs' design system for brand consistency. + Labs' design system for brand consistency.
@@ -210,7 +210,7 @@ export function AISuggestionsPanel({ questionId }: AISuggestionsPanelProps) {
- 4. Integration Hub: A dedicated integration layer for connecting with Velocity Labs' + 4. Integration Hub: A dedicated integration layer for connecting with Velocity Labs' existing systems, including HL7 FHIR support for healthcare interoperability and secure connections to third-party services.
diff --git a/app/projects/components/documents-section.tsx b/app/projects/components/documents-section.tsx index 46f5bfcb..360f38c6 100644 --- a/app/projects/components/documents-section.tsx +++ b/app/projects/components/documents-section.tsx @@ -1,5 +1,6 @@ "use client" +import { useState, useCallback } from "react" import { useSearchParams } from "next/navigation" import { ProjectIndexSelector } from "@/components/projects/ProjectIndexSelector" import { ProjectDocuments } from "@/components/projects/ProjectDocuments" @@ -13,6 +14,13 @@ export function DocumentsSection({ projectId: propProjectId }: DocumentsSectionP // Use prop if provided, otherwise fall back to search params const projectId = propProjectId || searchParams.get("projectId") + // Counter to trigger ProjectDocuments refresh when index changes + const [documentsRefreshKey, setDocumentsRefreshKey] = useState(0) + + const handleSaveSuccess = useCallback(() => { + setDocumentsRefreshKey(prev => prev + 1) + }, []) + if (!projectId) { return (- Choose the appropriate mode based on your document's complexity + Choose the appropriate mode based on your document's complexity
- "For every $1 that our company invests in AutoRFP, I estimate a return on investment of $500. We have seen really massive growth over the past few years and we couldn't have done it without AutoRFP." + "For every $1 that our company invests in AutoRFP, I estimate a return on investment of $500. We have seen really massive growth over the past few years and we couldn't have done it without AutoRFP."
- "We were able to reduce the time maintaining our content library by 50% through the elimination of writing/editing tasks involved in each RFP response, and AI Assistant has contributed to our increasing win rate." + "We were able to reduce the time maintaining our content library by 50% through the elimination of writing/editing tasks involved in each RFP response, and AI Assistant has contributed to our increasing win rate."
- "It could take 5 to 10 minutes to manually find something in the library or to take two things and merge them together. With AI Assistant, it is actually answering, on average, our questions in 30 seconds." + "It could take 5 to 10 minutes to manually find something in the library or to take two things and merge them together. With AI Assistant, it is actually answering, on average, our questions in 30 seconds."