File size: 4,776 Bytes
81ff144 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | import React, { useState, useEffect } from 'react';
import { supabase } from '../services/supabase';
import { Save, CheckCircle, XCircle, ThumbsUp, ThumbsDown } from 'lucide-react';
import { motion } from 'framer-motion';
interface TaskEditorProps {
taskId: string;
onClose: () => void;
}
interface EditableTask {
id: string;
title: string;
output_data: unknown;
}
const TaskEditor: React.FC<TaskEditorProps> = ({ taskId, onClose }) => {
const [task, setTask] = useState<EditableTask | null>(null);
const [editedOutput, setEditedOutput] = useState('');
const [saving, setSaving] = useState(false);
useEffect(() => {
const fetchTask = async () => {
const { data } = await supabase
.from('tasks')
.select('id,title,output_data')
.eq('id', taskId)
.single();
if (data) {
setTask(data);
setEditedOutput(JSON.stringify(data.output_data, null, 2));
}
};
fetchTask();
}, [taskId]);
const handleSave = async () => {
setSaving(true);
try {
const parsed = JSON.parse(editedOutput);
await supabase
.from('tasks')
.update({ output_data: parsed })
.eq('id', taskId);
alert('Task updated successfully!');
} catch {
alert('Invalid JSON format');
}
setSaving(false);
};
const handleFeedback = async (rating: number) => {
await supabase.from('task_feedback').upsert({
task_id: taskId,
rating: rating
});
alert(rating === 1 ? 'Glad you liked it!' : 'Feedback recorded. We will use this to improve.');
};
const handleApprove = async () => {
await supabase.from('tasks').update({ status: 'done' }).eq('id', taskId);
onClose();
};
if (!task) return null;
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
background: 'rgba(0,0,0,0.8)', backdropFilter: 'blur(8px)',
zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem'
}}
>
<motion.div
initial={{ y: 50 }}
animate={{ y: 0 }}
className="glass-panel task-editor-panel"
style={{ width: '100%', maxWidth: '800px', maxHeight: '90vh', display: 'flex', flexDirection: 'column' }}
>
<div style={{ padding: 'var(--space-lg)', borderBottom: '1px solid var(--glass-border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<h2 style={{ fontSize: '1.5rem' }}>{task.title}</h2>
<p style={{ color: 'var(--text-dim)', fontSize: '0.9rem' }}>Review and refine agent output</p>
</div>
<button onClick={onClose}><XCircle size={24} color="var(--text-muted)" /></button>
</div>
<div style={{ flex: 1, padding: 'var(--space-lg)', overflowY: 'auto' }}>
<label style={{ display: 'block', marginBottom: 'var(--space-sm)', fontWeight: 600 }}>Raw Output (JSON)</label>
<textarea
className="task-editor-textarea"
value={editedOutput}
onChange={(e) => setEditedOutput(e.target.value)}
style={{
width: '100%', height: '400px', background: 'rgba(0,0,0,0.3)',
color: 'var(--accent)', fontFamily: 'monospace', padding: '1rem',
border: '1px solid var(--glass-border)', borderRadius: 'var(--radius-md)',
outline: 'none', resize: 'none'
}}
/>
</div>
<div style={{ padding: 'var(--space-lg)', borderTop: '1px solid var(--glass-border)', display: 'flex', gap: 'var(--space-md)', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', gap: 'var(--space-md)' }}>
<button className="btn btn-glass" style={{ padding: '0.5rem' }} onClick={() => handleFeedback(1)}>
<ThumbsUp size={18} color="var(--success)" />
</button>
<button className="btn btn-glass" style={{ padding: '0.5rem' }} onClick={() => handleFeedback(-1)}>
<ThumbsDown size={18} color="var(--danger)" />
</button>
</div>
<div style={{ display: 'flex', gap: 'var(--space-md)' }}>
<button className="btn btn-glass" onClick={handleSave} disabled={saving}>
<Save size={18} />
{saving ? 'Saving...' : 'Save Draft'}
</button>
<button className="btn btn-primary" onClick={handleApprove}>
<CheckCircle size={18} />
Approve & Finalize
</button>
</div>
</div>
</motion.div>
</motion.div>
);
};
export default TaskEditor;
|