fixed production build errors
This commit is contained in:
parent
82087bf08f
commit
4355efdc1c
8 changed files with 29 additions and 376 deletions
|
|
@ -1,226 +0,0 @@
|
|||
import { useState, useRef, useEffect } from 'react';
|
||||
import type { TimelineEntry, Utterance } from '../types';
|
||||
|
||||
interface TimelineVisualizationProps {
|
||||
timeline: TimelineEntry[];
|
||||
utterances: Utterance[];
|
||||
pullPushTransitions: Array<{
|
||||
time_sec: number;
|
||||
from: string;
|
||||
to: string;
|
||||
speaker: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function TimelineVisualization({
|
||||
timeline,
|
||||
utterances,
|
||||
pullPushTransitions
|
||||
}: TimelineVisualizationProps) {
|
||||
const [selectedId, setSelectedId] = useState<number | null>(null);
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const scrollToUtterance = (utteranceId: number) => {
|
||||
setSelectedId(utteranceId);
|
||||
const element = document.getElementById(`utterance-${utteranceId}`);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
};
|
||||
|
||||
// Expose scrollToUtterance globally so action items can use it
|
||||
useEffect(() => {
|
||||
(window as any).scrollToUtterance = scrollToUtterance;
|
||||
return () => {
|
||||
delete (window as any).scrollToUtterance;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getBehaviorColor = (behavior: string): string => {
|
||||
const pullBehaviors = [
|
||||
'open_question',
|
||||
'closed_question',
|
||||
'testing_understanding',
|
||||
'summarizing',
|
||||
'bringing_in'
|
||||
];
|
||||
|
||||
if (pullBehaviors.includes(behavior)) {
|
||||
return 'bg-green-500/20 border-green-500';
|
||||
}
|
||||
|
||||
return 'bg-orange-500/20 border-orange-500';
|
||||
};
|
||||
|
||||
const getBehaviorLabel = (behavior: string): string => {
|
||||
return behavior
|
||||
.split('_')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
const isPullBehavior = (behavior: string): boolean => {
|
||||
const pullBehaviors = [
|
||||
'open_question',
|
||||
'closed_question',
|
||||
'testing_understanding',
|
||||
'summarizing',
|
||||
'bringing_in'
|
||||
];
|
||||
return pullBehaviors.includes(behavior);
|
||||
};
|
||||
|
||||
// Find transitions at specific times
|
||||
const getTransitionsAtTime = (timeRange: { start: number; end: number }) => {
|
||||
return pullPushTransitions.filter(
|
||||
t => t.time_sec >= timeRange.start && t.time_sec <= timeRange.end
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-btg-bg/50 border border-btg-primary/30 rounded-lg">
|
||||
<h2 className="text-xl font-semibold text-btg-fg mb-4">Meeting Timeline</h2>
|
||||
<p className="text-sm text-btg-fg/70 mb-4">
|
||||
Click on any utterance to highlight it. Pull behaviors are shown in green, Push behaviors in orange.
|
||||
</p>
|
||||
|
||||
<div
|
||||
ref={timelineRef}
|
||||
className="space-y-3 max-h-[600px] overflow-y-auto pr-2"
|
||||
role="log"
|
||||
aria-label="Meeting timeline"
|
||||
>
|
||||
{timeline.map((entry, index) => {
|
||||
const utterance = utterances[entry.utterance_id];
|
||||
if (!utterance) return null;
|
||||
|
||||
const isSelected = selectedId === entry.utterance_id;
|
||||
const behaviorColor = getBehaviorColor(entry.behavior);
|
||||
const isPull = isPullBehavior(entry.behavior);
|
||||
|
||||
// Check for transitions near this utterance
|
||||
const transitions = getTransitionsAtTime({
|
||||
start: entry.start_sec - 5,
|
||||
end: entry.end_sec + 5
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${entry.utterance_id}-${index}`}
|
||||
id={`utterance-${entry.utterance_id}`}
|
||||
className={`p-4 border-l-4 rounded-md transition-all cursor-pointer ${behaviorColor} ${
|
||||
isSelected ? 'ring-2 ring-btg-accent shadow-lg' : 'hover:shadow-md'
|
||||
}`}
|
||||
onClick={() => scrollToUtterance(entry.utterance_id)}
|
||||
role="article"
|
||||
aria-label={`Utterance by ${entry.speaker} at ${formatTime(entry.start_sec)}`}
|
||||
tabIndex={0}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
scrollToUtterance(entry.utterance_id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-bold text-btg-fg">{entry.speaker}</span>
|
||||
<span className="text-xs px-2 py-1 rounded-full bg-btg-bg/50 text-btg-fg/70">
|
||||
{formatTime(entry.start_sec)} - {formatTime(entry.end_sec)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-xs px-2 py-1 rounded-full font-medium ${
|
||||
isPull ? 'bg-green-500/30 text-green-200' : 'bg-orange-500/30 text-orange-200'
|
||||
}`}>
|
||||
{isPull ? 'PULL' : 'PUSH'}
|
||||
</span>
|
||||
<span className="text-xs px-2 py-1 rounded-full bg-btg-primary/30 text-btg-fg">
|
||||
{getBehaviorLabel(entry.behavior)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Utterance text */}
|
||||
<p className="text-sm text-btg-fg leading-relaxed mb-2">
|
||||
"{utterance.text}"
|
||||
</p>
|
||||
|
||||
{/* Proposal info (if applicable) */}
|
||||
{(entry.proposal.build_on || !entry.proposal.appropriate_push) && (
|
||||
<div className="mt-2 p-2 bg-btg-bg/50 rounded-md text-xs space-y-1">
|
||||
{entry.proposal.build_on && (
|
||||
<div className="flex items-center gap-2 text-btg-accent">
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 1.414L10.586 9H7a1 1 0 100 2h3.586l-1.293 1.293a1 1 0 101.414 1.414l3-3a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>Builds on prior idea</span>
|
||||
</div>
|
||||
)}
|
||||
{!entry.proposal.appropriate_push && (
|
||||
<div className="flex items-center gap-2 text-btg-warn">
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>Inappropriate Push timing (lacks urgency or has high rejection risk)</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pull→Push transitions near this utterance */}
|
||||
{transitions.length > 0 && (
|
||||
<div className="mt-2 p-2 bg-btg-accent/10 border border-btg-accent/30 rounded-md text-xs">
|
||||
<div className="flex items-center gap-2 text-btg-accent font-medium mb-1">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||
</svg>
|
||||
<span>Pull→Push Transition Nearby</span>
|
||||
</div>
|
||||
{transitions.map((t, i) => (
|
||||
<div key={i} className="text-btg-fg/70 ml-6">
|
||||
{t.speaker} transitioned from {t.from_behavior} to {t.to_behavior} at {formatTime(t.time_sec)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Legend */}
|
||||
<div className="mt-4 pt-4 border-t border-btg-primary/30">
|
||||
<p className="text-xs font-semibold text-btg-fg mb-2">Legend:</p>
|
||||
<div className="flex flex-wrap gap-4 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-green-500/30 border border-green-500 rounded"></div>
|
||||
<span className="text-btg-fg/70">Pull Behavior</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-orange-500/30 border border-orange-500 rounded"></div>
|
||||
<span className="text-btg-fg/70">Push Behavior</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-btg-accent" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 1.414L10.586 9H7a1 1 0 100 2h3.586l-1.293 1.293a1 1 0 101.414 1.414l3-3a1 1 0 000-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-btg-fg/70">Builds on idea</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-btg-warn" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-btg-fg/70">Inappropriate Push</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
interface Transition {
|
||||
time_sec: number;
|
||||
from_behavior: string;
|
||||
to_behavior: string;
|
||||
speaker: string;
|
||||
}
|
||||
|
||||
interface TransitionsPanelProps {
|
||||
transitions: Transition[];
|
||||
}
|
||||
|
||||
export function TransitionsPanel({ transitions }: TransitionsPanelProps) {
|
||||
const formatTime = (seconds: number): string => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const scrollToUtterance = (timeSec: number) => {
|
||||
// Find closest utterance to this time
|
||||
// This will be handled by the timeline component
|
||||
if ((window as any).scrollToUtterance) {
|
||||
// We'll need to find the utterance ID that matches this time
|
||||
// For now, just scroll to the timeline section
|
||||
const timelineElement = document.getElementById('timeline-section');
|
||||
if (timelineElement) {
|
||||
timelineElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const formatBehavior = (behavior: string): string => {
|
||||
return behavior
|
||||
.split('_')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
};
|
||||
|
||||
if (transitions.length === 0) {
|
||||
return (
|
||||
<div className="p-6 bg-btg-bg/50 border border-btg-primary/30 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-btg-fg mb-2">Pull→Push Transitions</h3>
|
||||
<p className="text-sm text-btg-fg/70">
|
||||
No significant Pull→Push transitions detected in this meeting.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-btg-bg/50 border border-btg-primary/30 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-btg-fg mb-2">Pull→Push Transitions</h3>
|
||||
<p className="text-sm text-btg-fg/70 mb-4">
|
||||
Key moments where speakers transitioned from Pull to Push behaviors (detected within 60-second windows)
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{transitions.map((transition, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="p-4 bg-btg-accent/10 border border-btg-accent/30 rounded-md hover:bg-btg-accent/15 transition-colors cursor-pointer"
|
||||
onClick={() => scrollToUtterance(transition.time_sec)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
scrollToUtterance(transition.time_sec);
|
||||
}
|
||||
}}
|
||||
aria-label={`Transition at ${formatTime(transition.time_sec)}`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg
|
||||
className="w-5 h-5 text-btg-accent flex-shrink-0"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 7l5 5m0 0l-5 5m5-5H6"
|
||||
/>
|
||||
</svg>
|
||||
<span className="font-semibold text-btg-fg">{transition.speaker}</span>
|
||||
</div>
|
||||
<span className="text-xs px-2 py-1 rounded-full bg-btg-bg/50 text-btg-fg/70">
|
||||
{formatTime(transition.time_sec)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="ml-7 text-sm text-btg-fg">
|
||||
<span className="text-green-400">{formatBehavior(transition.from_behavior)}</span>
|
||||
<span className="text-btg-fg/50 mx-2">→</span>
|
||||
<span className="text-orange-400">{formatBehavior(transition.to_behavior)}</span>
|
||||
</div>
|
||||
|
||||
<div className="ml-7 mt-2 text-xs text-btg-fg/60">
|
||||
Click to view in timeline
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{transitions.length > 5 && (
|
||||
<div className="mt-4 text-xs text-center text-btg-fg/60">
|
||||
Showing {transitions.length} transitions
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { authAPI } from '../services/api';
|
||||
import type { User, LoginRequest, RegisterRequest } from '../types';
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||
};
|
||||
|
||||
const register = async (data: RegisterRequest) => {
|
||||
const newUser = await authAPI.register(data);
|
||||
await authAPI.register(data);
|
||||
// After registration, user needs to login separately
|
||||
setUser(null);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useState, FormEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type { FormEvent } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useState, FormEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type { FormEvent } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../../contexts/AuthContext';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
|||
import { useParams } from 'react-router-dom';
|
||||
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts';
|
||||
import { analysesAPI } from '../../services/api';
|
||||
import type { AnalysisResponse, Participant, BehaviorExample } from '../../types';
|
||||
import type { AnalysisResponse, Participant } from '../../types';
|
||||
import { PullPushGauge } from '../../components/PullPushGauge';
|
||||
|
||||
export function DashboardPage() {
|
||||
|
|
@ -63,13 +63,6 @@ export function DashboardPage() {
|
|||
return participant.name || participant.id;
|
||||
};
|
||||
|
||||
// Get speaker display from ID (for behavior examples)
|
||||
const getSpeakerNameById = (speakerId: string, speakerName?: string | null): string => {
|
||||
if (speakerName) return speakerName;
|
||||
const participant = participants.find(p => p.id === speakerId);
|
||||
return participant?.name || speakerId;
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="p-4 bg-btg-warn/20 border border-btg-warn rounded-md">
|
||||
|
|
@ -145,7 +138,7 @@ export function DashboardPage() {
|
|||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
>
|
||||
{speakingTimeData.map((entry, index) => (
|
||||
{speakingTimeData.map((_, index) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { jobsAPI } from '../../services/api';
|
||||
import type { Job } from '../../types';
|
||||
import { JobStatus } from '../../types';
|
||||
import type { Job, JobStatus } from '../../types';
|
||||
|
||||
export function ProcessingPage() {
|
||||
const { jobId } = useParams<{ jobId: string }>();
|
||||
|
|
@ -13,7 +12,7 @@ export function ProcessingPage() {
|
|||
useEffect(() => {
|
||||
if (!jobId) return;
|
||||
|
||||
let interval: NodeJS.Timeout;
|
||||
let interval: ReturnType<typeof setInterval>;
|
||||
|
||||
const checkStatus = async () => {
|
||||
try {
|
||||
|
|
@ -21,7 +20,7 @@ export function ProcessingPage() {
|
|||
setJob(jobData);
|
||||
|
||||
// If completed, navigate to dashboard
|
||||
if (jobData.status === JobStatus.COMPLETED) {
|
||||
if (jobData.status === 'completed') {
|
||||
clearInterval(interval);
|
||||
setTimeout(() => {
|
||||
navigate(`/dashboard/${jobId}`);
|
||||
|
|
@ -29,7 +28,7 @@ export function ProcessingPage() {
|
|||
}
|
||||
|
||||
// If failed, show error
|
||||
if (jobData.status === JobStatus.FAILED) {
|
||||
if (jobData.status === 'failed') {
|
||||
clearInterval(interval);
|
||||
setError(jobData.error_message || 'Analysis failed');
|
||||
}
|
||||
|
|
@ -50,12 +49,12 @@ export function ProcessingPage() {
|
|||
|
||||
const getStepStatus = (status: JobStatus) => {
|
||||
const steps = [
|
||||
{ key: 'uploaded', label: 'Upload', statuses: [JobStatus.UPLOADED, JobStatus.PROCESSING, JobStatus.COMPLETED] },
|
||||
{ key: 'processing', label: 'Analyze', statuses: [JobStatus.PROCESSING, JobStatus.COMPLETED] },
|
||||
{ key: 'completed', label: 'Render', statuses: [JobStatus.COMPLETED] },
|
||||
{ key: 'uploaded', label: 'Upload', statuses: ['uploaded' as JobStatus, 'processing' as JobStatus, 'completed' as JobStatus] },
|
||||
{ key: 'processing', label: 'Analyze', statuses: ['processing' as JobStatus, 'completed' as JobStatus] },
|
||||
{ key: 'completed', label: 'Render', statuses: ['completed' as JobStatus] },
|
||||
];
|
||||
|
||||
return steps.map((step, index) => ({
|
||||
return steps.map((step) => ({
|
||||
...step,
|
||||
isActive: step.statuses.includes(status),
|
||||
isComplete: step.statuses.includes(status) && status !== step.statuses[0],
|
||||
|
|
@ -159,12 +158,12 @@ export function ProcessingPage() {
|
|||
{/* Status message */}
|
||||
<div className="p-4 bg-btg-primary/10 border border-btg-primary/30 rounded-lg">
|
||||
<p className="text-sm text-btg-fg">
|
||||
{job.status === JobStatus.UPLOADED && 'Video uploaded successfully. Starting analysis...'}
|
||||
{job.status === JobStatus.PROCESSING && 'Analyzing video with AI. This may take several minutes...'}
|
||||
{job.status === JobStatus.COMPLETED && 'Analysis complete! Redirecting to dashboard...'}
|
||||
{job.status === JobStatus.FAILED && 'Analysis failed. Please try again.'}
|
||||
{job.status === 'uploaded' && 'Video uploaded successfully. Starting analysis...'}
|
||||
{job.status === 'processing' && 'Analyzing video with AI. This may take several minutes...'}
|
||||
{job.status === 'completed' && 'Analysis complete! Redirecting to dashboard...'}
|
||||
{job.status === 'failed' && 'Analysis failed. Please try again.'}
|
||||
</p>
|
||||
{job.status === JobStatus.PROCESSING && (
|
||||
{job.status === 'processing' && (
|
||||
<p className="text-xs text-btg-fg/60 mt-2">
|
||||
The AI is transcribing audio, performing speaker diarization, and analyzing Rackham behaviors.
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -18,14 +18,13 @@ export interface RegisterRequest {
|
|||
}
|
||||
|
||||
// Job types
|
||||
export enum JobStatus {
|
||||
PENDING = 'pending',
|
||||
UPLOADING = 'uploading',
|
||||
UPLOADED = 'uploaded',
|
||||
PROCESSING = 'processing',
|
||||
COMPLETED = 'completed',
|
||||
FAILED = 'failed',
|
||||
}
|
||||
export type JobStatus =
|
||||
| 'pending'
|
||||
| 'uploading'
|
||||
| 'uploaded'
|
||||
| 'processing'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
|
||||
export interface Job {
|
||||
_id: string;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue