Add compiling option for captioning. Added progress bar for captioning on active job widget. Track steps on caption. Added reverse proxy iframe that plugins can use to add ui functionality.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import Link from 'next/link';
|
||||
import { CgSpinner } from 'react-icons/cg';
|
||||
import useJobsList from '@/hooks/useJobsList';
|
||||
import { JobConfig } from '@/types';
|
||||
import { getTotalSteps } from '@/utils/jobs';
|
||||
|
||||
export default function ActiveJobWidget() {
|
||||
const { jobs } = useJobsList({ onlyActive: true, reloadInterval: 5000 });
|
||||
@@ -17,13 +17,11 @@ export default function ActiveJobWidget() {
|
||||
{jobs.map(job => {
|
||||
let totalSteps: number | undefined;
|
||||
try {
|
||||
const cfg: JobConfig = JSON.parse(job.job_config);
|
||||
totalSteps = cfg.config.process[0].train?.steps;
|
||||
totalSteps = getTotalSteps(job);
|
||||
} catch {
|
||||
totalSteps = undefined;
|
||||
}
|
||||
const isTrain = job.job_type === 'train';
|
||||
const pct = isTrain && totalSteps ? Math.min(100, (job.step / totalSteps) * 100) : 0;
|
||||
const pct = totalSteps ? Math.min(100, (job.step / totalSteps) * 100) : 0;
|
||||
const isRunning = job.status === 'running' || job.status === 'stopping';
|
||||
|
||||
let label = job.name;
|
||||
@@ -47,7 +45,7 @@ export default function ActiveJobWidget() {
|
||||
{isRunning && <CgSpinner className="animate-spin text-blue-400 flex-shrink-0" />}
|
||||
<span className="text-xs text-gray-100 truncate min-w-0 flex-1">{label}</span>
|
||||
</div>
|
||||
{isTrain && totalSteps ? (
|
||||
{totalSteps ? (
|
||||
<div className="mt-1.5">
|
||||
<div className="bg-gray-700 rounded-full h-1">
|
||||
<div className="bg-blue-500 h-1 rounded-full transition-all" style={{ width: `${pct}%` }} />
|
||||
|
||||
@@ -178,6 +178,11 @@ const CaptionSimpleJob: React.FC<Props> = ({ jobConfig, setJobConfig, gpuIDs, se
|
||||
checked={jobConfig.config.process[0].caption.recaption}
|
||||
onChange={value => setJobConfig(value, 'config.process[0].caption.recaption')}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Compile Models"
|
||||
checked={jobConfig.config.process[0].caption.compile || false}
|
||||
onChange={value => setJobConfig(value, 'config.process[0].caption.compile')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function JobOverview({ job }: JobOverviewProps) {
|
||||
|
||||
<div className="p-4 space-y-6 flex flex-col flex-grow">
|
||||
{/* Progress Bar */}
|
||||
{job.job_type === 'train' && (
|
||||
{totalSteps > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Progress</span>
|
||||
|
||||
43
ui/src/components/JobPlugin.tsx
Normal file
43
ui/src/components/JobPlugin.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Job } from '@prisma/client';
|
||||
import { apiClient } from '@/utils/api';
|
||||
|
||||
interface JobPluginProps {
|
||||
job: Job;
|
||||
}
|
||||
|
||||
export default function JobPlugin({ job }: JobPluginProps) {
|
||||
const [html, setHtml] = useState<string | null>(null);
|
||||
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
||||
|
||||
// An iframe src request cannot send the Authorization header the API requires,
|
||||
// so fetch the html through apiClient and load it via srcDoc instead.
|
||||
useEffect(() => {
|
||||
apiClient
|
||||
.get(`/api/jobs/${job.id}/plugin`, { responseType: 'text' })
|
||||
.then(res => {
|
||||
setHtml(res.data);
|
||||
setStatus('success');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching plugin:', error);
|
||||
setStatus('error');
|
||||
});
|
||||
}, [job.id]);
|
||||
|
||||
if (status === 'loading') {
|
||||
return <p className="p-4">Loading plugin...</p>;
|
||||
}
|
||||
if (status === 'error' || html == null) {
|
||||
return <p className="p-4">Error loading plugin</p>;
|
||||
}
|
||||
|
||||
// Sandboxed iframe keeps the plugin's scripts and styles isolated from the app.
|
||||
// allow-scripts lets its <script> tags run, but it cannot touch the parent page,
|
||||
// cookies, or storage. The plugin sizes itself to 100% of its container, so the
|
||||
// iframe must have an explicit size.
|
||||
// 80px = top bar (48px) + tab bar (32px)
|
||||
return <iframe srcDoc={html} sandbox="allow-scripts" className="block w-full h-[calc(100dvh-80px)] border-0" />;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
|
||||
import useJobsList from '@/hooks/useJobsList';
|
||||
import Link from 'next/link';
|
||||
import UniversalTable, { TableColumn } from '@/components/UniversalTable';
|
||||
import { GpuInfo, JobConfig } from '@/types';
|
||||
import { GpuInfo } from '@/types';
|
||||
import JobActionBar from './JobActionBar';
|
||||
import { Job, Queue } from '@prisma/client';
|
||||
import useQueueList from '@/hooks/useQueueList';
|
||||
@@ -11,7 +11,7 @@ import { startQueue, stopQueue } from '@/utils/queue';
|
||||
import { CgSpinner } from 'react-icons/cg';
|
||||
import useGPUInfo from '@/hooks/useGPUInfo';
|
||||
import { openConfirm } from '@/components/ConfirmModal';
|
||||
import { deleteJob, stopJob } from '@/utils/jobs';
|
||||
import { deleteJob, getTotalSteps, stopJob } from '@/utils/jobs';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
|
||||
interface JobsTableProps {
|
||||
@@ -150,11 +150,10 @@ export default function JobsTable({ onlyActive = false, job_type = null }: JobsT
|
||||
title: 'Steps',
|
||||
key: 'steps',
|
||||
render: row => {
|
||||
const jobConfig: JobConfig = JSON.parse(row.job_config);
|
||||
if (row.job_type !== 'train') {
|
||||
const totalSteps = getTotalSteps(row);
|
||||
if (!totalSteps) {
|
||||
return <></>;
|
||||
}
|
||||
const totalSteps = jobConfig.config.process[0].train?.steps;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user