qdrant db in multi doc
This commit is contained in:
parent
8a7f6f74dc
commit
5e89f25f54
52
src/app/api/multi-docs/jobs/[jobId]/report/pdf/route.ts
Normal file
52
src/app/api/multi-docs/jobs/[jobId]/report/pdf/route.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getApiUrl } from '@/config/backend';
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ jobId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { jobId } = await params;
|
||||
|
||||
if (!jobId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Job ID is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Forward request to backend multi-document-upload-service via API gateway
|
||||
const apiGatewayUrl = process.env.NEXT_PUBLIC_API_GATEWAY_URL || 'http://localhost:8000'
|
||||
const fullUrl = `${apiGatewayUrl}/multi-document-upload-service/jobs/${jobId}/report/pdf`;
|
||||
|
||||
const response = await fetch(fullUrl, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return NextResponse.json(
|
||||
{ error: errorText || 'Failed to fetch PDF report' },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
// Get PDF as blob
|
||||
const pdfBlob = await response.blob();
|
||||
|
||||
// Return PDF with proper headers
|
||||
return new NextResponse(pdfBlob, {
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment; filename="onboarding_report_${jobId}.pdf"`,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching PDF report:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
48
src/app/api/multi-docs/jobs/[jobId]/report/route.ts
Normal file
48
src/app/api/multi-docs/jobs/[jobId]/report/route.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getApiUrl } from '@/config/backend';
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ jobId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { jobId } = await params;
|
||||
|
||||
if (!jobId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Job ID is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Forward request to backend multi-document-upload-service via API gateway
|
||||
// The API gateway routes /multi-document-upload-service/* to the service
|
||||
const apiGatewayUrl = process.env.NEXT_PUBLIC_API_GATEWAY_URL || 'http://localhost:8000'
|
||||
const fullUrl = `${apiGatewayUrl}/multi-document-upload-service/jobs/${jobId}/report`;
|
||||
|
||||
const response = await fetch(fullUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return NextResponse.json(
|
||||
{ error: errorText || 'Failed to fetch report' },
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const report = await response.json();
|
||||
return NextResponse.json(report);
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching report:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,14 +94,27 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
pollCount++;
|
||||
|
||||
try {
|
||||
// Poll for repository sync status
|
||||
const response = await authApiClient.get(`/api/vcs/${provider}/repositories?t=${Date.now()}`, {
|
||||
headers: { 'x-user-id': user?.id }
|
||||
// Poll for repository sync status from all_repositories table
|
||||
// Use the endpoint that queries the database, not the GitHub API
|
||||
const userId = user?.id;
|
||||
if (!userId) {
|
||||
console.error('No user ID available for monitoring');
|
||||
clearInterval(pollInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await authApiClient.get(`/api/github/user/${userId}/repositories?t=${Date.now()}&force_refresh=true`, {
|
||||
headers: { 'x-user-id': userId }
|
||||
});
|
||||
|
||||
if (response.data?.success) {
|
||||
const repositories = response.data.data || [];
|
||||
const repo = repositories.find((r: any) => r.repository_url === repositoryUrl);
|
||||
const repo = repositories.find((r: any) =>
|
||||
r.repository_url === repositoryUrl ||
|
||||
r.repository_url === decodeURIComponent(repositoryUrl) ||
|
||||
(r.owner_name && r.repository_name &&
|
||||
repositoryUrl.includes(`${r.owner_name}/${r.repository_name}`))
|
||||
);
|
||||
|
||||
if (repo) {
|
||||
const status = repo.sync_status;
|
||||
@ -741,11 +754,12 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
console.log('🔍 Full error object:', err)
|
||||
console.log('🔍 Error response object:', err?.response)
|
||||
|
||||
// If backend signals auth required, show authentication button instead of auto-redirect
|
||||
if ((status === 401 || status === 200) && (data?.requires_auth || data?.message?.includes('authentication'))) {
|
||||
console.log('🔐 Private repository detected, showing authentication button:', { status, requires_auth: data?.requires_auth, message: data?.message })
|
||||
// If backend signals auth required, handle OAuth redirect
|
||||
// Check for requires_auth in response (status can be 200, 401, or any status)
|
||||
if (data?.requires_auth || data?.message?.includes('authentication') || data?.message?.includes('authentication required')) {
|
||||
console.log('🔐 Private repository detected, auto-redirecting to OAuth:', { status, requires_auth: data?.requires_auth, message: data?.message, auth_url: data?.auth_url })
|
||||
|
||||
// Store the auth_url for when user clicks the authenticate button
|
||||
// Store the auth_url for OAuth redirect
|
||||
try {
|
||||
sessionStorage.setItem('pending_git_attach', JSON.stringify({
|
||||
repository_url: gitUrl.trim(),
|
||||
@ -758,8 +772,14 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
console.warn('⚠️ Failed to store pending git attach:', e)
|
||||
}
|
||||
|
||||
// Don't auto-redirect, let the user click the authenticate button
|
||||
console.log('🔐 Private repository detected - user must click authenticate button')
|
||||
// Auto-redirect to OAuth
|
||||
if (data?.auth_url) {
|
||||
console.log('🔐 Private repository detected - auto-redirecting to OAuth:', data.auth_url)
|
||||
window.location.replace(data.auth_url)
|
||||
return
|
||||
} else {
|
||||
alert('Authentication required but OAuth URL not available. Please try again.')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
238
src/components/multi-docs-report-viewer.tsx
Normal file
238
src/components/multi-docs-report-viewer.tsx
Normal file
@ -0,0 +1,238 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { Download, FileText, Loader2, X } from "lucide-react"
|
||||
import { getApiUrl } from "@/config/backend"
|
||||
|
||||
interface ProjectReport {
|
||||
job_id: string
|
||||
title: string
|
||||
content: string
|
||||
sections: Record<string, string>
|
||||
key_concepts: string[]
|
||||
total_pages: number
|
||||
generated_at: string
|
||||
metadata: {
|
||||
pdf_path?: string
|
||||
total_relations?: number
|
||||
total_concepts?: number
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
interface MultiDocsReportViewerProps {
|
||||
jobId: string
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export function MultiDocsReportViewer({ jobId, onClose }: MultiDocsReportViewerProps) {
|
||||
const [report, setReport] = useState<ProjectReport | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [downloading, setDownloading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!jobId) return
|
||||
|
||||
const fetchReport = async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await fetch(getApiUrl(`/api/multi-docs/jobs/${jobId}/report`))
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ detail: response.statusText }))
|
||||
// Extract error message from API response
|
||||
const errorMsg = errorData.detail || errorData.error || `Failed to fetch report: ${response.status}`
|
||||
throw new Error(errorMsg)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
setReport(data)
|
||||
} catch (err: any) {
|
||||
console.error("Failed to fetch report:", err)
|
||||
setError(err.message || "Failed to load report")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchReport()
|
||||
}, [jobId])
|
||||
|
||||
const handleDownloadPDF = async () => {
|
||||
if (!jobId) return
|
||||
|
||||
setDownloading(true)
|
||||
try {
|
||||
const response = await fetch(getApiUrl(`/api/multi-docs/jobs/${jobId}/report/pdf`))
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to download PDF")
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.download = `onboarding_report_${jobId}.pdf`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
document.body.removeChild(a)
|
||||
} catch (err: any) {
|
||||
console.error("Failed to download PDF:", err)
|
||||
setError(err.message || "Failed to download PDF")
|
||||
} finally {
|
||||
setDownloading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card className="bg-white/5 border border-white/10">
|
||||
<CardContent className="p-8 text-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-orange-400 mx-auto mb-4" />
|
||||
<p className="text-white/60">Loading report...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Card className="bg-white/5 border border-white/10">
|
||||
<CardContent className="p-8">
|
||||
<Alert variant="destructive" className="bg-red-500/10 border border-red-500/40 text-red-200">
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
{onClose && (
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="outline"
|
||||
className="mt-4 w-full border-white/20 text-white hover:bg-white/10"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
if (!report) {
|
||||
return (
|
||||
<Card className="bg-white/5 border border-white/10">
|
||||
<CardContent className="p-8 text-center">
|
||||
<p className="text-white/60">No report available</p>
|
||||
{onClose && (
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="outline"
|
||||
className="mt-4 border-white/20 text-white hover:bg-white/10"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-white/5 border border-white/10">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<FileText className="h-5 w-5 text-orange-400" />
|
||||
<CardTitle className="text-lg text-white">{report.title}</CardTitle>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
onClick={handleDownloadPDF}
|
||||
disabled={downloading || !report.metadata?.pdf_path}
|
||||
className="bg-orange-500 hover:bg-orange-600 text-black font-semibold"
|
||||
size="sm"
|
||||
>
|
||||
{downloading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Downloading...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="mr-2 h-4 w-4" /> Download PDF
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{onClose && (
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 mt-2 text-xs text-white/60">
|
||||
<span>{report.total_pages} pages</span>
|
||||
{report.metadata?.total_relations && (
|
||||
<span>{report.metadata.total_relations} relationships</span>
|
||||
)}
|
||||
{report.metadata?.total_concepts && (
|
||||
<span>{report.metadata.total_concepts} concepts</span>
|
||||
)}
|
||||
<span>
|
||||
Generated: {new Date(report.generated_at).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="bg-black/20 rounded-lg p-6 border border-white/10 max-h-[600px] overflow-y-auto">
|
||||
<div
|
||||
className="text-white/90 markdown-content prose prose-invert"
|
||||
style={{
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
}}
|
||||
>
|
||||
{report.content.split('\n').map((line, idx) => {
|
||||
// Headers
|
||||
if (line.match(/^### /)) {
|
||||
return <h3 key={idx} className="text-lg font-semibold text-white mb-2 mt-4">{line.replace(/^### /, '')}</h3>
|
||||
}
|
||||
if (line.match(/^## /)) {
|
||||
return <h2 key={idx} className="text-xl font-semibold text-orange-300 mb-3 mt-5 border-b border-white/10 pb-1">{line.replace(/^## /, '')}</h2>
|
||||
}
|
||||
if (line.match(/^# /)) {
|
||||
return <h1 key={idx} className="text-2xl font-bold text-orange-400 mb-4 mt-6 border-b border-orange-400/40 pb-2">{line.replace(/^# /, '')}</h1>
|
||||
}
|
||||
// Lists
|
||||
if (line.match(/^[\-\*] /)) {
|
||||
return <li key={idx} className="text-white/80 ml-4 list-disc">{line.replace(/^[\-\*] /, '')}</li>
|
||||
}
|
||||
if (line.match(/^\d+\. /)) {
|
||||
return <li key={idx} className="text-white/80 ml-4 list-decimal">{line.replace(/^\d+\. /, '')}</li>
|
||||
}
|
||||
// Code blocks
|
||||
if (line.match(/^```/)) {
|
||||
return <br key={idx} />
|
||||
}
|
||||
// Regular paragraphs
|
||||
if (line.trim() === '') {
|
||||
return <br key={idx} />
|
||||
}
|
||||
return <p key={idx} className="text-white/80 mb-3 leading-relaxed">{line}</p>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,8 +5,9 @@ import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { Upload, File as FileIcon, Loader2, ChevronDown, ChevronUp, X } from "lucide-react"
|
||||
import { Upload, File as FileIcon, Loader2, ChevronDown, ChevronUp, X, FileText } from "lucide-react"
|
||||
import { getApiUrl } from "@/config/backend"
|
||||
import { MultiDocsReportViewer } from "@/components/multi-docs-report-viewer"
|
||||
|
||||
interface JobStatus {
|
||||
job_id: string
|
||||
@ -15,14 +16,16 @@ interface JobStatus {
|
||||
processed_files: number
|
||||
total_files: number
|
||||
error?: string | null
|
||||
report?: any | null
|
||||
}
|
||||
|
||||
const STAGE_COPY: Record<string, string> = {
|
||||
received: "Job received",
|
||||
saving_files: "Saving files",
|
||||
extracting: "Extracting document content",
|
||||
analyzing: "Calling Claude for causal relations",
|
||||
building_graph: "Writing to Neo4j knowledge graph",
|
||||
extracting: "Extracting content (PyMuPDF + Qwen2.5-VL)",
|
||||
building_graph: "Building knowledge graph (DoWhy + Neo4j)",
|
||||
indexing_vectors: "Indexing in vector database (Qdrant)",
|
||||
generating_report: "Generating onboarding report (Claude AI)",
|
||||
completed: "Completed",
|
||||
failed: "Failed",
|
||||
}
|
||||
@ -41,6 +44,7 @@ export function MultiDocsUploadInline({ onJobCreated }: MultiDocsUploadInlinePro
|
||||
const [jobId, setJobId] = useState<string | null>(null)
|
||||
const [status, setStatus] = useState<JobStatus | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [showReport, setShowReport] = useState(false)
|
||||
|
||||
const uploadUrl = useMemo(() => getApiUrl("/api/multi-docs/jobs"), [])
|
||||
|
||||
@ -148,12 +152,13 @@ export function MultiDocsUploadInline({ onJobCreated }: MultiDocsUploadInlinePro
|
||||
if (status.stage === "saving_files") {
|
||||
stageProgress = 5
|
||||
} else if (status.stage === "extracting") {
|
||||
stageProgress = 10 + fileProgress // 10-40%
|
||||
} else if (status.stage === "analyzing") {
|
||||
// Analyzing takes the longest, show 40-85%
|
||||
stageProgress = 40 + Math.min(fileProgress * 1.5, 45) // 40-85%
|
||||
stageProgress = 10 + Math.min(fileProgress * 0.3, 30) // 10-40%
|
||||
} else if (status.stage === "building_graph") {
|
||||
stageProgress = 85 + Math.min(fileProgress * 0.5, 10) // 85-95%
|
||||
stageProgress = 40 + Math.min(fileProgress * 0.2, 20) // 40-60%
|
||||
} else if (status.stage === "indexing_vectors") {
|
||||
stageProgress = 60 + Math.min(fileProgress * 0.15, 15) // 60-75%
|
||||
} else if (status.stage === "generating_report") {
|
||||
stageProgress = 75 + Math.min(fileProgress * 0.2, 20) // 75-95%
|
||||
} else if (status.stage === "completed") {
|
||||
stageProgress = 100
|
||||
} else if (status.stage === "failed") {
|
||||
@ -215,15 +220,36 @@ export function MultiDocsUploadInline({ onJobCreated }: MultiDocsUploadInlinePro
|
||||
<Alert className="bg-green-500/10 border border-green-500/40 text-green-200">
|
||||
<AlertDescription>
|
||||
✅ Knowledge graph created successfully! {status.total_files} file(s) processed.
|
||||
{status.error && (
|
||||
<div className="mt-2 text-xs text-yellow-300">
|
||||
⚠️ Note: {status.error}
|
||||
</div>
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Button
|
||||
onClick={resetUpload}
|
||||
variant="outline"
|
||||
className="w-full border-white/20 text-white hover:bg-white/10"
|
||||
>
|
||||
Upload More Documents
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => setShowReport(true)}
|
||||
className="flex-1 bg-orange-500 hover:bg-orange-600 text-black font-semibold"
|
||||
>
|
||||
<FileText className="mr-2 h-4 w-4" /> View Report
|
||||
</Button>
|
||||
<Button
|
||||
onClick={resetUpload}
|
||||
variant="outline"
|
||||
className="flex-1 border-white/20 text-white hover:bg-white/10"
|
||||
>
|
||||
Upload More
|
||||
</Button>
|
||||
</div>
|
||||
{showReport && jobId && (
|
||||
<div className="mt-4">
|
||||
<MultiDocsReportViewer
|
||||
jobId={jobId}
|
||||
onClose={() => setShowReport(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : status?.stage === "failed" ? (
|
||||
<div className="space-y-3">
|
||||
|
||||
@ -5,8 +5,9 @@ import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||
import { Upload, File as FileIcon, Loader2, ArrowLeft, ArrowRight } from "lucide-react"
|
||||
import { Upload, File as FileIcon, Loader2, ArrowLeft, ArrowRight, FileText } from "lucide-react"
|
||||
import { getApiUrl } from "@/config/backend"
|
||||
import { MultiDocsReportViewer } from "@/components/multi-docs-report-viewer"
|
||||
|
||||
interface MultiDocsUploadStepProps {
|
||||
onBack: () => void
|
||||
@ -25,9 +26,10 @@ interface JobStatus {
|
||||
const STAGE_COPY: Record<string, string> = {
|
||||
received: "Job received",
|
||||
saving_files: "Saving files",
|
||||
extracting: "Extracting document content",
|
||||
analyzing: "Calling Claude for causal relations",
|
||||
building_graph: "Writing to Neo4j knowledge graph",
|
||||
extracting: "Extracting content (PyMuPDF + Qwen2.5-VL)",
|
||||
building_graph: "Building knowledge graph (DoWhy + Neo4j)",
|
||||
indexing_vectors: "Indexing in vector database (Qdrant)",
|
||||
generating_report: "Generating onboarding report (Claude AI)",
|
||||
completed: "Completed",
|
||||
failed: "Failed",
|
||||
}
|
||||
@ -38,6 +40,7 @@ export function MultiDocsUploadStep({ onBack, onNext }: MultiDocsUploadStepProps
|
||||
const [jobId, setJobId] = useState<string | null>(null)
|
||||
const [status, setStatus] = useState<JobStatus | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [showReport, setShowReport] = useState(false)
|
||||
|
||||
const uploadUrl = useMemo(() => getApiUrl("/api/multi-docs/jobs"), [])
|
||||
|
||||
@ -214,6 +217,31 @@ export function MultiDocsUploadStep({ onBack, onNext }: MultiDocsUploadStepProps
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status?.stage === "completed" && jobId && !showReport && (
|
||||
<div className="space-y-3 pt-4 border-t border-white/10">
|
||||
<Alert className="bg-green-500/10 border border-green-500/40 text-green-200">
|
||||
<AlertDescription>
|
||||
✅ Processing completed! Report is ready.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<Button
|
||||
onClick={() => setShowReport(true)}
|
||||
className="w-full bg-orange-500 hover:bg-orange-600 text-black font-semibold"
|
||||
>
|
||||
<FileText className="mr-2 h-4 w-4" /> View Onboarding Report
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showReport && jobId && (
|
||||
<div className="pt-4 border-t border-white/10">
|
||||
<MultiDocsReportViewer
|
||||
jobId={jobId}
|
||||
onClose={() => setShowReport(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between pt-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@ -78,10 +78,14 @@ export async function attachRepository(payload: AttachRepositoryPayload, retries
|
||||
user_id: userId
|
||||
}, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
timeout: 60000 // 60 seconds for repository operations
|
||||
timeout: 60000, // 60 seconds for repository operations
|
||||
validateStatus: (status) => {
|
||||
// Accept 200, 201, and also 401/403 if they contain requires_auth
|
||||
return status >= 200 && status < 300 || status === 401 || status === 403;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`📡 [attachRepository] ${provider.toUpperCase()} response:`, response.data);
|
||||
console.log(`📡 [attachRepository] ${provider.toUpperCase()} response status: ${response.status}, data:`, response.data);
|
||||
|
||||
// Normalize response
|
||||
let parsed: any = response.data;
|
||||
@ -98,18 +102,24 @@ export async function attachRepository(payload: AttachRepositoryPayload, retries
|
||||
success: (parsed?.success === true || parsed?.success === 'true')
|
||||
};
|
||||
|
||||
// If authentication is required, return the response instead of throwing error
|
||||
if (normalized.requires_auth || (normalized.message && normalized.message.includes('authentication'))) {
|
||||
console.log('🔐 [attachRepository] Authentication required, returning response for UI to show authenticate button:', {
|
||||
// If authentication is required, return the response immediately (even if success: false)
|
||||
if (normalized.requires_auth || (normalized.message && (normalized.message.includes('authentication') || normalized.message.includes('authentication required')))) {
|
||||
console.log('🔐 [attachRepository] Authentication required, returning response for OAuth redirect:', {
|
||||
requires_auth: normalized.requires_auth,
|
||||
message: normalized.message,
|
||||
auth_url: normalized.auth_url
|
||||
auth_url: normalized.auth_url,
|
||||
status: response.status
|
||||
});
|
||||
|
||||
// Return the response so UI can show authenticate button
|
||||
// Return the response so UI can redirect to OAuth
|
||||
return normalized;
|
||||
}
|
||||
|
||||
// If success is false and no auth required, it's a real error
|
||||
if (!normalized.success && !normalized.requires_auth) {
|
||||
throw new Error(normalized.message || 'Failed to attach repository');
|
||||
}
|
||||
|
||||
return normalized;
|
||||
} catch (error: any) {
|
||||
// If it's the last retry or not a connection error, throw immediately
|
||||
|
||||
Loading…
Reference in New Issue
Block a user