mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-31 19:45:15 +02:00
refactor(automations): streamline model eligibility handling in automation creation
- Removed the eligibility gate for model selection in the automation creation process, allowing users to choose models directly in the builder. - Updated the `AutomationBuilderForm` to incorporate model selection logic, ensuring that selected models are validated and preserved during automation creation and editing. - Simplified the `AutomationsContent` and `AutomationNewContent` components by eliminating unnecessary eligibility checks and alerts. - Enhanced the user experience by integrating model selection directly into the automation approval process, ensuring that only billable models are used. - Refactored related tests to cover new model selection behavior and ensure proper validation of user-selected models.
This commit is contained in:
parent
fade9d1b9d
commit
9d1a01eb0c
13 changed files with 887 additions and 263 deletions
|
|
@ -5,6 +5,10 @@ import { useAtomValue } from "jotai";
|
|||
import { AlertCircle, CornerDownLeftIcon, ExternalLink, Pencil, Workflow } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
AutomationModelFields,
|
||||
type AutomationModelSelection,
|
||||
} from "@/app/dashboard/[search_space_id]/automations/components/builder/automation-model-fields";
|
||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { JsonView } from "@/components/json-view";
|
||||
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
|
||||
|
|
@ -12,6 +16,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { automationCreateRequest } from "@/contracts/types/automation.types";
|
||||
import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
|
||||
import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
|
||||
import { useAutomationEligibleModels } from "@/hooks/use-automation-eligible-models";
|
||||
import { AutomationDraftPreview } from "./automation-draft-preview";
|
||||
|
||||
const editArgsSchema = automationCreateRequest.omit({ search_space_id: true });
|
||||
|
|
@ -94,17 +99,71 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
|
|||
const effectiveArgs = pendingEdits ?? args;
|
||||
const draft = useMemo(() => extractDraft(effectiveArgs), [effectiveArgs]);
|
||||
|
||||
// Per-automation model selection. The card always supplies models (chosen
|
||||
// here, not snapshotted from the search space), so Approve dispatches an
|
||||
// `edit` decision carrying `definition.models`.
|
||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||
const eligibleModels = useAutomationEligibleModels();
|
||||
const [modelSelection, setModelSelection] = useState<AutomationModelSelection>({
|
||||
agentLlmId: 0,
|
||||
imageConfigId: 0,
|
||||
visionConfigId: 0,
|
||||
});
|
||||
// Resolve each slot during render: an explicit pick wins, else the eligible
|
||||
// default. No effect seeds async hook data into state.
|
||||
const resolvedModels = useMemo<AutomationModelSelection>(
|
||||
() => ({
|
||||
agentLlmId: modelSelection.agentLlmId || eligibleModels.llm.defaultId || 0,
|
||||
imageConfigId: modelSelection.imageConfigId || eligibleModels.image.defaultId || 0,
|
||||
visionConfigId: modelSelection.visionConfigId || eligibleModels.vision.defaultId || 0,
|
||||
}),
|
||||
[
|
||||
modelSelection,
|
||||
eligibleModels.llm.defaultId,
|
||||
eligibleModels.image.defaultId,
|
||||
eligibleModels.vision.defaultId,
|
||||
]
|
||||
);
|
||||
const modelsResolved =
|
||||
resolvedModels.agentLlmId !== 0 &&
|
||||
resolvedModels.imageConfigId !== 0 &&
|
||||
resolvedModels.visionConfigId !== 0;
|
||||
|
||||
const handleApprove = useCallback(() => {
|
||||
if (phase !== "pending" || !canApprove || isEditing) return;
|
||||
if (phase !== "pending" || !canApprove || isEditing || !modelsResolved) return;
|
||||
setProcessing();
|
||||
const baseArgs = pendingEdits ?? args;
|
||||
const baseDefinition = (baseArgs.definition ?? {}) as Record<string, unknown>;
|
||||
const mergedArgs = {
|
||||
...baseArgs,
|
||||
definition: {
|
||||
...baseDefinition,
|
||||
models: {
|
||||
agent_llm_id: resolvedModels.agentLlmId,
|
||||
image_generation_config_id: resolvedModels.imageConfigId,
|
||||
vision_llm_config_id: resolvedModels.visionConfigId,
|
||||
},
|
||||
},
|
||||
};
|
||||
onDecision({
|
||||
type: pendingEdits ? "edit" : "approve",
|
||||
type: "edit",
|
||||
edited_action: {
|
||||
name: interruptData.action_requests[0]?.name ?? "create_automation",
|
||||
args: pendingEdits ?? args,
|
||||
args: mergedArgs,
|
||||
},
|
||||
});
|
||||
}, [phase, canApprove, isEditing, setProcessing, onDecision, interruptData, args, pendingEdits]);
|
||||
}, [
|
||||
phase,
|
||||
canApprove,
|
||||
isEditing,
|
||||
modelsResolved,
|
||||
setProcessing,
|
||||
onDecision,
|
||||
interruptData,
|
||||
args,
|
||||
pendingEdits,
|
||||
resolvedModels,
|
||||
]);
|
||||
|
||||
const handleReject = useCallback(() => {
|
||||
if (phase !== "pending" || !canReject || isEditing) return;
|
||||
|
|
@ -193,10 +252,24 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) {
|
|||
|
||||
{phase === "pending" && !isEditing && (
|
||||
<>
|
||||
<div className="mx-5 h-px bg-border/50" />
|
||||
<div className="px-5 py-4">
|
||||
<p className="mb-3 text-xs font-medium text-foreground">Models</p>
|
||||
<AutomationModelFields
|
||||
searchSpaceId={Number(searchSpaceId)}
|
||||
value={resolvedModels}
|
||||
onChange={(patch) => setModelSelection((prev) => ({ ...prev, ...patch }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-5 h-px bg-border/50" />
|
||||
<div className="px-5 py-4 flex items-center gap-2 select-none">
|
||||
{canApprove && (
|
||||
<Button size="sm" className="rounded-lg gap-1.5" onClick={handleApprove}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="rounded-lg gap-1.5"
|
||||
disabled={!modelsResolved}
|
||||
onClick={handleApprove}
|
||||
>
|
||||
Approve
|
||||
<CornerDownLeftIcon className="size-3 opacity-60" />
|
||||
</Button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue