opengraph stuff
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import Layout from 'emailer/emails/layouts/MainLayout.jsx';
|
||||
import { Container, Text } from '@react-email/components';
|
||||
|
||||
type JobEmailData = {
|
||||
job: { type: string; completedAt?: number };
|
||||
};
|
||||
|
||||
const Email = ({ job }: JobEmailData) => {
|
||||
return (
|
||||
<Layout>
|
||||
<Container>
|
||||
<Text className="pt-4 text-2xl">Job Completed</Text>
|
||||
<Text>
|
||||
{job?.type === 'gmail-sync'
|
||||
? 'Your Google account is now completely synced.'
|
||||
: `Your ${job?.type ?? 'unknown'} job has finished successfully.`}
|
||||
</Text>
|
||||
</Container>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Email;
|
||||
@@ -0,0 +1,20 @@
|
||||
import Layout from 'emailer/emails/layouts/MainLayout.jsx';
|
||||
import { Container, Text } from '@react-email/components';
|
||||
|
||||
type JobEmailData = {
|
||||
job: { type: string; error?: string };
|
||||
};
|
||||
|
||||
const Email = ({ job }: JobEmailData) => {
|
||||
return (
|
||||
<Layout>
|
||||
<Container>
|
||||
<Text className="pt-4 text-2xl">Job Failed</Text>
|
||||
<Text>Your {job?.type ?? 'unknown'} job has failed.</Text>
|
||||
{job?.error ? <Text className="text-sm text-gray-600">{job.error}</Text> : null}
|
||||
</Container>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Email;
|
||||
@@ -16,6 +16,7 @@ export { useChatWebSocket } from './useChatWebSocket';
|
||||
export { useDataControl } from './useDataControl';
|
||||
export { useCustomSorter } from './useCustomSorter';
|
||||
|
||||
export { useJobs, useJob } from './useJobs';
|
||||
export { useAuth } from './useAuth';
|
||||
export { useForm } from './useForm';
|
||||
export { useFullscreen } from './useFullscreen';
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { Job } from 'types';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useClient } from './useClient';
|
||||
|
||||
type JobFilters = {
|
||||
lane?: string;
|
||||
type?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
function buildQuery(filters?: JobFilters) {
|
||||
const params = new URLSearchParams();
|
||||
if (filters?.lane) params.set('lane', filters.lane);
|
||||
if (filters?.type) params.set('type', filters.type);
|
||||
if (filters?.status) params.set('status', filters.status);
|
||||
const qs = params.toString();
|
||||
return qs ? `/queue/jobs?${qs}` : '/queue/jobs';
|
||||
}
|
||||
|
||||
export const useJobs = (filters?: JobFilters) => {
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: jobs = [], refetch } = useQuery<Job[]>({
|
||||
queryKey: ['jobs', filters],
|
||||
queryFn: () => client.get<Job[]>(buildQuery(filters)),
|
||||
refetchInterval: (query) => {
|
||||
const data = query.state.data;
|
||||
if (!data) return 3000;
|
||||
const hasActive = data.some((j) => j.status === 'queued' || j.status === 'running');
|
||||
return hasActive ? 3000 : false;
|
||||
},
|
||||
});
|
||||
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['jobs'] });
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (params: { lane: string; type: string; meta?: Record<string, unknown> }) =>
|
||||
client.post<Job>('/queue/jobs', params),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
|
||||
const cancelMutation = useMutation({
|
||||
mutationFn: (id: string) => client.delete<Job>(`/queue/jobs/${id}`),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
|
||||
return {
|
||||
jobs,
|
||||
refetch,
|
||||
createJob: createMutation.mutateAsync,
|
||||
cancelJob: cancelMutation.mutateAsync,
|
||||
};
|
||||
};
|
||||
|
||||
export const useJob = (id: string | null) => {
|
||||
const client = useClient();
|
||||
|
||||
const { data: job } = useQuery<Job>({
|
||||
queryKey: ['job', id],
|
||||
queryFn: () => client.get<Job>(`/queue/jobs/${id}`),
|
||||
enabled: !!id,
|
||||
refetchInterval: (query) => {
|
||||
const data = query.state.data;
|
||||
if (!data) return 2000;
|
||||
const isActive = data.status === 'queued' || data.status === 'running';
|
||||
return isActive ? 2000 : false;
|
||||
},
|
||||
});
|
||||
|
||||
return { job: job ?? null };
|
||||
};
|
||||
+19
-10
@@ -40,9 +40,10 @@ type PiMonoInnerProps = {
|
||||
cwd: { root?: string; path: string };
|
||||
initialModel: string | null;
|
||||
taskInfo: TaskInfo;
|
||||
sandboxed?: boolean;
|
||||
};
|
||||
|
||||
const PiMonoInner = ({ defaultInput, cwd, initialModel, taskInfo }: PiMonoInnerProps) => {
|
||||
const PiMonoInner = ({ defaultInput, cwd, initialModel, taskInfo, sandboxed }: PiMonoInnerProps) => {
|
||||
const [phase, setPhase] = useState<Phase>('ready');
|
||||
const chat = usePiChat(undefined, initialModel, { replaceUrl: false, taskInfo });
|
||||
const availableModels = useVisiblePiModels();
|
||||
@@ -108,7 +109,7 @@ const PiMonoInner = ({ defaultInput, cwd, initialModel, taskInfo }: PiMonoInnerP
|
||||
|
||||
const handleRun = () => {
|
||||
setPhase('running');
|
||||
chat.sendPrompt(defaultInput, undefined, undefined, cwd);
|
||||
chat.sendPrompt(defaultInput, undefined, undefined, cwd, undefined, sandboxed);
|
||||
};
|
||||
|
||||
if (phase === 'ready') {
|
||||
@@ -187,9 +188,11 @@ type TaskRunnerModalProps = {
|
||||
entryType?: 'file' | 'directory';
|
||||
cwd?: { root?: string; path: string };
|
||||
promptOverride?: string;
|
||||
description?: string;
|
||||
sandboxed?: boolean;
|
||||
};
|
||||
|
||||
export const TaskRunnerModal = ({ open, onOpenChange, task, entryName, entryFullPath, entryType, cwd = { path: '' }, promptOverride }: TaskRunnerModalProps) => {
|
||||
export const TaskRunnerModal = ({ open, onOpenChange, task, entryName, entryFullPath, entryType, cwd = { path: '' }, promptOverride, description, sandboxed }: TaskRunnerModalProps) => {
|
||||
const { settings } = useSettings();
|
||||
const taskSettings = settings.tasks;
|
||||
const entryRef = entryFullPath ?? entryName;
|
||||
@@ -209,14 +212,19 @@ export const TaskRunnerModal = ({ open, onOpenChange, task, entryName, entryFull
|
||||
style={cardStyle()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="shrink-0 flex items-center gap-3 px-5 py-3 border-b border-duck-dark/10 bg-background/60">
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-sm font-semibold text-duck-dark truncate block">{task.name}</span>
|
||||
<span className="text-xs text-duck-dark/50 truncate block">{entryName}</span>
|
||||
<div className="shrink-0 flex flex-col border-b border-duck-dark/10 bg-background/60">
|
||||
<div className="flex items-center gap-3 px-5 py-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-sm font-semibold text-duck-dark truncate block">{task.name}</span>
|
||||
<span className="text-xs text-duck-dark/50 truncate block">{entryName}</span>
|
||||
</div>
|
||||
<DialogPrimitive.Close className="p-1.5 rounded-md text-duck-dark/40 hover:text-duck-dark hover:bg-duck-dark/5 transition-colors cursor-pointer">
|
||||
<X className="h-4 w-4" />
|
||||
</DialogPrimitive.Close>
|
||||
</div>
|
||||
<DialogPrimitive.Close className="p-1.5 rounded-md text-duck-dark/40 hover:text-duck-dark hover:bg-duck-dark/5 transition-colors cursor-pointer">
|
||||
<X className="h-4 w-4" />
|
||||
</DialogPrimitive.Close>
|
||||
{description && (
|
||||
<p className="px-5 pb-3 text-xs text-duck-dark/50 dark:text-foreground/50 -mt-1">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Task Runner */}
|
||||
@@ -226,6 +234,7 @@ export const TaskRunnerModal = ({ open, onOpenChange, task, entryName, entryFull
|
||||
cwd={cwd}
|
||||
initialModel={taskSettings.defaultProvider === 'pi' ? taskSettings.defaultModel : null}
|
||||
taskInfo={taskInfo}
|
||||
sandboxed={sandboxed}
|
||||
/>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export type EmailSummary = {
|
||||
id: string;
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
date: string;
|
||||
snippet: string;
|
||||
read?: boolean;
|
||||
};
|
||||
|
||||
export type EmailMessage = EmailSummary & {
|
||||
cc?: string;
|
||||
html?: string;
|
||||
text?: string;
|
||||
attachments: Array<{ filename: string; size: number; contentType: string }>;
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
export type {} from './globals';
|
||||
export * from 'officerdb/types';
|
||||
|
||||
export type { Job, JobStep, JobStatus, JobStepStatus, JobProgress } from './queue';
|
||||
export type { EmailSummary, EmailMessage } from './email';
|
||||
|
||||
export type SelectOption = { value: number | string; label?: string; href?: string };
|
||||
|
||||
export type StateSetter<T> = React.Dispatch<React.SetStateAction<T>>;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
export type JobStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
|
||||
export type JobStepStatus = 'pending' | 'running' | 'completed' | 'failed';
|
||||
|
||||
export type JobProgress = {
|
||||
current: number;
|
||||
total: number;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export type JobStep = {
|
||||
name: string;
|
||||
status: JobStepStatus;
|
||||
startedAt?: number;
|
||||
completedAt?: number;
|
||||
error?: string;
|
||||
progress?: JobProgress;
|
||||
};
|
||||
|
||||
export type Job = {
|
||||
id: string;
|
||||
lane: string;
|
||||
type: string;
|
||||
userId: string;
|
||||
status: JobStatus;
|
||||
steps: JobStep[];
|
||||
currentStep: number;
|
||||
error?: string;
|
||||
createdAt: number;
|
||||
startedAt?: number;
|
||||
completedAt?: number;
|
||||
meta?: Record<string, unknown>;
|
||||
};
|
||||
Reference in New Issue
Block a user