tool-ui: route HITL imports through chat-messages slice.

This commit is contained in:
CREDO23 2026-05-09 18:31:52 +02:00
parent 97a7626179
commit a32d089199
27 changed files with 130 additions and 546 deletions

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -15,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface ConfluenceAccount { interface ConfluenceAccount {
id: number; id: number;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
type DeleteConfluencePageInterruptContext = { type DeleteConfluencePageInterruptContext = {
account?: { account?: {

View file

@ -4,13 +4,16 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
type UpdateConfluencePageInterruptContext = { type UpdateConfluencePageInterruptContext = {
account?: { account?: {

View file

@ -1,187 +0,0 @@
"use client";
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { CornerDownLeftIcon, OctagonAlert } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { useHitlPhase } from "@/hooks/use-hitl-phase";
import type { HitlDecision, InterruptResult } from "@/lib/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
/**
* Specialized HITL card for ``DoomLoopMiddleware`` interrupts. The
* backend signals these by setting ``context.permission === "doom_loop"``
* on the ``permission_ask`` interrupt.
*
* The card replaces the generic "approve/reject" framing with a
* "continue/stop" affordance that better matches the user's mental
* model: the agent is stuck repeating itself, not asking permission
* for a destructive action.
*/
function DoomLoopCard({
toolName,
args,
interruptData,
onDecision,
}: {
toolName: string;
args: Record<string, unknown>;
interruptData: InterruptResult;
onDecision: (decision: HitlDecision) => void;
}) {
const { phase, setProcessing, setRejected } = useHitlPhase(interruptData);
const context = (interruptData.context ?? {}) as Record<string, unknown>;
const threshold = typeof context.threshold === "number" ? context.threshold : 3;
const stuckTool = (typeof context.tool === "string" && context.tool) || toolName;
const recentSignatures = Array.isArray(context.recent_signatures)
? (context.recent_signatures as string[])
: [];
const displayName = stuckTool.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
const argPreview = useMemo(() => {
if (!args || Object.keys(args).length === 0) return null;
try {
const json = JSON.stringify(args, null, 2);
return json.length > 600 ? `${json.slice(0, 600)}` : json;
} catch {
return null;
}
}, [args]);
const handleContinue = useCallback(() => {
if (phase !== "pending") return;
setProcessing();
onDecision({ type: "approve" });
}, [phase, setProcessing, onDecision]);
const handleStop = useCallback(() => {
if (phase !== "pending") return;
setRejected();
onDecision({ type: "reject", message: "Doom loop: user requested stop." });
}, [phase, setRejected, onDecision]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (phase !== "pending") return;
if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
handleStop();
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [phase, handleStop]);
const isResolved = phase === "complete" || phase === "rejected";
return (
<Alert variant={phase === "rejected" ? "default" : "destructive"} className="my-4 max-w-lg">
<OctagonAlert className="size-4" />
<AlertTitle className="flex items-center gap-2">
<span>
{phase === "rejected"
? "Stopped"
: phase === "processing"
? "Continuing…"
: phase === "complete"
? "Continued"
: "I might be stuck"}
</span>
{!isResolved && (
<Badge variant="outline" className="font-mono text-[10px]">
doom-loop
</Badge>
)}
</AlertTitle>
<AlertDescription className="flex flex-col gap-3">
{phase === "processing" ? (
<TextShimmerLoader text="Resuming…" size="sm" />
) : phase === "rejected" ? (
<p className="text-xs">
I stopped retrying <span className="font-medium">{displayName}</span> as you asked.
</p>
) : phase === "complete" ? (
<p className="text-xs">
Continuing to call <span className="font-medium">{displayName}</span> as you asked.
</p>
) : (
<p className="text-xs">
I called <span className="font-medium">{displayName}</span> {threshold} times in a row
with similar arguments. Should I keep going or stop and rethink?
</p>
)}
{argPreview && phase === "pending" && (
<>
<Separator />
<div className="flex flex-col gap-1">
<p className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
Last arguments
</p>
<pre className="max-h-32 overflow-auto rounded-md bg-muted/50 p-2 text-[11px] text-foreground/80">
{argPreview}
</pre>
</div>
</>
)}
{recentSignatures.length > 0 && phase === "pending" && (
<details className="text-[11px] text-muted-foreground">
<summary className="cursor-pointer select-none">
Show repeated signatures ({recentSignatures.length})
</summary>
<ul className="mt-1 ml-4 list-disc">
{recentSignatures.map((sig) => (
<li key={sig} className="font-mono break-all">
{sig}
</li>
))}
</ul>
</details>
)}
{phase === "pending" && (
<div className="flex items-center gap-2">
<Button size="sm" variant="outline" className="rounded-lg gap-1.5" onClick={handleStop}>
Stop and rethink
<CornerDownLeftIcon className="size-3 opacity-60" />
</Button>
<Button size="sm" variant="ghost" onClick={handleContinue}>
Continue anyway
</Button>
</div>
)}
</AlertDescription>
</Alert>
);
}
export const DoomLoopApprovalToolUI: ToolCallMessagePartComponent = ({
toolName,
args,
result,
}) => {
const { dispatch } = useHitlDecision();
if (!result || !isInterruptResult(result)) return null;
return (
<DoomLoopCard
toolName={toolName}
args={args as Record<string, unknown>}
interruptData={result}
onDecision={(decision) => dispatch([decision])}
/>
);
};
export function isDoomLoopInterrupt(result: unknown): boolean {
if (!isInterruptResult(result)) return false;
const ctx = (result.context ?? {}) as Record<string, unknown>;
return ctx.permission === "doom_loop";
}

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, FileIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, FileIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -15,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface DropboxAccount { interface DropboxAccount {
id: number; id: number;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
interface DropboxAccount { interface DropboxAccount {
id: number; id: number;

View file

@ -1,263 +0,0 @@
"use client";
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { getToolDisplayName } from "@/contracts/enums/toolIcons";
import { useHitlPhase } from "@/hooks/use-hitl-phase";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import type { HitlDecision, InterruptResult } from "@/lib/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
function ParamEditor({
params,
onChange,
disabled,
}: {
params: Record<string, unknown>;
onChange: (updated: Record<string, unknown>) => void;
disabled: boolean;
}) {
const entries = Object.entries(params);
if (entries.length === 0) return null;
return (
<div className="space-y-2">
{entries.map(([key, value]) => {
const strValue = value == null ? "" : String(value);
const isLong = strValue.length > 120;
const fieldId = `hitl-param-${key}`;
return (
<div key={key} className="space-y-1">
<label htmlFor={fieldId} className="text-xs font-medium text-muted-foreground">
{key}
</label>
{isLong ? (
<Textarea
id={fieldId}
value={strValue}
disabled={disabled}
rows={3}
onChange={(e) => onChange({ ...params, [key]: e.target.value })}
className="text-xs"
/>
) : (
<Input
id={fieldId}
value={strValue}
disabled={disabled}
onChange={(e) => onChange({ ...params, [key]: e.target.value })}
className="text-xs"
/>
)}
</div>
);
})}
</div>
);
}
function GenericApprovalCard({
toolName,
args,
interruptData,
onDecision,
}: {
toolName: string;
args: Record<string, unknown>;
interruptData: InterruptResult;
onDecision: (decision: HitlDecision) => void;
}) {
const { phase, setProcessing, setRejected } = useHitlPhase(interruptData);
const [editedParams, setEditedParams] = useState<Record<string, unknown>>(args);
const [isEditing, setIsEditing] = useState(false);
const displayName = getToolDisplayName(toolName);
const mcpServer = interruptData.context?.mcp_server as string | undefined;
const toolDescription = interruptData.context?.tool_description as string | undefined;
const mcpConnectorId = interruptData.context?.mcp_connector_id as number | undefined;
const isMCPTool = mcpConnectorId != null;
const reviewConfig = interruptData.review_configs?.[0];
const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"];
const canEdit = allowedDecisions.includes("edit");
const hasChanged = useMemo(() => {
return JSON.stringify(editedParams) !== JSON.stringify(args);
}, [editedParams, args]);
const handleApprove = useCallback(() => {
if (phase !== "pending") return;
const isEdited = isEditing && hasChanged;
setProcessing();
onDecision({
type: isEdited ? "edit" : "approve",
edited_action: isEdited
? { name: interruptData.action_requests[0]?.name ?? toolName, args: editedParams }
: undefined,
});
}, [
phase,
setProcessing,
isEditing,
hasChanged,
onDecision,
interruptData,
toolName,
editedParams,
]);
const handleAlwaysAllow = useCallback(() => {
if (phase !== "pending" || !isMCPTool) return;
setProcessing();
onDecision({ type: "approve" });
connectorsApiService.trustMCPTool(mcpConnectorId, toolName).catch(() => {
toast.error(
"Failed to save 'Always Allow' preference. The tool will still require approval next time."
);
});
}, [phase, setProcessing, onDecision, isMCPTool, mcpConnectorId, toolName]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey && phase === "pending") {
handleApprove();
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [handleApprove, phase]);
return (
<div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 transition-[box-shadow] duration-300">
{/* Header */}
<div className="flex items-start justify-between px-5 pt-5 pb-4 select-none">
<div>
<p className="text-sm font-semibold text-foreground">
{phase === "rejected"
? `${displayName} — Rejected`
: phase === "processing" || phase === "complete"
? `${displayName} — Approved`
: displayName}
</p>
{phase === "processing" ? (
<TextShimmerLoader text="Executing..." size="sm" />
) : phase === "complete" ? (
<p className="text-xs text-muted-foreground mt-0.5">Action completed</p>
) : phase === "rejected" ? (
<p className="text-xs text-muted-foreground mt-0.5">Action was cancelled</p>
) : (
<p className="text-xs text-muted-foreground mt-0.5">
Requires your approval to proceed
</p>
)}
{mcpServer && (
<p className="text-[10px] text-muted-foreground/70 mt-1">
via <span className="font-medium">{mcpServer}</span>
</p>
)}
</div>
{phase === "pending" && canEdit && !isEditing && (
<Button
size="sm"
variant="ghost"
className="rounded-lg text-muted-foreground -mt-1 -mr-2"
onClick={() => setIsEditing(true)}
>
<Pencil className="size-3.5" />
Edit
</Button>
)}
</div>
{/* Description */}
{toolDescription && phase === "pending" && (
<>
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 py-3">
<p className="text-xs text-muted-foreground">{toolDescription}</p>
</div>
</>
)}
{Object.keys(args).length > 0 && (
<>
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 py-4 space-y-2">
<p className="text-xs font-medium text-muted-foreground">Inputs</p>
{phase === "pending" && isEditing ? (
<ParamEditor
params={editedParams}
onChange={setEditedParams}
disabled={phase !== "pending"}
/>
) : (
<pre className="text-xs text-foreground/80 whitespace-pre-wrap break-all bg-muted/50 rounded-lg p-3">
{JSON.stringify(args, null, 2)}
</pre>
)}
</div>
</>
)}
{/* Action buttons */}
{phase === "pending" && (
<>
<div className="mx-5 h-px bg-border/50" />
<div className="px-5 py-4 flex items-center gap-2 select-none">
{allowedDecisions.includes("approve") && (
<Button size="sm" className="rounded-lg gap-1.5" onClick={handleApprove}>
{isEditing && hasChanged ? "Approve with edits" : "Approve"}
<CornerDownLeftIcon className="size-3 opacity-60" />
</Button>
)}
{isMCPTool && (
<Button size="sm" className="rounded-lg" onClick={handleAlwaysAllow}>
Always Allow
</Button>
)}
{allowedDecisions.includes("reject") && (
<Button
size="sm"
variant="ghost"
className="rounded-lg text-muted-foreground"
onClick={() => {
setRejected();
onDecision({ type: "reject", message: "User rejected the action." });
}}
>
Reject
</Button>
)}
</div>
</>
)}
</div>
);
}
export const GenericHitlApprovalToolUI: ToolCallMessagePartComponent = ({
toolName,
args,
result,
}) => {
const { dispatch } = useHitlDecision();
if (!result || !isInterruptResult(result)) return null;
return (
<GenericApprovalCard
toolName={toolName}
args={args as Record<string, unknown>}
interruptData={result}
onDecision={(decision) => dispatch([decision])}
/>
);
};

View file

@ -4,8 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil, UserIcon, UsersIcon } from "lucide-react"; import { CornerDownLeftIcon, Pencil, UserIcon, UsersIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -16,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { ExtraField, HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface GmailAccount { interface GmailAccount {
id: number; id: number;

View file

@ -4,8 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, MailIcon, Pencil, UserIcon, UsersIcon } from "lucide-react"; import { CornerDownLeftIcon, MailIcon, Pencil, UserIcon, UsersIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -16,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { ExtraField, HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface GmailAccount { interface GmailAccount {
id: number; id: number;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
interface GmailAccount { interface GmailAccount {
id: number; id: number;

View file

@ -4,14 +4,16 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, MailIcon, Pencil, UserIcon, UsersIcon } from "lucide-react"; import { CornerDownLeftIcon, MailIcon, Pencil, UserIcon, UsersIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { ExtraField, HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface GmailAccount { interface GmailAccount {
id: number; id: number;

View file

@ -11,8 +11,6 @@ import {
UsersIcon, UsersIcon,
} from "lucide-react"; } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -23,9 +21,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { ExtraField, HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface GoogleCalendarAccount { interface GoogleCalendarAccount {
id: number; id: number;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
interface GoogleCalendarAccount { interface GoogleCalendarAccount {
id: number; id: number;

View file

@ -11,14 +11,16 @@ import {
UsersIcon, UsersIcon,
} from "lucide-react"; } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { ExtraField, HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface GoogleCalendarAccount { interface GoogleCalendarAccount {
id: number; id: number;

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, FileIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, FileIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -15,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface GoogleDriveAccount { interface GoogleDriveAccount {
id: number; id: number;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
interface GoogleDriveAccount { interface GoogleDriveAccount {
id: number; id: number;

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -15,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface JiraAccount { interface JiraAccount {
id: number; id: number;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
interface JiraAccount { interface JiraAccount {
id: number; id: number;

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@ -16,9 +15,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface JiraIssue { interface JiraIssue {
issue_id: string; issue_id: string;

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@ -17,9 +16,13 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface LinearLabel { interface LinearLabel {
id: string; id: string;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
type LinearDeleteIssueContext = { type LinearDeleteIssueContext = {
workspace?: { id: number; organization_name: string }; workspace?: { id: number; organization_name: string };

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@ -17,9 +16,13 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface LinearLabel { interface LinearLabel {
id: string; id: string;

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -15,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
type NotionCreatePageContext = { type NotionCreatePageContext = {
accounts?: Array<{ accounts?: Array<{

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
type NotionDeletePageContext = { type NotionDeletePageContext = {
account?: { account?: {

View file

@ -4,13 +4,16 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
type NotionUpdatePageContext = { type NotionUpdatePageContext = {
account?: { account?: {

View file

@ -4,7 +4,6 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, FileIcon, Pencil } from "lucide-react"; import { CornerDownLeftIcon, FileIcon, Pencil } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom";
import { PlateEditor } from "@/components/editor/plate-editor"; import { PlateEditor } from "@/components/editor/plate-editor";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -15,9 +14,13 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import {
import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; isInterruptResult,
openHitlEditPanelAtom,
useHitlDecision,
useHitlPhase,
} from "@/features/chat-messages/hitl";
interface OneDriveAccount { interface OneDriveAccount {
id: number; id: number;

View file

@ -6,9 +6,8 @@ import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { TextShimmerLoader } from "@/components/prompt-kit/loader";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl";
import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl";
import { isInterruptResult, useHitlDecision } from "@/lib/hitl";
interface OneDriveAccount { interface OneDriveAccount {
id: number; id: number;