mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-02 20:03:21 +02:00
Merge pull request #264 from rowboatlabs/fix_image_fr
moved generate_image from being attached to the workflow
This commit is contained in:
commit
726559de76
8 changed files with 209 additions and 136 deletions
36
apps/rowboat/app/lib/default_tools.ts
Normal file
36
apps/rowboat/app/lib/default_tools.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// Returns the list of built-in tools that should appear by default
|
||||||
|
// in the workflow editor and be usable at runtime without attaching
|
||||||
|
// them to the workflow. These are displayed as read-only library tools.
|
||||||
|
// Note: avoid importing WorkflowTool here to prevent circular deps.
|
||||||
|
// Return a structurally compatible object instead.
|
||||||
|
export function getDefaultTools(): Array<any> {
|
||||||
|
// Show built-in tools only when a public, non-secret flag is set.
|
||||||
|
// Avoids exposing real secrets in client bundles.
|
||||||
|
const hasGoogleKeyFlag = (process.env.NEXT_PUBLIC_HAS_GOOGLE_API_KEY || '').toLowerCase() === 'true';
|
||||||
|
|
||||||
|
if (!hasGoogleKeyFlag) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'Generate Image',
|
||||||
|
description:
|
||||||
|
'Generate an image using Google Gemini given a text prompt. Returns base64-encoded image data and any text parts.',
|
||||||
|
isGeminiImage: true,
|
||||||
|
isLibrary: true,
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
prompt: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Text prompt describing the image to generate',
|
||||||
|
},
|
||||||
|
modelName: { type: 'string', description: 'Optional Gemini model override' },
|
||||||
|
},
|
||||||
|
required: ['prompt'],
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ import { z } from 'zod';
|
||||||
|
|
||||||
// Provide a minimal default template to satisfy legacy code paths that
|
// Provide a minimal default template to satisfy legacy code paths that
|
||||||
// still reference `templates.default`. Real templates are DB-backed.
|
// still reference `templates.default`. Real templates are DB-backed.
|
||||||
const includeGeminiImageTool = !!process.env.GOOGLE_API_KEY;
|
|
||||||
|
|
||||||
const defaultTemplate: z.infer<typeof WorkflowTemplate> = {
|
const defaultTemplate: z.infer<typeof WorkflowTemplate> = {
|
||||||
name: 'Blank Template',
|
name: 'Blank Template',
|
||||||
|
|
@ -11,22 +10,7 @@ const defaultTemplate: z.infer<typeof WorkflowTemplate> = {
|
||||||
startAgent: "",
|
startAgent: "",
|
||||||
agents: [],
|
agents: [],
|
||||||
prompts: [],
|
prompts: [],
|
||||||
tools: includeGeminiImageTool ? [
|
tools: [],
|
||||||
{
|
|
||||||
name: "Generate Image",
|
|
||||||
description: "Generate an image using Google Gemini given a text prompt. Returns base64-encoded image data and any text parts.",
|
|
||||||
isGeminiImage: true,
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
prompt: { type: 'string', description: 'Text prompt describing the image to generate' },
|
|
||||||
modelName: { type: 'string', description: 'Optional Gemini model override' },
|
|
||||||
},
|
|
||||||
required: ['prompt'],
|
|
||||||
additionalProperties: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] : [],
|
|
||||||
pipelines: [],
|
pipelines: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||||
export const WorkflowAgent = z.object({
|
export const WorkflowAgent = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
order: z.number().int().optional(),
|
order: z.number().int().optional(),
|
||||||
|
|
@ -165,7 +166,10 @@ export function sanitizeTextWithMentions(
|
||||||
const agent = workflow.agents.find(a => a.name === entity.name);
|
const agent = workflow.agents.find(a => a.name === entity.name);
|
||||||
return agent && agent.type !== 'pipeline';
|
return agent && agent.type !== 'pipeline';
|
||||||
} else if (entity.type === 'tool') {
|
} else if (entity.type === 'tool') {
|
||||||
return workflow.tools.some(t => t.name === entity.name);
|
// Allow referencing workflow tools or default library tools
|
||||||
|
const inWorkflow = workflow.tools.some(t => t.name === entity.name);
|
||||||
|
const inDefaults = getDefaultTools().some(t => t.name === entity.name);
|
||||||
|
return inWorkflow || inDefaults;
|
||||||
} else if (entity.type === 'prompt') {
|
} else if (entity.type === 'prompt') {
|
||||||
return workflow.prompts.some(p => p.name === entity.name);
|
return workflow.prompts.some(p => p.name === entity.name);
|
||||||
} else if (entity.type === 'pipeline') {
|
} else if (entity.type === 'pipeline') {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { Panel } from "@/components/common/panel-common";
|
||||||
import { Button as CustomButton } from "@/components/ui/button";
|
import { Button as CustomButton } from "@/components/ui/button";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { InputField } from "@/app/lib/components/input-field";
|
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 { USE_TRANSFER_CONTROL_OPTIONS } from "@/app/lib/feature_flags";
|
||||||
import { Info as InfoIcon } from "lucide-react";
|
import { Info as InfoIcon } from "lucide-react";
|
||||||
import { useCopilot } from "../copilot/use-copilot";
|
import { useCopilot } from "../copilot/use-copilot";
|
||||||
|
|
@ -236,7 +237,13 @@ export function AgentConfig({
|
||||||
const atMentions = createAtMentions({
|
const atMentions = createAtMentions({
|
||||||
agents: agents,
|
agents: agents,
|
||||||
prompts,
|
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
|
pipelines: agent.type === "pipeline" ? [] : (workflow.pipelines || []), // Pipeline agents can't reference pipelines
|
||||||
currentAgentName: agent.name,
|
currentAgentName: agent.name,
|
||||||
currentAgent: agent
|
currentAgent: agent
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { DataSource } from "@/src/entities/models/data-source";
|
||||||
import { WithStringId } from "../../../lib/types/types";
|
import { WithStringId } from "../../../lib/types/types";
|
||||||
import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@heroui/react";
|
import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@heroui/react";
|
||||||
import { useRef, useEffect, useState } from "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 { Tooltip } from "@heroui/react";
|
||||||
import { DndContext, DragEndEvent, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
import { DndContext, DragEndEvent, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||||
import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
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 { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
|
||||||
import { ToolsModal } from './components/ToolsModal';
|
import { ToolsModal } from './components/ToolsModal';
|
||||||
import { DataSourcesModal } from './components/DataSourcesModal';
|
import { DataSourcesModal } from './components/DataSourcesModal';
|
||||||
|
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||||
import { DataSourceIcon } from '../../../lib/components/datasource-icon';
|
import { DataSourceIcon } from '../../../lib/components/datasource-icon';
|
||||||
import { deleteDataSource } from '../../../actions/data-source.actions';
|
import { deleteDataSource } from '../../../actions/data-source.actions';
|
||||||
import { ToolkitAuthModal } from '../tools/components/ToolkitAuthModal';
|
import { ToolkitAuthModal } from '../tools/components/ToolkitAuthModal';
|
||||||
|
|
@ -939,97 +940,121 @@ export const EntityList = forwardRef<
|
||||||
{expandedPanels.tools && (
|
{expandedPanels.tools && (
|
||||||
<div className="h-full overflow-y-auto">
|
<div className="h-full overflow-y-auto">
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
{tools.length > 0 ? (
|
{(() => {
|
||||||
<div className="space-y-1">
|
// Merge workflow tools with default library tools (unique by name)
|
||||||
{/* Group tools by server */}
|
const defaults = getDefaultTools();
|
||||||
{(() => {
|
const toolMap = new Map<string, z.infer<typeof WorkflowTool>>();
|
||||||
// Get custom tools (non-MCP tools)
|
for (const t of tools) toolMap.set(t.name, t);
|
||||||
const customTools = tools.filter(tool => !tool.isMcp && !tool.isComposio);
|
for (const t of defaults) if (!toolMap.has(t.name)) toolMap.set(t.name, t as any);
|
||||||
|
const toolsForDisplay = Array.from(toolMap.values());
|
||||||
// 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>);
|
|
||||||
|
|
||||||
return (
|
if (toolsForDisplay.length > 0) {
|
||||||
<>
|
return (
|
||||||
{/* Show composio cards - ordered by status */}
|
<div className="space-y-1">
|
||||||
{Object.values(composioTools)
|
{/* Group tools by server */}
|
||||||
.map((card) => (
|
{(() => {
|
||||||
<ComposioCard
|
// Get custom tools (non-MCP, non-Composio)
|
||||||
key={card.slug}
|
const customTools = toolsForDisplay.filter(tool => !tool.isMcp && !tool.isComposio);
|
||||||
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 */}
|
// Group MCP tools by server
|
||||||
{Object.entries(serverTools).map(([serverName, tools]) => (
|
const serverTools = toolsForDisplay.reduce((acc, tool) => {
|
||||||
<ServerCard
|
if (tool.isMcp && tool.mcpServerName) {
|
||||||
key={serverName}
|
if (!acc[tool.mcpServerName]) {
|
||||||
serverName={serverName}
|
acc[tool.mcpServerName] = [];
|
||||||
tools={tools}
|
}
|
||||||
selectedEntity={selectedEntity}
|
acc[tool.mcpServerName].push(tool);
|
||||||
onSelectTool={handleToolSelection}
|
}
|
||||||
onDeleteTool={onDeleteTool}
|
return acc;
|
||||||
selectedRef={selectedRef}
|
}, {} as Record<string, typeof toolsForDisplay>);
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Show custom tools */}
|
return (
|
||||||
{customTools.length > 0 && (
|
<>
|
||||||
<div className="mt-2">
|
{/* Show composio cards - ordered by status */}
|
||||||
{customTools.map((tool, index) => (
|
{Object.values(composioTools)
|
||||||
<div
|
.map((card) => (
|
||||||
key={`custom-tool-${index}`}
|
<ComposioCard
|
||||||
className={clsx(
|
key={card.slug}
|
||||||
"flex items-center gap-2 px-3 py-2 rounded cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-800",
|
card={card}
|
||||||
selectedEntity?.type === "tool" && selectedEntity.name === tool.name && "bg-indigo-50 dark:bg-indigo-950/30"
|
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>
|
</div>
|
||||||
{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}>
|
return (
|
||||||
<button
|
<EmptyState
|
||||||
className="ml-1 p-1 pr-2 rounded hover:bg-red-100 dark:hover:bg-red-900 flex items-center"
|
entity="tools"
|
||||||
onClick={e => { e.stopPropagation(); onDeleteTool(tool.name); }}
|
hasFilteredItems={false}
|
||||||
>
|
/>
|
||||||
<Trash2 className="w-3 h-3 text-red-500" />
|
);
|
||||||
</button>
|
})()}
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<EmptyState
|
|
||||||
entity="tools"
|
|
||||||
hasFilteredItems={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1627,7 +1652,8 @@ const ComposioCard = ({
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex-1 flex items-center gap-2 text-sm text-left bg-transparent border-none p-0 m-0",
|
"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)}
|
onClick={() => onSelectTool(tool.name)}
|
||||||
disabled={tool.isLibrary}
|
disabled={tool.isLibrary}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import { Panel } from "@/components/common/panel-common";
|
||||||
import { Button as CustomButton } from "@/components/ui/button";
|
import { Button as CustomButton } from "@/components/ui/button";
|
||||||
|
|
||||||
import { InputField } from "@/app/lib/components/input-field";
|
import { InputField } from "@/app/lib/components/input-field";
|
||||||
|
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||||
import { VoiceSection } from "../config/components/voice";
|
import { VoiceSection } from "../config/components/voice";
|
||||||
import { TopBar } from "./components/TopBar";
|
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))}
|
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))}
|
usedPipelineNames={new Set((state.present.workflow.pipelines || []).map((pipeline) => pipeline.name))}
|
||||||
agents={state.present.workflow.agents}
|
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}
|
prompts={state.present.workflow.prompts}
|
||||||
dataSources={dataSources}
|
dataSources={dataSources}
|
||||||
handleUpdate={(update) => { dispatchGuarded({ type: "update_agent", name: state.present.selection!.name, agent: update }); }}
|
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}`}
|
key={`overlay-${state.present.selection.name}-${configKey}`}
|
||||||
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
|
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
|
||||||
agents={state.present.workflow.agents}
|
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}
|
prompts={state.present.workflow.prompts}
|
||||||
usedPromptNames={new Set(state.present.workflow.prompts.filter((prompt) => prompt.name !== state.present.selection!.name).map((prompt) => prompt.name))}
|
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 }); }}
|
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))}
|
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))}
|
usedPipelineNames={new Set((state.present.workflow.pipelines || []).map((pipeline) => pipeline.name))}
|
||||||
agents={state.present.workflow.agents}
|
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}
|
prompts={state.present.workflow.prompts}
|
||||||
dataSources={dataSources}
|
dataSources={dataSources}
|
||||||
handleUpdate={(update) => { dispatchGuarded({ type: "update_agent", name: state.present.selection!.name, agent: update }); }}
|
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}`}
|
key={`overlay2-${state.present.selection.name}-${configKey}`}
|
||||||
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
|
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
|
||||||
agents={state.present.workflow.agents}
|
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}
|
prompts={state.present.workflow.prompts}
|
||||||
usedPromptNames={new Set(state.present.workflow.prompts.filter((prompt) => prompt.name !== state.present.selection!.name).map((prompt) => prompt.name))}
|
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 }); }}
|
handleUpdate={(update) => { dispatchGuarded({ type: "update_prompt", name: state.present.selection!.name, prompt: update }); }}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import crypto from "crypto";
|
||||||
// Internal dependencies
|
// Internal dependencies
|
||||||
import { createTools, createRagTool } from "./agent-tools";
|
import { createTools, createRagTool } from "./agent-tools";
|
||||||
import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPipeline, WorkflowPrompt, WorkflowTool } from "@/app/lib/types/workflow_types";
|
import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPipeline, WorkflowPrompt, WorkflowTool } from "@/app/lib/types/workflow_types";
|
||||||
|
import { getDefaultTools } from "@/app/lib/default_tools";
|
||||||
import { CHILD_TRANSFER_RELATED_INSTRUCTIONS, CONVERSATION_TYPE_INSTRUCTIONS, PIPELINE_TYPE_INSTRUCTIONS, RAG_INSTRUCTIONS, TASK_TYPE_INSTRUCTIONS, VARIABLES_CONTEXT_INSTRUCTIONS } from "./agent_instructions";
|
import { CHILD_TRANSFER_RELATED_INSTRUCTIONS, CONVERSATION_TYPE_INSTRUCTIONS, PIPELINE_TYPE_INSTRUCTIONS, RAG_INSTRUCTIONS, TASK_TYPE_INSTRUCTIONS, VARIABLES_CONTEXT_INSTRUCTIONS } from "./agent_instructions";
|
||||||
import { PrefixLogger } from "@/app/lib/utils";
|
import { PrefixLogger } from "@/app/lib/utils";
|
||||||
import { Message, AssistantMessage, AssistantMessageWithToolCalls, ToolMessage } from "@/app/lib/types/types";
|
import { Message, AssistantMessage, AssistantMessageWithToolCalls, ToolMessage } from "@/app/lib/types/types";
|
||||||
|
|
@ -361,7 +362,15 @@ function mapConfig(workflow: z.infer<typeof Workflow>): {
|
||||||
...acc,
|
...acc,
|
||||||
[agent.name]: agent
|
[agent.name]: agent
|
||||||
}), {});
|
}), {});
|
||||||
const toolConfig: Record<string, z.infer<typeof WorkflowTool>> = workflow.tools.reduce((acc, tool) => ({
|
// Merge workflow tools with default library tools (unique by name)
|
||||||
|
const mergedTools = (() => {
|
||||||
|
const defaults = getDefaultTools();
|
||||||
|
const map = new Map<string, z.infer<typeof WorkflowTool>>();
|
||||||
|
for (const t of workflow.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());
|
||||||
|
})();
|
||||||
|
const toolConfig: Record<string, z.infer<typeof WorkflowTool>> = mergedTools.reduce((acc, tool) => ({
|
||||||
...acc,
|
...acc,
|
||||||
[tool.name]: tool
|
[tool.name]: tool
|
||||||
}), {});
|
}), {});
|
||||||
|
|
|
||||||
|
|
@ -95,29 +95,7 @@ export class CreateProjectUseCase implements ICreateProjectUseCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conditionally include Gemini Image tool by default if GOOGLE_API_KEY is present
|
// Do not auto-attach image generation tool; it is available as a default library tool in the editor/runtime
|
||||||
const hasGoogleKey = !!process.env.GOOGLE_API_KEY;
|
|
||||||
const hasImageTool = (workflow.tools || []).some(t => t.isGeminiImage || t.name === 'Generate Image');
|
|
||||||
if (hasGoogleKey && !hasImageTool) {
|
|
||||||
const imageTool = {
|
|
||||||
name: 'Generate Image',
|
|
||||||
description: 'Generate an image using Google Gemini given a text prompt. Returns base64-encoded image data and any text parts.',
|
|
||||||
isGeminiImage: true,
|
|
||||||
parameters: {
|
|
||||||
type: 'object' as const,
|
|
||||||
properties: {
|
|
||||||
prompt: { type: 'string', description: 'Text prompt describing the image to generate' },
|
|
||||||
modelName: { type: 'string', description: 'Optional Gemini model override' },
|
|
||||||
},
|
|
||||||
required: ['prompt'],
|
|
||||||
additionalProperties: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
workflow = {
|
|
||||||
...workflow,
|
|
||||||
tools: [...(workflow.tools || []), imageTool] as any,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// create project secret
|
// create project secret
|
||||||
const secret = crypto.randomBytes(32).toString('hex');
|
const secret = crypto.randomBytes(32).toString('hex');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue