mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-06 19:35:44 +02:00
refactor: route trigger actions via copilot helper
Keep workflow reducer synchronous by removing trigger jobs from the switch and moving job rule API calls into a dedicated helper in messages.tsx. Cache dynamic imports and guard types so Copilot Apply/Apply All handle trigger creation without touching reducer state.
This commit is contained in:
parent
651a998fbb
commit
18fee55a89
1 changed files with 91 additions and 56 deletions
|
|
@ -11,6 +11,26 @@ import { useParsedBlocks } from "../use-parsed-blocks";
|
||||||
import { validateConfigChanges } from "@/app/lib/client_utils";
|
import { validateConfigChanges } from "@/app/lib/client_utils";
|
||||||
import { PreviewModalProvider } from '../../workflow/preview-modal';
|
import { PreviewModalProvider } from '../../workflow/preview-modal';
|
||||||
|
|
||||||
|
type ScheduledJobActionsModule = typeof import('@/app/actions/scheduled-job-rules.actions');
|
||||||
|
type RecurringJobActionsModule = typeof import('@/app/actions/recurring-job-rules.actions');
|
||||||
|
|
||||||
|
let scheduledJobActionsPromise: Promise<ScheduledJobActionsModule> | null = null;
|
||||||
|
let recurringJobActionsPromise: Promise<RecurringJobActionsModule> | null = null;
|
||||||
|
|
||||||
|
function loadScheduledJobActions(): Promise<ScheduledJobActionsModule> {
|
||||||
|
if (!scheduledJobActionsPromise) {
|
||||||
|
scheduledJobActionsPromise = import('@/app/actions/scheduled-job-rules.actions');
|
||||||
|
}
|
||||||
|
return scheduledJobActionsPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadRecurringJobActions(): Promise<RecurringJobActionsModule> {
|
||||||
|
if (!recurringJobActionsPromise) {
|
||||||
|
recurringJobActionsPromise = import('@/app/actions/recurring-job-rules.actions');
|
||||||
|
}
|
||||||
|
return recurringJobActionsPromise;
|
||||||
|
}
|
||||||
|
|
||||||
const CopilotResponsePart = z.union([
|
const CopilotResponsePart = z.union([
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal('text'),
|
type: z.literal('text'),
|
||||||
|
|
@ -210,11 +230,10 @@ function AssistantMessage({
|
||||||
const allApplied = pendingCount === 0 && totalActions > 0;
|
const allApplied = pendingCount === 0 && totalActions > 0;
|
||||||
|
|
||||||
// Memoized applyAction for useCallback dependencies
|
// Memoized applyAction for useCallback dependencies
|
||||||
const applyAction = useCallback(async (action: any, actionIndex: number): Promise<boolean> => {
|
const applyAction = useCallback((action: any): boolean => {
|
||||||
if (action.action === 'create_new') {
|
if (action.action === 'create_new') {
|
||||||
switch (action.config_type) {
|
switch (action.config_type) {
|
||||||
case 'agent': {
|
case 'agent': {
|
||||||
// Prevent duplicate agent names
|
|
||||||
if (workflow.agents.some((agent: any) => agent.name === action.name)) {
|
if (workflow.agents.some((agent: any) => agent.name === action.name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +248,6 @@ function AssistantMessage({
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case 'tool': {
|
case 'tool': {
|
||||||
// Prevent duplicate tool names
|
|
||||||
if (workflow.tools.some((tool: any) => tool.name === action.name)) {
|
if (workflow.tools.some((tool: any) => tool.name === action.name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -263,44 +281,6 @@ function AssistantMessage({
|
||||||
fromCopilot: true
|
fromCopilot: true
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
case 'one_time_trigger': {
|
|
||||||
const { scheduledTime, input } = action.config_changes || {};
|
|
||||||
if (!scheduledTime || !input) {
|
|
||||||
console.error('Missing scheduledTime or input for one-time trigger', action);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const { createScheduledJobRule } = await import('@/app/actions/scheduled-job-rules.actions');
|
|
||||||
await createScheduledJobRule({
|
|
||||||
projectId,
|
|
||||||
scheduledTime,
|
|
||||||
input,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create one-time trigger', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'recurring_trigger': {
|
|
||||||
const { cron, input } = action.config_changes || {};
|
|
||||||
if (!cron || !input) {
|
|
||||||
console.error('Missing cron or input for recurring trigger', action);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const { createRecurringJobRule } = await import('@/app/actions/recurring-job-rules.actions');
|
|
||||||
await createRecurringJobRule({
|
|
||||||
projectId,
|
|
||||||
cron,
|
|
||||||
input,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create recurring trigger', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (action.action === 'edit') {
|
} else if (action.action === 'edit') {
|
||||||
switch (action.config_type) {
|
switch (action.config_type) {
|
||||||
|
|
@ -368,26 +348,77 @@ function AssistantMessage({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn('Unhandled action from Copilot applyAction', action, actionIndex);
|
console.warn('Unhandled action from Copilot applyAction', action);
|
||||||
return false;
|
return false;
|
||||||
}, [dispatch, projectId, workflow.agents, workflow.tools]);
|
}, [dispatch, workflow.agents, workflow.tools]);
|
||||||
|
|
||||||
|
const handleTriggerAction = useCallback(async (action: any): Promise<boolean> => {
|
||||||
|
if (action.action !== 'create_new') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.config_type === 'one_time_trigger') {
|
||||||
|
const { scheduledTime, input } = action.config_changes || {};
|
||||||
|
if (!scheduledTime || !input) {
|
||||||
|
console.error('Missing scheduledTime or input for one-time trigger', action);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { createScheduledJobRule } = await loadScheduledJobActions();
|
||||||
|
await createScheduledJobRule({
|
||||||
|
projectId,
|
||||||
|
scheduledTime,
|
||||||
|
input,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create one-time trigger', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.config_type === 'recurring_trigger') {
|
||||||
|
const { cron, input } = action.config_changes || {};
|
||||||
|
if (!cron || !input) {
|
||||||
|
console.error('Missing cron or input for recurring trigger', action);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { createRecurringJobRule } = await loadRecurringJobActions();
|
||||||
|
await createRecurringJobRule({
|
||||||
|
projectId,
|
||||||
|
cron,
|
||||||
|
input,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create recurring trigger', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('Unhandled trigger action from Copilot applyAction', action);
|
||||||
|
return false;
|
||||||
|
}, [projectId]);
|
||||||
|
|
||||||
// Memoized handleApplyAll for useEffect dependencies
|
// Memoized handleApplyAll for useEffect dependencies
|
||||||
const handleApplyAll = useCallback(async () => {
|
const handleApplyAll = useCallback(async () => {
|
||||||
const unapplied = parsed
|
const unapplied = parsed.reduce<Array<{ action: z.infer<typeof CopilotAssistantMessageActionPart>['content']; actionIndex: number }>>((acc, part, idx) => {
|
||||||
.map((part, idx) => ({ part, actionIndex: idx }))
|
if (part.type === 'action' && !appliedActions.has(idx)) {
|
||||||
.filter(({ part, actionIndex }) => part.type === 'action' && !appliedActions.has(actionIndex))
|
acc.push({ action: part.action, actionIndex: idx });
|
||||||
.map(({ part, actionIndex }) => ({
|
}
|
||||||
action: part.type === 'action' ? part.action : null,
|
return acc;
|
||||||
actionIndex,
|
}, []);
|
||||||
}))
|
|
||||||
.filter(({ action }) => action !== null);
|
|
||||||
|
|
||||||
const newlyApplied: number[] = [];
|
const newlyApplied: number[] = [];
|
||||||
|
|
||||||
for (const { action, actionIndex } of unapplied) {
|
for (const { action, actionIndex } of unapplied) {
|
||||||
try {
|
try {
|
||||||
const success = await applyAction(action, actionIndex);
|
const isTrigger = action.config_type === 'one_time_trigger' || action.config_type === 'recurring_trigger';
|
||||||
|
const success = isTrigger
|
||||||
|
? await handleTriggerAction(action)
|
||||||
|
: applyAction(action);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
newlyApplied.push(actionIndex);
|
newlyApplied.push(actionIndex);
|
||||||
}
|
}
|
||||||
|
|
@ -403,23 +434,27 @@ function AssistantMessage({
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [parsed, appliedActions, applyAction]);
|
}, [parsed, appliedActions, applyAction, handleTriggerAction]);
|
||||||
|
|
||||||
// Manual single apply (from card)
|
// Manual single apply (from card)
|
||||||
const handleSingleApply = useCallback(async (action: any, actionIndex: number) => {
|
const handleSingleApply = useCallback(async (action: z.infer<typeof CopilotAssistantMessageActionPart>['content'], actionIndex: number) => {
|
||||||
if (appliedActions.has(actionIndex)) {
|
if (appliedActions.has(actionIndex)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const success = await applyAction(action, actionIndex);
|
const isTrigger = action.config_type === 'one_time_trigger' || action.config_type === 'recurring_trigger';
|
||||||
|
const success = isTrigger
|
||||||
|
? await handleTriggerAction(action)
|
||||||
|
: applyAction(action);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
setAppliedActions(prev => new Set([...prev, actionIndex]));
|
setAppliedActions(prev => new Set([...prev, actionIndex]));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to apply Copilot action', action, error);
|
console.error('Failed to apply Copilot action', action, error);
|
||||||
}
|
}
|
||||||
}, [appliedActions, applyAction]);
|
}, [appliedActions, applyAction, handleTriggerAction]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue