Merge pull request #264 from rowboatlabs/fix_image_fr

moved generate_image from being attached to the workflow
This commit is contained in:
Ramnique Singh 2025-09-16 17:15:22 +05:30 committed by GitHub
commit 726559de76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 209 additions and 136 deletions

View file

@ -16,6 +16,7 @@ import { Panel } from "@/components/common/panel-common";
import { Button as CustomButton } from "@/components/ui/button";
import clsx from "clsx";
import { InputField } from "@/app/lib/components/input-field";
import { getDefaultTools } from "@/app/lib/default_tools";
import { USE_TRANSFER_CONTROL_OPTIONS } from "@/app/lib/feature_flags";
import { Info as InfoIcon } from "lucide-react";
import { useCopilot } from "../copilot/use-copilot";
@ -236,7 +237,13 @@ export function AgentConfig({
const atMentions = createAtMentions({
agents: agents,
prompts,
tools,
tools: (() => {
const defaults = getDefaultTools();
const map = new Map<string, z.infer<typeof WorkflowTool>>();
for (const t of tools) map.set(t.name, t);
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t as any);
return Array.from(map.values());
})(),
pipelines: agent.type === "pipeline" ? [] : (workflow.pipelines || []), // Pipeline agents can't reference pipelines
currentAgentName: agent.name,
currentAgent: agent

View file

@ -6,7 +6,7 @@ import { DataSource } from "@/src/entities/models/data-source";
import { WithStringId } from "../../../lib/types/types";
import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@heroui/react";
import { useRef, useEffect, useState } from "react";
import { EllipsisVerticalIcon, ImportIcon, PlusIcon, Brain, Boxes, Wrench, PenLine, Library, ChevronDown, ChevronRight, ServerIcon, Component, ScrollText, GripVertical, Users, Cog, CheckCircle2, LinkIcon, UnlinkIcon, MoreVertical, Eye, Trash2, AlertTriangle, Circle, Database } from "lucide-react";
import { EllipsisVerticalIcon, ImportIcon, PlusIcon, Brain, Boxes, Wrench, PenLine, Library, ChevronDown, ChevronRight, ServerIcon, Component, ScrollText, GripVertical, Users, Cog, CheckCircle2, LinkIcon, UnlinkIcon, MoreVertical, Eye, Trash2, AlertTriangle, Circle, Database, Image as ImageIcon } from "lucide-react";
import { Tooltip } from "@heroui/react";
import { DndContext, DragEndEvent, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
@ -20,6 +20,7 @@ import { ServerLogo } from '../tools/components/MCPServersCommon';
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
import { ToolsModal } from './components/ToolsModal';
import { DataSourcesModal } from './components/DataSourcesModal';
import { getDefaultTools } from "@/app/lib/default_tools";
import { DataSourceIcon } from '../../../lib/components/datasource-icon';
import { deleteDataSource } from '../../../actions/data-source.actions';
import { ToolkitAuthModal } from '../tools/components/ToolkitAuthModal';
@ -939,97 +940,121 @@ export const EntityList = forwardRef<
{expandedPanels.tools && (
<div className="h-full overflow-y-auto">
<div className="p-2">
{tools.length > 0 ? (
<div className="space-y-1">
{/* Group tools by server */}
{(() => {
// Get custom tools (non-MCP tools)
const customTools = tools.filter(tool => !tool.isMcp && !tool.isComposio);
// Group MCP tools by server
const serverTools = tools.reduce((acc, tool) => {
if (tool.isMcp && tool.mcpServerName) {
if (!acc[tool.mcpServerName]) {
acc[tool.mcpServerName] = [];
}
acc[tool.mcpServerName].push(tool);
}
return acc;
}, {} as Record<string, typeof tools>);
{(() => {
// Merge workflow tools with default library tools (unique by name)
const defaults = getDefaultTools();
const toolMap = new Map<string, z.infer<typeof WorkflowTool>>();
for (const t of tools) toolMap.set(t.name, t);
for (const t of defaults) if (!toolMap.has(t.name)) toolMap.set(t.name, t as any);
const toolsForDisplay = Array.from(toolMap.values());
return (
<>
{/* Show composio cards - ordered by status */}
{Object.values(composioTools)
.map((card) => (
<ComposioCard
key={card.slug}
card={card}
selectedEntity={selectedEntity}
onSelectTool={handleToolSelection}
onDeleteTool={onDeleteTool}
selectedRef={selectedRef}
projectConfig={projectConfig}
projectId={projectId}
workflow={workflow}
onProjectToolsUpdated={onProjectToolsUpdated}
setSelectedToolkitSlug={setSelectedToolkitSlug}
setShowToolsModal={setShowToolsModal}
/>
))}
if (toolsForDisplay.length > 0) {
return (
<div className="space-y-1">
{/* Group tools by server */}
{(() => {
// Get custom tools (non-MCP, non-Composio)
const customTools = toolsForDisplay.filter(tool => !tool.isMcp && !tool.isComposio);
{/* Show MCP server cards */}
{Object.entries(serverTools).map(([serverName, tools]) => (
<ServerCard
key={serverName}
serverName={serverName}
tools={tools}
selectedEntity={selectedEntity}
onSelectTool={handleToolSelection}
onDeleteTool={onDeleteTool}
selectedRef={selectedRef}
/>
))}
// Group MCP tools by server
const serverTools = toolsForDisplay.reduce((acc, tool) => {
if (tool.isMcp && tool.mcpServerName) {
if (!acc[tool.mcpServerName]) {
acc[tool.mcpServerName] = [];
}
acc[tool.mcpServerName].push(tool);
}
return acc;
}, {} as Record<string, typeof toolsForDisplay>);
{/* Show custom tools */}
{customTools.length > 0 && (
<div className="mt-2">
{customTools.map((tool, index) => (
<div
key={`custom-tool-${index}`}
className={clsx(
"flex items-center gap-2 px-3 py-2 rounded cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800",
selectedEntity?.type === "tool" && selectedEntity.name === tool.name && "bg-indigo-50 dark:bg-indigo-950/30"
return (
<>
{/* Show composio cards - ordered by status */}
{Object.values(composioTools)
.map((card) => (
<ComposioCard
key={card.slug}
card={card}
selectedEntity={selectedEntity}
onSelectTool={handleToolSelection}
onDeleteTool={onDeleteTool}
selectedRef={selectedRef}
projectConfig={projectConfig}
projectId={projectId}
workflow={workflow}
onProjectToolsUpdated={onProjectToolsUpdated}
setSelectedToolkitSlug={setSelectedToolkitSlug}
setShowToolsModal={setShowToolsModal}
/>
))}
{/* Show MCP server cards */}
{Object.entries(serverTools).map(([serverName, tools]) => (
<ServerCard
key={serverName}
serverName={serverName}
tools={tools}
selectedEntity={selectedEntity}
onSelectTool={handleToolSelection}
onDeleteTool={onDeleteTool}
selectedRef={selectedRef}
/>
))}
{/* Show custom tools, including default library tools (read-only) */}
{customTools.length > 0 && (
<div className="mt-2">
{customTools.map((tool, index) => (
<div
key={`custom-tool-${index}`}
className={clsx(
"flex items-center gap-2 px-3 py-2 rounded cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800",
selectedEntity?.type === "tool" && selectedEntity.name === tool.name && "bg-indigo-50 dark:bg-indigo-950/30",
tool.isLibrary ? "cursor-default" : ""
)}
onClick={() => { if (!tool.isLibrary) handleToolSelection(tool.name); }}
>
{tool.isGeminiImage ? (
<ImageIcon className="w-4 h-4 text-blue-600/70 dark:text-blue-500/70" />
) : (
<Boxes className="w-4 h-4 text-blue-600/70 dark:text-blue-500/70" />
)}
<span className={clsx(
"flex-1 text-xs whitespace-normal break-words",
// Match font styling to other tools even if read-only
"text-zinc-900 dark:text-zinc-100"
)}>{tool.name}</span>
{tool.mockTool && (
<span className="ml-2 px-1 py-0 rounded bg-purple-50 text-purple-400 dark:bg-purple-900/40 dark:text-purple-200 text-[11px] font-normal align-middle">Mocked</span>
)}
{!tool.isLibrary && (
<Tooltip content="Remove tool" size="sm" delay={500}>
<button
className="ml-1 p-1 pr-2 rounded hover:bg-red-100 dark:hover:bg-red-900 flex items-center"
onClick={e => { e.stopPropagation(); onDeleteTool(tool.name); }}
>
<Trash2 className="w-3 h-3 text-red-500" />
</button>
</Tooltip>
)}
</div>
))}
</div>
)}
onClick={() => handleToolSelection(tool.name)}
>
<Boxes className="w-4 h-4 text-blue-600/70 dark:text-blue-500/70" />
<span className="flex-1 text-xs text-zinc-900 dark:text-zinc-100 whitespace-normal break-words">{tool.name}</span>
{tool.mockTool && (
<span className="ml-2 px-1 py-0 rounded bg-purple-50 text-purple-400 dark:bg-purple-900/40 dark:text-purple-200 text-[11px] font-normal align-middle">Mocked</span>
)}
<Tooltip content="Remove tool" size="sm" delay={500}>
<button
className="ml-1 p-1 pr-2 rounded hover:bg-red-100 dark:hover:bg-red-900 flex items-center"
onClick={e => { e.stopPropagation(); onDeleteTool(tool.name); }}
>
<Trash2 className="w-3 h-3 text-red-500" />
</button>
</Tooltip>
</div>
))}
</div>
)}
</>
);
})()}
</div>
) : (
<EmptyState
entity="tools"
hasFilteredItems={false}
/>
)}
</>
);
})()}
</div>
);
}
return (
<EmptyState
entity="tools"
hasFilteredItems={false}
/>
);
})()}
</div>
</div>
)}
@ -1627,7 +1652,8 @@ const ComposioCard = ({
<button
className={clsx(
"flex-1 flex items-center gap-2 text-sm text-left bg-transparent border-none p-0 m-0",
tool.isLibrary ? "text-zinc-400 dark:text-zinc-600" : "text-zinc-900 dark:text-zinc-100"
// Use same font styling for library tools; keep disabled state only
"text-zinc-900 dark:text-zinc-100"
)}
onClick={() => onSelectTool(tool.name)}
disabled={tool.isLibrary}

View file

@ -38,6 +38,7 @@ import { Panel } from "@/components/common/panel-common";
import { Button as CustomButton } from "@/components/ui/button";
import { InputField } from "@/app/lib/components/input-field";
import { getDefaultTools } from "@/app/lib/default_tools";
import { VoiceSection } from "../config/components/voice";
import { TopBar } from "./components/TopBar";
@ -2207,7 +2208,14 @@ export function WorkflowEditor({
usedAgentNames={new Set(state.present.workflow.agents.filter((agent) => agent.name !== state.present.selection!.name).map((agent) => agent.name))}
usedPipelineNames={new Set((state.present.workflow.pipelines || []).map((pipeline) => pipeline.name))}
agents={state.present.workflow.agents}
tools={state.present.workflow.tools}
tools={(() => {
const { tools } = state.present.workflow;
const defaults = getDefaultTools();
const map = new Map<string, any>();
for (const t of tools) map.set(t.name, t);
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
return Array.from(map.values());
})()}
prompts={state.present.workflow.prompts}
dataSources={dataSources}
handleUpdate={(update) => { dispatchGuarded({ type: "update_agent", name: state.present.selection!.name, agent: update }); }}
@ -2235,7 +2243,14 @@ export function WorkflowEditor({
key={`overlay-${state.present.selection.name}-${configKey}`}
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
agents={state.present.workflow.agents}
tools={state.present.workflow.tools}
tools={(() => {
const { tools } = state.present.workflow;
const defaults = getDefaultTools();
const map = new Map<string, any>();
for (const t of tools) map.set(t.name, t);
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
return Array.from(map.values());
})()}
prompts={state.present.workflow.prompts}
usedPromptNames={new Set(state.present.workflow.prompts.filter((prompt) => prompt.name !== state.present.selection!.name).map((prompt) => prompt.name))}
handleUpdate={(update) => { dispatchGuarded({ type: "update_prompt", name: state.present.selection!.name, prompt: update }); }}
@ -2313,7 +2328,14 @@ export function WorkflowEditor({
usedAgentNames={new Set(state.present.workflow.agents.filter((agent) => agent.name !== state.present.selection!.name).map((agent) => agent.name))}
usedPipelineNames={new Set((state.present.workflow.pipelines || []).map((pipeline) => pipeline.name))}
agents={state.present.workflow.agents}
tools={state.present.workflow.tools}
tools={(() => {
const { tools } = state.present.workflow;
const defaults = getDefaultTools();
const map = new Map<string, any>();
for (const t of tools) map.set(t.name, t);
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
return Array.from(map.values());
})()}
prompts={state.present.workflow.prompts}
dataSources={dataSources}
handleUpdate={(update) => { dispatchGuarded({ type: "update_agent", name: state.present.selection!.name, agent: update }); }}
@ -2341,7 +2363,14 @@ export function WorkflowEditor({
key={`overlay2-${state.present.selection.name}-${configKey}`}
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
agents={state.present.workflow.agents}
tools={state.present.workflow.tools}
tools={(() => {
const { tools } = state.present.workflow;
const defaults = getDefaultTools();
const map = new Map<string, any>();
for (const t of tools) map.set(t.name, t);
for (const t of defaults) if (!map.has(t.name)) map.set(t.name, t);
return Array.from(map.values());
})()}
prompts={state.present.workflow.prompts}
usedPromptNames={new Set(state.present.workflow.prompts.filter((prompt) => prompt.name !== state.present.selection!.name).map((prompt) => prompt.name))}
handleUpdate={(update) => { dispatchGuarded({ type: "update_prompt", name: state.present.selection!.name, prompt: update }); }}