mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-22 21:48:12 -05:00
Merge remote-tracking branch 'origin/dev' into fix/frontend-chat-things
This commit is contained in:
@@ -33,7 +33,7 @@ from .models import (
|
||||
UserReadiness,
|
||||
)
|
||||
from .utils import (
|
||||
check_user_has_required_credentials,
|
||||
build_missing_credentials_from_graph,
|
||||
extract_credentials_from_schema,
|
||||
fetch_graph_from_store_slug,
|
||||
get_or_create_library_agent,
|
||||
@@ -237,15 +237,13 @@ class RunAgentTool(BaseTool):
|
||||
# Return credentials needed response with input data info
|
||||
# The UI handles credential setup automatically, so the message
|
||||
# focuses on asking about input data
|
||||
credentials = extract_credentials_from_schema(
|
||||
graph.credentials_input_schema
|
||||
requirements_creds_dict = build_missing_credentials_from_graph(
|
||||
graph, None
|
||||
)
|
||||
missing_creds_check = await check_user_has_required_credentials(
|
||||
user_id, credentials
|
||||
missing_credentials_dict = build_missing_credentials_from_graph(
|
||||
graph, graph_credentials
|
||||
)
|
||||
missing_credentials_dict = {
|
||||
c.id: c.model_dump() for c in missing_creds_check
|
||||
}
|
||||
requirements_creds_list = list(requirements_creds_dict.values())
|
||||
|
||||
return SetupRequirementsResponse(
|
||||
message=self._build_inputs_message(graph, MSG_WHAT_VALUES_TO_USE),
|
||||
@@ -259,7 +257,7 @@ class RunAgentTool(BaseTool):
|
||||
ready_to_run=False,
|
||||
),
|
||||
requirements={
|
||||
"credentials": [c.model_dump() for c in credentials],
|
||||
"credentials": requirements_creds_list,
|
||||
"inputs": self._get_inputs_list(graph.input_schema),
|
||||
"execution_modes": self._get_execution_modes(graph),
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ from .models import (
|
||||
ToolResponseBase,
|
||||
UserReadiness,
|
||||
)
|
||||
from .utils import build_missing_credentials_from_field_info
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -189,7 +190,11 @@ class RunBlockTool(BaseTool):
|
||||
|
||||
if missing_credentials:
|
||||
# Return setup requirements response with missing credentials
|
||||
missing_creds_dict = {c.id: c.model_dump() for c in missing_credentials}
|
||||
credentials_fields_info = block.input_schema.get_credentials_fields_info()
|
||||
missing_creds_dict = build_missing_credentials_from_field_info(
|
||||
credentials_fields_info, set(matched_credentials.keys())
|
||||
)
|
||||
missing_creds_list = list(missing_creds_dict.values())
|
||||
|
||||
return SetupRequirementsResponse(
|
||||
message=(
|
||||
@@ -206,7 +211,7 @@ class RunBlockTool(BaseTool):
|
||||
ready_to_run=False,
|
||||
),
|
||||
requirements={
|
||||
"credentials": [c.model_dump() for c in missing_credentials],
|
||||
"credentials": missing_creds_list,
|
||||
"inputs": self._get_inputs_list(block),
|
||||
"execution_modes": ["immediate"],
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ from backend.api.features.library import model as library_model
|
||||
from backend.api.features.store import db as store_db
|
||||
from backend.data import graph as graph_db
|
||||
from backend.data.graph import GraphModel
|
||||
from backend.data.model import CredentialsMetaInput
|
||||
from backend.data.model import CredentialsFieldInfo, CredentialsMetaInput
|
||||
from backend.integrations.creds_manager import IntegrationCredentialsManager
|
||||
from backend.util.exceptions import NotFoundError
|
||||
|
||||
@@ -89,6 +89,59 @@ def extract_credentials_from_schema(
|
||||
return credentials
|
||||
|
||||
|
||||
def _serialize_missing_credential(
|
||||
field_key: str, field_info: CredentialsFieldInfo
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Convert credential field info into a serializable dict that preserves all supported
|
||||
credential types (e.g., api_key + oauth2) so the UI can offer multiple options.
|
||||
"""
|
||||
supported_types = sorted(field_info.supported_types)
|
||||
provider = next(iter(field_info.provider), "unknown")
|
||||
scopes = sorted(field_info.required_scopes or [])
|
||||
|
||||
return {
|
||||
"id": field_key,
|
||||
"title": field_key.replace("_", " ").title(),
|
||||
"provider": provider,
|
||||
"provider_name": provider.replace("_", " ").title(),
|
||||
"type": supported_types[0] if supported_types else "api_key",
|
||||
"types": supported_types,
|
||||
"scopes": scopes,
|
||||
}
|
||||
|
||||
|
||||
def build_missing_credentials_from_graph(
|
||||
graph: GraphModel, matched_credentials: dict[str, CredentialsMetaInput] | None
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build a missing_credentials mapping from a graph's aggregated credentials inputs,
|
||||
preserving all supported credential types for each field.
|
||||
"""
|
||||
matched_keys = set(matched_credentials.keys()) if matched_credentials else set()
|
||||
aggregated_fields = graph.aggregate_credentials_inputs()
|
||||
|
||||
return {
|
||||
field_key: _serialize_missing_credential(field_key, field_info)
|
||||
for field_key, (field_info, _node_fields) in aggregated_fields.items()
|
||||
if field_key not in matched_keys
|
||||
}
|
||||
|
||||
|
||||
def build_missing_credentials_from_field_info(
|
||||
credential_fields: dict[str, CredentialsFieldInfo],
|
||||
matched_keys: set[str],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build missing_credentials mapping from a simple credentials field info dictionary.
|
||||
"""
|
||||
return {
|
||||
field_key: _serialize_missing_credential(field_key, field_info)
|
||||
for field_key, field_info in credential_fields.items()
|
||||
if field_key not in matched_keys
|
||||
}
|
||||
|
||||
|
||||
def extract_credentials_as_dict(
|
||||
credentials_input_schema: dict[str, Any] | None,
|
||||
) -> dict[str, CredentialsMetaInput]:
|
||||
|
||||
@@ -5,10 +5,11 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||
import { PlayIcon, StopIcon } from "@phosphor-icons/react";
|
||||
import { CircleNotchIcon, PlayIcon, StopIcon } from "@phosphor-icons/react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import { RunInputDialog } from "../RunInputDialog/RunInputDialog";
|
||||
import { useRunGraph } from "./useRunGraph";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const RunGraph = ({ flowID }: { flowID: string | null }) => {
|
||||
const {
|
||||
@@ -24,6 +25,31 @@ export const RunGraph = ({ flowID }: { flowID: string | null }) => {
|
||||
useShallow((state) => state.isGraphRunning),
|
||||
);
|
||||
|
||||
const isLoading = isExecutingGraph || isTerminatingGraph || isSaving;
|
||||
|
||||
// Determine which icon to show with proper animation
|
||||
const renderIcon = () => {
|
||||
const iconClass = cn(
|
||||
"size-4 transition-transform duration-200 ease-out",
|
||||
!isLoading && "group-hover:scale-110",
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className={cn(iconClass, "animate-spin")}
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isGraphRunning) {
|
||||
return <StopIcon className={iconClass} weight="fill" />;
|
||||
}
|
||||
|
||||
return <PlayIcon className={iconClass} weight="fill" />;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip>
|
||||
@@ -33,18 +59,18 @@ export const RunGraph = ({ flowID }: { flowID: string | null }) => {
|
||||
variant={isGraphRunning ? "destructive" : "primary"}
|
||||
data-id={isGraphRunning ? "stop-graph-button" : "run-graph-button"}
|
||||
onClick={isGraphRunning ? handleStopGraph : handleRunGraph}
|
||||
disabled={!flowID || isExecutingGraph || isTerminatingGraph}
|
||||
loading={isExecutingGraph || isTerminatingGraph || isSaving}
|
||||
disabled={!flowID || isLoading}
|
||||
className="group"
|
||||
>
|
||||
{!isGraphRunning ? (
|
||||
<PlayIcon className="size-4" />
|
||||
) : (
|
||||
<StopIcon className="size-4" />
|
||||
)}
|
||||
{renderIcon()}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isGraphRunning ? "Stop agent" : "Run agent"}
|
||||
{isLoading
|
||||
? "Processing..."
|
||||
: isGraphRunning
|
||||
? "Stop agent"
|
||||
: "Run agent"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<RunInputDialog
|
||||
|
||||
@@ -61,63 +61,67 @@ export const RunInputDialog = ({
|
||||
isOpen,
|
||||
set: setIsOpen,
|
||||
}}
|
||||
styling={{ maxWidth: "600px", minWidth: "600px" }}
|
||||
styling={{ maxWidth: "700px", minWidth: "700px" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<div className="space-y-6 p-1" data-id="run-input-dialog-content">
|
||||
{/* Credentials Section */}
|
||||
{hasCredentials() && credentialFields.length > 0 && (
|
||||
<div data-id="run-input-credentials-section">
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Credentials
|
||||
</Text>
|
||||
<div
|
||||
className="grid grid-cols-[1fr_auto] gap-10 p-1"
|
||||
data-id="run-input-dialog-content"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Credentials Section */}
|
||||
{hasCredentials() && credentialFields.length > 0 && (
|
||||
<div data-id="run-input-credentials-section">
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Credentials
|
||||
</Text>
|
||||
</div>
|
||||
<div className="px-2" data-id="run-input-credentials-form">
|
||||
<CredentialsGroupedView
|
||||
credentialFields={credentialFields}
|
||||
requiredCredentials={requiredCredentials}
|
||||
inputCredentials={credentialValues}
|
||||
inputValues={inputValues}
|
||||
onCredentialChange={handleCredentialFieldChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2" data-id="run-input-credentials-form">
|
||||
<CredentialsGroupedView
|
||||
credentialFields={credentialFields}
|
||||
requiredCredentials={requiredCredentials}
|
||||
inputCredentials={credentialValues}
|
||||
inputValues={inputValues}
|
||||
onCredentialChange={handleCredentialFieldChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Inputs Section */}
|
||||
{hasInputs() && (
|
||||
<div data-id="run-input-inputs-section">
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Inputs
|
||||
</Text>
|
||||
{/* Inputs Section */}
|
||||
{hasInputs() && (
|
||||
<div data-id="run-input-inputs-section">
|
||||
<div className="mb-4">
|
||||
<Text variant="h4" className="text-gray-900">
|
||||
Inputs
|
||||
</Text>
|
||||
</div>
|
||||
<div data-id="run-input-inputs-form">
|
||||
<FormRenderer
|
||||
jsonSchema={inputSchema as RJSFSchema}
|
||||
handleChange={(v) => handleInputChange(v.formData)}
|
||||
uiSchema={uiSchema}
|
||||
initialValues={{}}
|
||||
formContext={{
|
||||
showHandles: false,
|
||||
size: "large",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div data-id="run-input-inputs-form">
|
||||
<FormRenderer
|
||||
jsonSchema={inputSchema as RJSFSchema}
|
||||
handleChange={(v) => handleInputChange(v.formData)}
|
||||
uiSchema={uiSchema}
|
||||
initialValues={{}}
|
||||
formContext={{
|
||||
showHandles: false,
|
||||
size: "large",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Button */}
|
||||
<div
|
||||
className="flex justify-end pt-2"
|
||||
className="flex flex-col items-end justify-start"
|
||||
data-id="run-input-actions-section"
|
||||
>
|
||||
{purpose === "run" && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="group h-fit min-w-0 gap-2"
|
||||
className="group h-fit min-w-0 gap-2 px-10"
|
||||
onClick={handleManualRun}
|
||||
loading={isExecutingGraph}
|
||||
data-id="run-input-manual-run-button"
|
||||
@@ -132,7 +136,7 @@ export const RunInputDialog = ({
|
||||
<Button
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="group h-fit min-w-0 gap-2"
|
||||
className="group h-fit min-w-0 gap-2 px-10"
|
||||
onClick={() => setOpenCronSchedulerDialog(true)}
|
||||
data-id="run-input-schedule-button"
|
||||
>
|
||||
|
||||
@@ -53,14 +53,14 @@ export const CustomControls = memo(
|
||||
const controls = [
|
||||
{
|
||||
id: "zoom-in-button",
|
||||
icon: <PlusIcon className="size-4" />,
|
||||
icon: <PlusIcon className="size-3.5 text-zinc-600" />,
|
||||
label: "Zoom In",
|
||||
onClick: () => zoomIn(),
|
||||
className: "h-10 w-10 border-none",
|
||||
},
|
||||
{
|
||||
id: "zoom-out-button",
|
||||
icon: <MinusIcon className="size-4" />,
|
||||
icon: <MinusIcon className="size-3.5 text-zinc-600" />,
|
||||
label: "Zoom Out",
|
||||
onClick: () => zoomOut(),
|
||||
className: "h-10 w-10 border-none",
|
||||
@@ -68,9 +68,9 @@ export const CustomControls = memo(
|
||||
{
|
||||
id: "tutorial-button",
|
||||
icon: isTutorialLoading ? (
|
||||
<CircleNotchIcon className="size-4 animate-spin" />
|
||||
<CircleNotchIcon className="size-3.5 animate-spin text-zinc-600" />
|
||||
) : (
|
||||
<ChalkboardIcon className="size-4" />
|
||||
<ChalkboardIcon className="size-3.5 text-zinc-600" />
|
||||
),
|
||||
label: isTutorialLoading ? "Loading Tutorial..." : "Start Tutorial",
|
||||
onClick: handleTutorialClick,
|
||||
@@ -79,7 +79,7 @@ export const CustomControls = memo(
|
||||
},
|
||||
{
|
||||
id: "fit-view-button",
|
||||
icon: <FrameCornersIcon className="size-4" />,
|
||||
icon: <FrameCornersIcon className="size-3.5 text-zinc-600" />,
|
||||
label: "Fit View",
|
||||
onClick: () => fitView({ padding: 0.2, duration: 800, maxZoom: 1 }),
|
||||
className: "h-10 w-10 border-none",
|
||||
@@ -87,9 +87,9 @@ export const CustomControls = memo(
|
||||
{
|
||||
id: "lock-button",
|
||||
icon: !isLocked ? (
|
||||
<LockOpenIcon className="size-4" />
|
||||
<LockOpenIcon className="size-3.5 text-zinc-600" />
|
||||
) : (
|
||||
<LockIcon className="size-4" />
|
||||
<LockIcon className="size-3.5 text-zinc-600" />
|
||||
),
|
||||
label: "Toggle Lock",
|
||||
onClick: () => setIsLocked(!isLocked),
|
||||
|
||||
@@ -19,6 +19,8 @@ export type CustomEdgeData = {
|
||||
beadUp?: number;
|
||||
beadDown?: number;
|
||||
beadData?: Map<string, NodeExecutionResult["status"]>;
|
||||
edgeColorClass?: string;
|
||||
edgeHexColor?: string;
|
||||
};
|
||||
|
||||
export type CustomEdge = XYEdge<CustomEdgeData, "custom">;
|
||||
@@ -36,7 +38,6 @@ const CustomEdge = ({
|
||||
selected,
|
||||
}: EdgeProps<CustomEdge>) => {
|
||||
const removeConnection = useEdgeStore((state) => state.removeEdge);
|
||||
// Subscribe to the brokenEdgeIDs map and check if this edge is broken across any node
|
||||
const isBroken = useNodeStore((state) => state.isEdgeBroken(id));
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
@@ -52,6 +53,7 @@ const CustomEdge = ({
|
||||
const isStatic = data?.isStatic ?? false;
|
||||
const beadUp = data?.beadUp ?? 0;
|
||||
const beadDown = data?.beadDown ?? 0;
|
||||
const edgeColorClass = data?.edgeColorClass;
|
||||
|
||||
const handleRemoveEdge = () => {
|
||||
removeConnection(id);
|
||||
@@ -70,7 +72,9 @@ const CustomEdge = ({
|
||||
? "!stroke-red-500 !stroke-[2px] [stroke-dasharray:4]"
|
||||
: selected
|
||||
? "stroke-zinc-800"
|
||||
: "stroke-zinc-500/50 hover:stroke-zinc-500",
|
||||
: edgeColorClass
|
||||
? cn(edgeColorClass, "opacity-70 hover:opacity-100")
|
||||
: "stroke-zinc-500/50 hover:stroke-zinc-500",
|
||||
)}
|
||||
/>
|
||||
<JSBeads
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useCallback } from "react";
|
||||
import { useNodeStore } from "../../../stores/nodeStore";
|
||||
import { useHistoryStore } from "../../../stores/historyStore";
|
||||
import { CustomEdge } from "./CustomEdge";
|
||||
import { getEdgeColorFromOutputType } from "../nodes/helpers";
|
||||
|
||||
export const useCustomEdge = () => {
|
||||
const edges = useEdgeStore((s) => s.edges);
|
||||
@@ -34,8 +35,13 @@ export const useCustomEdge = () => {
|
||||
if (exists) return;
|
||||
|
||||
const nodes = useNodeStore.getState().nodes;
|
||||
const isStatic = nodes.find((n) => n.id === conn.source)?.data
|
||||
?.staticOutput;
|
||||
const sourceNode = nodes.find((n) => n.id === conn.source);
|
||||
const isStatic = sourceNode?.data?.staticOutput;
|
||||
|
||||
const { colorClass, hexColor } = getEdgeColorFromOutputType(
|
||||
sourceNode?.data?.outputSchema,
|
||||
conn.sourceHandle,
|
||||
);
|
||||
|
||||
addEdge({
|
||||
source: conn.source,
|
||||
@@ -44,6 +50,8 @@ export const useCustomEdge = () => {
|
||||
targetHandle: conn.targetHandle,
|
||||
data: {
|
||||
isStatic,
|
||||
edgeColorClass: colorClass,
|
||||
edgeHexColor: hexColor,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -187,3 +187,38 @@ export const getTypeDisplayInfo = (schema: any) => {
|
||||
hexColor,
|
||||
};
|
||||
};
|
||||
|
||||
export function getEdgeColorFromOutputType(
|
||||
outputSchema: RJSFSchema | undefined,
|
||||
sourceHandle: string,
|
||||
): { colorClass: string; hexColor: string } {
|
||||
const defaultColor = {
|
||||
colorClass: "stroke-zinc-500/50",
|
||||
hexColor: "#6b7280",
|
||||
};
|
||||
|
||||
if (!outputSchema?.properties) return defaultColor;
|
||||
|
||||
const properties = outputSchema.properties as Record<string, unknown>;
|
||||
const handleParts = sourceHandle.split("_#_");
|
||||
let currentSchema: Record<string, unknown> = properties;
|
||||
|
||||
for (let i = 0; i < handleParts.length; i++) {
|
||||
const part = handleParts[i];
|
||||
const fieldSchema = currentSchema[part] as Record<string, unknown>;
|
||||
if (!fieldSchema) return defaultColor;
|
||||
|
||||
if (i === handleParts.length - 1) {
|
||||
const { hexColor, colorClass } = getTypeDisplayInfo(fieldSchema);
|
||||
return { colorClass: colorClass.replace("!text-", "stroke-"), hexColor };
|
||||
}
|
||||
|
||||
if (fieldSchema.properties) {
|
||||
currentSchema = fieldSchema.properties as Record<string, unknown>;
|
||||
} else {
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
// These are SVG Phosphor icons
|
||||
type IconOptions = {
|
||||
size?: number;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
const DEFAULT_SIZE = 16;
|
||||
const DEFAULT_COLOR = "#52525b"; // zinc-600
|
||||
|
||||
const iconPaths = {
|
||||
ClickIcon: `M88,24V16a8,8,0,0,1,16,0v8a8,8,0,0,1-16,0ZM16,104h8a8,8,0,0,0,0-16H16a8,8,0,0,0,0,16ZM124.42,39.16a8,8,0,0,0,10.74-3.58l8-16a8,8,0,0,0-14.31-7.16l-8,16A8,8,0,0,0,124.42,39.16Zm-96,81.69-16,8a8,8,0,0,0,7.16,14.31l16-8a8,8,0,1,0-7.16-14.31ZM219.31,184a16,16,0,0,1,0,22.63l-12.68,12.68a16,16,0,0,1-22.63,0L132.7,168,115,214.09c0,.1-.08.21-.13.32a15.83,15.83,0,0,1-14.6,9.59l-.79,0a15.83,15.83,0,0,1-14.41-11L32.8,52.92A16,16,0,0,1,52.92,32.8L213,85.07a16,16,0,0,1,1.41,29.8l-.32.13L168,132.69ZM208,195.31,156.69,144h0a16,16,0,0,1,4.93-26l.32-.14,45.95-17.64L48,48l52.2,159.86,17.65-46c0-.11.08-.22.13-.33a16,16,0,0,1,11.69-9.34,16.72,16.72,0,0,1,3-.28,16,16,0,0,1,11.3,4.69L195.31,208Z`,
|
||||
Keyboard: `M224,48H32A16,16,0,0,0,16,64V192a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V64A16,16,0,0,0,224,48Zm0,144H32V64H224V192Zm-16-64a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,128Zm0-32a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,96ZM72,160a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16h8A8,8,0,0,1,72,160Zm96,0a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,160Zm40,0a8,8,0,0,1-8,8h-8a8,8,0,0,1,0-16h8A8,8,0,0,1,208,160Z`,
|
||||
Drag: `M188,80a27.79,27.79,0,0,0-13.36,3.4,28,28,0,0,0-46.64-11A28,28,0,0,0,80,92v20H68a28,28,0,0,0-28,28v12a88,88,0,0,0,176,0V108A28,28,0,0,0,188,80Zm12,72a72,72,0,0,1-144,0V140a12,12,0,0,1,12-12H80v24a8,8,0,0,0,16,0V92a12,12,0,0,1,24,0v28a8,8,0,0,0,16,0V92a12,12,0,0,1,24,0v28a8,8,0,0,0,16,0V108a12,12,0,0,1,24,0Z`,
|
||||
};
|
||||
|
||||
function createIcon(path: string, options: IconOptions = {}): string {
|
||||
const size = options.size ?? DEFAULT_SIZE;
|
||||
const color = options.color ?? DEFAULT_COLOR;
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="${color}" viewBox="0 0 256 256"><path d="${path}"></path></svg>`;
|
||||
}
|
||||
|
||||
export const ICONS = {
|
||||
ClickIcon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256"><path d="M88,24V16a8,8,0,0,1,16,0v8a8,8,0,0,1-16,0ZM16,104h8a8,8,0,0,0,0-16H16a8,8,0,0,0,0,16ZM124.42,39.16a8,8,0,0,0,10.74-3.58l8-16a8,8,0,0,0-14.31-7.16l-8,16A8,8,0,0,0,124.42,39.16Zm-96,81.69-16,8a8,8,0,0,0,7.16,14.31l16-8a8,8,0,1,0-7.16-14.31ZM219.31,184a16,16,0,0,1,0,22.63l-12.68,12.68a16,16,0,0,1-22.63,0L132.7,168,115,214.09c0,.1-.08.21-.13.32a15.83,15.83,0,0,1-14.6,9.59l-.79,0a15.83,15.83,0,0,1-14.41-11L32.8,52.92A16,16,0,0,1,52.92,32.8L213,85.07a16,16,0,0,1,1.41,29.8l-.32.13L168,132.69ZM208,195.31,156.69,144h0a16,16,0,0,1,4.93-26l.32-.14,45.95-17.64L48,48l52.2,159.86,17.65-46c0-.11.08-.22.13-.33a16,16,0,0,1,11.69-9.34,16.72,16.72,0,0,1,3-.28,16,16,0,0,1,11.3,4.69L195.31,208Z"></path></svg>`,
|
||||
Keyboard: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256"><path d="M224,48H32A16,16,0,0,0,16,64V192a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V64A16,16,0,0,0,224,48Zm0,144H32V64H224V192Zm-16-64a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,128Zm0-32a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,96ZM72,160a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16h8A8,8,0,0,1,72,160Zm96,0a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,160Zm40,0a8,8,0,0,1-8,8h-8a8,8,0,0,1,0-16h8A8,8,0,0,1,208,160Z"></path></svg>`,
|
||||
Drag: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256"><path d="M188,80a27.79,27.79,0,0,0-13.36,3.4,28,28,0,0,0-46.64-11A28,28,0,0,0,80,92v20H68a28,28,0,0,0-28,28v12a88,88,0,0,0,176,0V108A28,28,0,0,0,188,80Zm12,72a72,72,0,0,1-144,0V140a12,12,0,0,1,12-12H80v24a8,8,0,0,0,16,0V92a12,12,0,0,1,24,0v28a8,8,0,0,0,16,0V92a12,12,0,0,1,24,0v28a8,8,0,0,0,16,0V108a12,12,0,0,1,24,0Z"></path></svg>`,
|
||||
ClickIcon: createIcon(iconPaths.ClickIcon),
|
||||
Keyboard: createIcon(iconPaths.Keyboard),
|
||||
Drag: createIcon(iconPaths.Drag),
|
||||
};
|
||||
|
||||
export function getIcon(
|
||||
name: keyof typeof iconPaths,
|
||||
options?: IconOptions,
|
||||
): string {
|
||||
return createIcon(iconPaths[name], options);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "./helpers";
|
||||
import { useNodeStore } from "../../../stores/nodeStore";
|
||||
import { useEdgeStore } from "../../../stores/edgeStore";
|
||||
import { useTutorialStore } from "../../../stores/tutorialStore";
|
||||
|
||||
let isTutorialLoading = false;
|
||||
let tutorialLoadingCallback: ((loading: boolean) => void) | null = null;
|
||||
@@ -60,12 +61,14 @@ export const startTutorial = async () => {
|
||||
handleTutorialComplete();
|
||||
removeTutorialStyles();
|
||||
clearPrefetchedBlocks();
|
||||
useTutorialStore.getState().setIsTutorialRunning(false);
|
||||
});
|
||||
|
||||
tour.on("cancel", () => {
|
||||
handleTutorialCancel(tour);
|
||||
removeTutorialStyles();
|
||||
clearPrefetchedBlocks();
|
||||
useTutorialStore.getState().setIsTutorialRunning(false);
|
||||
});
|
||||
|
||||
for (const step of tour.steps) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { FileInput } from "@/components/atoms/FileInput/FileInput";
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
import {
|
||||
Form,
|
||||
@@ -120,7 +121,7 @@ export default function LibraryUploadAgentDialog() {
|
||||
>
|
||||
{isUploading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-b-2 border-t-2 border-white"></div>
|
||||
<LoadingSpinner size="small" className="text-white" />
|
||||
<span>Uploading...</span>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -304,23 +304,34 @@ export function extractCredentialsNeeded(
|
||||
| undefined;
|
||||
if (missingCreds && Object.keys(missingCreds).length > 0) {
|
||||
const agentName = (setupInfo?.agent_name as string) || "this block";
|
||||
const credentials = Object.values(missingCreds).map((credInfo) => ({
|
||||
provider: (credInfo.provider as string) || "unknown",
|
||||
providerName:
|
||||
(credInfo.provider_name as string) ||
|
||||
(credInfo.provider as string) ||
|
||||
"Unknown Provider",
|
||||
credentialType:
|
||||
const credentials = Object.values(missingCreds).map((credInfo) => {
|
||||
// Normalize to array at boundary - prefer 'types' array, fall back to single 'type'
|
||||
const typesArray = credInfo.types as
|
||||
| Array<"api_key" | "oauth2" | "user_password" | "host_scoped">
|
||||
| undefined;
|
||||
const singleType =
|
||||
(credInfo.type as
|
||||
| "api_key"
|
||||
| "oauth2"
|
||||
| "user_password"
|
||||
| "host_scoped") || "api_key",
|
||||
title:
|
||||
(credInfo.title as string) ||
|
||||
`${(credInfo.provider_name as string) || (credInfo.provider as string)} credentials`,
|
||||
scopes: credInfo.scopes as string[] | undefined,
|
||||
}));
|
||||
| "host_scoped"
|
||||
| undefined) || "api_key";
|
||||
const credentialTypes =
|
||||
typesArray && typesArray.length > 0 ? typesArray : [singleType];
|
||||
|
||||
return {
|
||||
provider: (credInfo.provider as string) || "unknown",
|
||||
providerName:
|
||||
(credInfo.provider_name as string) ||
|
||||
(credInfo.provider as string) ||
|
||||
"Unknown Provider",
|
||||
credentialTypes,
|
||||
title:
|
||||
(credInfo.title as string) ||
|
||||
`${(credInfo.provider_name as string) || (credInfo.provider as string)} credentials`,
|
||||
scopes: credInfo.scopes as string[] | undefined,
|
||||
};
|
||||
});
|
||||
return {
|
||||
type: "credentials_needed",
|
||||
toolName,
|
||||
@@ -395,11 +406,14 @@ export function extractInputsNeeded(
|
||||
credentials.forEach((cred) => {
|
||||
const id = cred.id as string;
|
||||
if (id) {
|
||||
const credentialTypes = Array.isArray(cred.types)
|
||||
? cred.types
|
||||
: [(cred.type as string) || "api_key"];
|
||||
credentialsSchema[id] = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
credentials_provider: [cred.provider as string],
|
||||
credentials_types: [(cred.type as string) || "api_key"],
|
||||
credentials_types: credentialTypes,
|
||||
credentials_scopes: cred.scopes as string[] | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import { useChatCredentialsSetup } from "./useChatCredentialsSetup";
|
||||
export interface CredentialInfo {
|
||||
provider: string;
|
||||
providerName: string;
|
||||
credentialType: "api_key" | "oauth2" | "user_password" | "host_scoped";
|
||||
credentialTypes: Array<
|
||||
"api_key" | "oauth2" | "user_password" | "host_scoped"
|
||||
>;
|
||||
title: string;
|
||||
scopes?: string[];
|
||||
}
|
||||
@@ -30,7 +32,7 @@ function createSchemaFromCredentialInfo(
|
||||
type: "object",
|
||||
properties: {},
|
||||
credentials_provider: [credential.provider],
|
||||
credentials_types: [credential.credentialType],
|
||||
credentials_types: credential.credentialTypes,
|
||||
credentials_scopes: credential.scopes,
|
||||
discriminator: undefined,
|
||||
discriminator_mapping: undefined,
|
||||
|
||||
@@ -41,7 +41,9 @@ export type ChatMessageData =
|
||||
credentials: Array<{
|
||||
provider: string;
|
||||
providerName: string;
|
||||
credentialType: "api_key" | "oauth2" | "user_password" | "host_scoped";
|
||||
credentialTypes: Array<
|
||||
"api_key" | "oauth2" | "user_password" | "host_scoped"
|
||||
>;
|
||||
title: string;
|
||||
scopes?: string[];
|
||||
}>;
|
||||
|
||||
@@ -35,12 +35,13 @@ export const CredentialFieldTitle = (props: {
|
||||
uiOptions,
|
||||
);
|
||||
|
||||
const credentialProvider = toDisplayName(
|
||||
getCredentialProviderFromSchema(
|
||||
useNodeStore.getState().getHardCodedValues(nodeId),
|
||||
schema as BlockIOCredentialsSubSchema,
|
||||
) ?? "",
|
||||
const provider = getCredentialProviderFromSchema(
|
||||
useNodeStore.getState().getHardCodedValues(nodeId),
|
||||
schema as BlockIOCredentialsSubSchema,
|
||||
);
|
||||
const credentialProvider = provider
|
||||
? `${toDisplayName(provider)} credential`
|
||||
: "credential";
|
||||
|
||||
const updatedUiSchema = updateUiOption(uiSchema, {
|
||||
showHandles: false,
|
||||
|
||||
@@ -5,7 +5,7 @@ import isEqual from "lodash/isEqual";
|
||||
export function cleanNode(node: CustomNode) {
|
||||
return {
|
||||
id: node.id,
|
||||
position: node.position,
|
||||
// Note: position is intentionally excluded to prevent draft saves when dragging nodes
|
||||
data: {
|
||||
hardcodedValues: node.data.hardcodedValues,
|
||||
title: node.data.title,
|
||||
|
||||
Reference in New Issue
Block a user