147 lines
4.7 KiB
TypeScript
147 lines
4.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useMutation, useQuery } from 'convex/react';
|
|
import { toast } from 'sonner';
|
|
|
|
import type { Id } from '@spoon/backend/convex/_generated/dataModel.js';
|
|
import { api } from '@spoon/backend/convex/_generated/api.js';
|
|
import {
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
Input,
|
|
Label,
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
Textarea,
|
|
} from '@spoon/ui';
|
|
|
|
const AgentsPage = () => {
|
|
const spoons = useQuery(api.spoons.listMine, {}) ?? [];
|
|
const requests = useQuery(api.agentRequests.listRecent, { limit: 50 }) ?? [];
|
|
const createRequest = useMutation(api.agentRequests.create);
|
|
const [spoonId, setSpoonId] = useState('');
|
|
const [targetBranch, setTargetBranch] = useState('');
|
|
const [prompt, setPrompt] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
if (!spoonId) {
|
|
toast.error('Choose a Spoon first.');
|
|
return;
|
|
}
|
|
setSubmitting(true);
|
|
try {
|
|
await createRequest({
|
|
spoonId: spoonId as Id<'spoons'>,
|
|
prompt,
|
|
targetBranch: targetBranch || undefined,
|
|
});
|
|
setPrompt('');
|
|
setTargetBranch('');
|
|
toast.success('Agent request queued.');
|
|
} catch (error) {
|
|
console.error(error);
|
|
toast.error('Could not queue agent request.');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<main className='space-y-6'>
|
|
<div>
|
|
<h1 className='text-3xl font-semibold tracking-normal'>Agents</h1>
|
|
<p className='text-muted-foreground mt-2'>
|
|
Queue prompt-driven work for future AI merge request automation.
|
|
</p>
|
|
</div>
|
|
<div className='grid gap-6 xl:grid-cols-[0.9fr_1.1fr]'>
|
|
<Card className='shadow-none'>
|
|
<CardHeader>
|
|
<CardTitle>Request work</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleSubmit} className='space-y-4'>
|
|
<div className='grid gap-2'>
|
|
<Label>Spoon</Label>
|
|
<Select value={spoonId} onValueChange={setSpoonId}>
|
|
<SelectTrigger className='w-full'>
|
|
<SelectValue placeholder='Choose a Spoon' />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{spoons.map((spoon) => (
|
|
<SelectItem key={spoon._id} value={spoon._id}>
|
|
{spoon.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className='grid gap-2'>
|
|
<Label htmlFor='targetBranch'>Target branch</Label>
|
|
<Input
|
|
id='targetBranch'
|
|
value={targetBranch}
|
|
placeholder='feature/my-change'
|
|
onChange={(event) => setTargetBranch(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className='grid gap-2'>
|
|
<Label htmlFor='prompt'>Prompt</Label>
|
|
<Textarea
|
|
id='prompt'
|
|
value={prompt}
|
|
required
|
|
onChange={(event) => setPrompt(event.target.value)}
|
|
/>
|
|
</div>
|
|
<Button type='submit' disabled={submitting || !spoons.length}>
|
|
{submitting ? 'Queueing...' : 'Queue request'}
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className='shadow-none'>
|
|
<CardHeader>
|
|
<CardTitle>Recent requests</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{requests.length ? (
|
|
<div className='space-y-3'>
|
|
{requests.map((request) => (
|
|
<div key={request._id} className='border-border border p-4'>
|
|
<p className='font-medium'>{request.prompt}</p>
|
|
<p className='text-muted-foreground mt-1 text-sm'>
|
|
{request.status.replaceAll('_', ' ')} ·{' '}
|
|
{(request.requestType ?? 'future_code_change').replaceAll(
|
|
'_',
|
|
' ',
|
|
)}{' '}
|
|
· {request.source ?? 'user'}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className='text-muted-foreground'>
|
|
Agent requests will appear here after you create a Spoon and
|
|
queue work.
|
|
</p>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</main>
|
|
);
|
|
};
|
|
|
|
export default AgentsPage;
|