mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-29 18:40:38 +00:00
feat: instruction + model in agent wizard, error toasts, auto-run interval agents
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
fetchLearningLog,
|
||||
triggerLearning,
|
||||
fetchAgentTraces,
|
||||
fetchManagedAgent,
|
||||
} from '../lib/api';
|
||||
import type { AgentTask, ChannelBinding, AgentTemplate, AgentMessage, ManagedAgent, LearningLogEntry, AgentTrace } from '../lib/api';
|
||||
import {
|
||||
@@ -132,9 +133,11 @@ const AVAILABLE_TOOLS = [
|
||||
];
|
||||
|
||||
interface WizardState {
|
||||
step: 1 | 2 | 3;
|
||||
step: number;
|
||||
templateId: string;
|
||||
name: string;
|
||||
instruction: string;
|
||||
model: string;
|
||||
scheduleType: string;
|
||||
scheduleValue: string;
|
||||
selectedTools: string[];
|
||||
@@ -155,6 +158,8 @@ function LaunchWizard({
|
||||
step: 1,
|
||||
templateId: '',
|
||||
name: '',
|
||||
instruction: '',
|
||||
model: '',
|
||||
scheduleType: 'manual',
|
||||
scheduleValue: '',
|
||||
selectedTools: [],
|
||||
@@ -183,6 +188,10 @@ function LaunchWizard({
|
||||
}
|
||||
|
||||
async function handleLaunch() {
|
||||
if ((wizard.scheduleType === 'cron' || wizard.scheduleType === 'interval') && !wizard.instruction.trim()) {
|
||||
toast.error('Instruction is required for scheduled agents');
|
||||
return;
|
||||
}
|
||||
if (!wizard.name.trim()) {
|
||||
toast.error('Agent name is required');
|
||||
return;
|
||||
@@ -196,12 +205,18 @@ function LaunchWizard({
|
||||
learning_enabled: wizard.learningEnabled,
|
||||
};
|
||||
if (wizard.budget) config.budget = parseFloat(wizard.budget);
|
||||
await createManagedAgent({
|
||||
if (wizard.instruction.trim()) config.instruction = wizard.instruction.trim();
|
||||
if (wizard.model) config.model = wizard.model;
|
||||
const created = await createManagedAgent({
|
||||
name: wizard.name,
|
||||
template_id: wizard.templateId || undefined,
|
||||
config,
|
||||
});
|
||||
toast.success(`Agent "${wizard.name}" launched`);
|
||||
// Auto-run first tick for interval agents
|
||||
if (wizard.scheduleType === 'interval' && created.id) {
|
||||
runManagedAgent(created.id).catch(() => {});
|
||||
}
|
||||
onLaunched();
|
||||
} catch (err) {
|
||||
toast.error('Could not create agent', {
|
||||
@@ -327,6 +342,51 @@ function LaunchWizard({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Instruction */}
|
||||
<div>
|
||||
<div className="text-xs mb-1" style={{ color: 'var(--color-text-secondary)' }}>
|
||||
What should this agent do?
|
||||
{(wizard.scheduleType === 'cron' || wizard.scheduleType === 'interval') && (
|
||||
<span style={{ color: 'var(--color-error)' }}> *</span>
|
||||
)}
|
||||
</div>
|
||||
<textarea
|
||||
value={wizard.instruction}
|
||||
onChange={(e) => update({ instruction: e.target.value })}
|
||||
placeholder="e.g. Monitor my inbox and summarize new emails every hour"
|
||||
rows={3}
|
||||
className="w-full text-sm px-3 py-2 rounded-lg outline-none resize-none"
|
||||
style={{
|
||||
background: 'var(--color-bg-secondary)',
|
||||
color: 'var(--color-text)',
|
||||
border: '1px solid var(--color-border)',
|
||||
}}
|
||||
/>
|
||||
<div className="text-[10px] mt-0.5" style={{ color: 'var(--color-text-tertiary)' }}>
|
||||
This instruction runs every tick. Tasks are optional one-off goals.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Model */}
|
||||
<div>
|
||||
<div className="text-xs mb-1" style={{ color: 'var(--color-text-secondary)' }}>Model</div>
|
||||
<select
|
||||
value={wizard.model}
|
||||
onChange={(e) => update({ model: e.target.value })}
|
||||
className="w-full text-sm px-3 py-2 rounded-lg outline-none cursor-pointer"
|
||||
style={{
|
||||
background: 'var(--color-bg-secondary)',
|
||||
color: 'var(--color-text)',
|
||||
border: '1px solid var(--color-border)',
|
||||
}}
|
||||
>
|
||||
<option value="">Server default</option>
|
||||
{useAppStore.getState().models.map((m) => (
|
||||
<option key={m.id} value={m.id}>{m.id}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
|
||||
@@ -1034,6 +1094,22 @@ export function AgentsPage() {
|
||||
const handleRun = async (id: string) => {
|
||||
await runManagedAgent(id).catch(() => {});
|
||||
await refresh();
|
||||
// Wait 3 seconds then check if the agent errored
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const agent = await fetchManagedAgent(id);
|
||||
if (agent.status === 'error') {
|
||||
toast.error(`Agent "${agent.name}" failed`, {
|
||||
description: agent.summary_memory?.replace(/^ERROR: /, '') || 'Unknown error',
|
||||
});
|
||||
useAppStore.getState().addLogEntry({
|
||||
timestamp: Date.now(), level: 'error', category: 'model',
|
||||
message: `Agent "${agent.name}" failed: ${agent.summary_memory || 'Unknown error'}`,
|
||||
});
|
||||
}
|
||||
} catch {}
|
||||
await refresh();
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const handleRecover = async (id: string) => {
|
||||
@@ -1041,6 +1117,25 @@ export function AgentsPage() {
|
||||
await refresh();
|
||||
};
|
||||
|
||||
const prevStatuses = useRef<Record<string, string>>({});
|
||||
useEffect(() => {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const agents = await fetchManagedAgents();
|
||||
for (const agent of agents) {
|
||||
const prev = prevStatuses.current[agent.id];
|
||||
if (prev && prev !== 'error' && agent.status === 'error') {
|
||||
toast.error(`Agent "${agent.name}" failed`, {
|
||||
description: agent.summary_memory?.replace(/^ERROR: /, '') || 'Unknown error',
|
||||
});
|
||||
}
|
||||
prevStatuses.current[agent.id] = agent.status;
|
||||
}
|
||||
} catch {}
|
||||
}, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center" style={{ color: 'var(--color-text-tertiary)' }}>
|
||||
|
||||
Reference in New Issue
Block a user