- {label &&
{label}
}
+ {label &&
}
{isEditing && multiline &&
{label}
;
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/lib/components/markdown-content.tsx b/apps/rowboat/app/lib/components/markdown-content.tsx
index 46f93916..e475d927 100644
--- a/apps/rowboat/app/lib/components/markdown-content.tsx
+++ b/apps/rowboat/app/lib/components/markdown-content.tsx
@@ -7,19 +7,19 @@ export default function MarkdownContent({ content }: { content: string }) {
remarkPlugins={[remarkGfm]}
components={{
h1({ children }) {
- return
{children}
+ return
{children}
},
h2({ children }) {
- return
{children}
+ return
{children}
},
h3({ children }) {
- return
{children}
+ return
{children}
},
h4({ children }) {
- return
{children}
+ return
{children}
},
h5({ children }) {
- return
{children}
+ return
{children}
},
h6({ children }) {
return
{children}
diff --git a/apps/rowboat/app/lib/types.ts b/apps/rowboat/app/lib/types.ts
index eeb4361b..c24f1378 100644
--- a/apps/rowboat/app/lib/types.ts
+++ b/apps/rowboat/app/lib/types.ts
@@ -159,7 +159,10 @@ export const WorkflowAgent = z.object({
examples: z.string().optional(),
prompts: z.array(z.string()),
tools: z.array(z.string()),
- model: z.string(),
+ model: z.union([
+ z.literal('gpt-4o'),
+ z.literal('gpt-4o-mini'),
+ ]),
locked: z.boolean().default(false).describe('Whether this agent is locked and cannot be deleted').optional(),
toggleAble: z.boolean().default(true).describe('Whether this agent can be enabled or disabled').optional(),
global: z.boolean().default(false).describe('Whether this agent is a global agent, in which case it cannot be connected to other agents').optional(),
diff --git a/apps/rowboat/app/projects/[projectId]/playground/messages.tsx b/apps/rowboat/app/projects/[projectId]/playground/messages.tsx
index 9db05b76..58a49eaa 100644
--- a/apps/rowboat/app/projects/[projectId]/playground/messages.tsx
+++ b/apps/rowboat/app/projects/[projectId]/playground/messages.tsx
@@ -8,13 +8,14 @@ import MarkdownContent from "@/app/lib/components/markdown-content";
import Link from "next/link";
import { apiV1 } from "rowboat-shared";
import { EditableField } from "@/app/lib/components/editable-field";
+import { MessageSquareIcon, EllipsisIcon } from "lucide-react";
function UserMessage({ content }: { content: string }) {
return
-
+
User
-
;
@@ -26,17 +27,13 @@ function InternalAssistantMessage({ content, sender, latency }: { content: strin
// show a message icon with a + symbol to expand and show the content
return
{!expanded &&
setExpanded(true)}>
-
-
-
-
-
-
+
+
Show debug message
}
{expanded &&
-
+
{sender ?? 'Assistant'}
setExpanded(false)}>
@@ -55,27 +52,25 @@ function InternalAssistantMessage({ content, sender, latency }: { content: strin
function AssistantMessage({ content, sender, latency }: { content: string, sender: string | null | undefined, latency: number }) {
return
-
+
{sender ?? 'Assistant'}
{Math.round(latency / 1000)}s
-
;
}
function AssistantMessageLoading() {
- return
-
+ return
;
}
@@ -84,8 +79,8 @@ function UserMessageLoading() {
User
-
;
}
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/agent_config.tsx b/apps/rowboat/app/projects/[projectId]/workflow/agent_config.tsx
index dd61c32c..e52b0a36 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/agent_config.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/agent_config.tsx
@@ -1,11 +1,13 @@
"use client";
import { AgenticAPITool, DataSource, WithStringId, WorkflowAgent, WorkflowPrompt } from "@/app/lib/types";
-import { Accordion, AccordionItem, Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Radio, RadioGroup, Select, SelectItem, Textarea } from "@nextui-org/react";
+import { Button, Divider, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Radio, RadioGroup, Select, SelectItem } from "@nextui-org/react";
import { z } from "zod";
import { DataSourceIcon } from "@/app/lib/components/datasource-icon";
import { ActionButton, Pane } from "./pane";
import { EditableField } from "@/app/lib/components/editable-field";
-import MarkdownContent from "@/app/lib/components/markdown-content";
+import { Label } from "@/app/lib/components/label";
+import { PlusIcon, XIcon } from "lucide-react";
+import { List } from "./config_list";
export function AgentConfig({
agent,
@@ -38,7 +40,7 @@ export function AgentConfig({
]}>
- {!agent.locked && (
+ {!agent.locked && <>
- )}
+
+ >}
+
+
+
+
+
-
-
Attach prompts:
-
- {agent.prompts.map((prompt) => (
-
-
{prompt}
-
{
- const newPrompts = agent.prompts.filter((p) => p !== prompt);
- handleUpdate({
- ...agent,
- prompts: newPrompts
- });
- }}
- className="bg-white rounded-md text-gray-500 hover:text-gray-800"
- >
-
-
-
-
-
- ))}
-
-
+
+
+
+
+
({
+ id: prompt,
+ node: {prompt}
+ }))}
+ onRemove={(id) => {
+ const newPrompts = agent.prompts.filter((p) => p !== id);
+ handleUpdate({
+ ...agent,
+ prompts: newPrompts
+ });
+ }}
+ />
+
-
- }
+ startContent={ }
>
Add prompt
@@ -154,40 +154,33 @@ export function AgentConfig({
-
-
RAG:
-
- {agent.ragDataSources?.map((source) => (
-
-
-
ds._id === source)?.data.type} />
- {dataSources.find((ds) => ds._id === source)?.name || "Unknown"}
-
-
{
- const newSources = agent.ragDataSources?.filter((s) => s !== source);
- handleUpdate({
- ...agent,
- ragDataSources: newSources
- });
- }}
- className="bg-white rounded-md text-gray-500 hover:text-gray-800"
- >
-
-
-
-
+
+
+
+
+
+
({
+ id: source,
+ node:
+
ds._id === source)?.data.type} />
+ {dataSources.find((ds) => ds._id === source)?.name || "Unknown"}
- ))}
-
+ })) || []}
+ onRemove={(id) => {
+ const newSources = agent.ragDataSources?.filter((s) => s !== id);
+ handleUpdate({
+ ...agent,
+ ragDataSources: newSources
+ });
+ }}
+ />
-
- }
+ startContent={ }
>
Add data source
@@ -206,72 +199,64 @@ export function AgentConfig({
))}
- {agent.ragDataSources !== undefined && agent.ragDataSources.length > 0 &&
-
-
- handleUpdate({
- ...agent,
- ragReturnType: value as z.infer['ragReturnType']
- })}
- >
- Chunks
- Content
-
- handleUpdate({
- ...agent,
- ragK: parseInt(value)
- })}
- type="number"
- />
-
-
- }
-
-
Tools:
-
- {agent.tools.map((tool) => (
-
-
{tool}
-
{
- const newTools = agent.tools.filter((t) => t !== tool);
- handleUpdate({
- ...agent,
- tools: newTools
- });
- }}
- className="bg-white rounded-md text-gray-500 hover:text-gray-800"
- >
-
-
-
-
-
- ))}
+
+
+
+ {agent.ragDataSources !== undefined && agent.ragDataSources.length > 0 && <>
+
+
+
+ handleUpdate({
+ ...agent,
+ ragReturnType: value as z.infer['ragReturnType']
+ })}
+ >
+ Chunks
+ Content
+
+
+ handleUpdate({
+ ...agent,
+ ragK: parseInt(value)
+ })}
+ type="number"
+ />
+
+ >}
+
+
+
+
+
({
+ id: tool,
+ node: {tool}
+ }))}
+ onRemove={(id) => {
+ const newTools = agent.tools.filter((t) => t !== id);
+ handleUpdate({
+ ...agent,
+ tools: newTools
+ });
+ }}
+ />
-
- }
+ startContent={ }
>
Add tool
@@ -288,37 +273,29 @@ export function AgentConfig({
-
-
Connected agents:
-
- {agent.connectedAgents?.map((connectedAgentName) => (
-
-
{connectedAgentName}
-
{
- const newAgents = (agent.connectedAgents || []).filter((a) => a !== connectedAgentName);
- handleUpdate({
- ...agent,
- connectedAgents: newAgents
- });
- }}
- className="bg-white rounded-md text-gray-500 hover:text-gray-800"
- >
-
-
-
-
-
- ))}
-
+
+
+
+
+
({
+ id: connectedAgentName,
+ node: {connectedAgentName}
+ })) || []}
+ onRemove={(id) => {
+ const newAgents = (agent.connectedAgents || []).filter((a) => a !== id);
+ handleUpdate({
+ ...agent,
+ connectedAgents: newAgents
+ });
+ }}
+ />
-
- }
+ startContent={ }
>
Connect agent
@@ -339,27 +316,30 @@ export function AgentConfig({
+
+
- {
- handleUpdate({
- ...agent,
- model: value
- });
- }}
- validate={(value) => {
- if (value.length === 0) {
- return { valid: false, errorMessage: "Model cannot be empty" };
- }
- return { valid: true };
- }}
+
+ handleUpdate({
+ ...agent,
+ model: keys.currentKey! as z.infer['model']
+ })}
className="w-40"
- />
+ >
+ {WorkflowAgent.shape.model.options.map((model) => (
+ {model.value}
+ ))}
+
+
+
+
-
Conversation control after turn:
+
void;
+}) {
+ return
+ {items.map((item) => (
+ onRemove(item.id)}>
+ {item.node}
+
+ ))}
+
;
+}
+
+export function ListItem({
+ children,
+ onRemove,
+}: {
+ children: React.ReactNode;
+ onRemove: () => void;
+}) {
+ return
+}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx b/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx
index 6305f166..acea66ea 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/copilot.tsx
@@ -130,10 +130,10 @@ function AssistantMessage({
return
-
+
{message.content.response.map((part, index) => {
if (part.type === "text") {
- return
+ return
;
} else if (part.type === "action") {
@@ -158,7 +158,7 @@ function UserMessage({
}: {
message: z.infer
;
}) {
- return
+ return
}
@@ -376,7 +376,7 @@ function App({
return
-
+
{messages.map((m, index) => {
// Calculate if this assistant message is stale
const isStale = m.role === 'assistant' && messages.slice(index + 1).some(
@@ -402,7 +402,7 @@ function App({
)}
>;
})}
- {loadingResponse &&
+ {loadingResponse &&
{loadingMessage}
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/pane.tsx b/apps/rowboat/app/projects/[projectId]/workflow/pane.tsx
index 4d8e4a8e..b0a6afc2 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/pane.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/pane.tsx
@@ -39,23 +39,24 @@ export function Pane({
export function ActionButton({
icon = null,
children,
- onClick,
+ onClick = undefined,
disabled = false,
primary = false,
}: {
icon?: React.ReactNode;
children: React.ReactNode;
- onClick: () => void;
+ onClick?: () => void | undefined;
disabled?: boolean;
primary?: boolean;
}) {
+ const onClickProp = onClick ? { onClick } : {};
return
{icon}
{children}
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/prompt_config.tsx b/apps/rowboat/app/projects/[projectId]/workflow/prompt_config.tsx
index 191d8d61..909aa2fd 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/prompt_config.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/prompt_config.tsx
@@ -1,9 +1,7 @@
"use client";
-import { useState } from "react";
import { WorkflowPrompt } from "@/app/lib/types";
-import { Input, Textarea } from "@nextui-org/react";
+import { Divider, Input, Textarea } from "@nextui-org/react";
import { z } from "zod";
-import MarkdownContent from "@/app/lib/components/markdown-content";
import { ActionButton, Pane } from "./pane";
import { EditableField } from "@/app/lib/components/editable-field";
@@ -31,41 +29,46 @@ export function PromptConfig({
]}>
{prompt.type === "base_prompt" && (
+ <>
+
{
+ handleUpdate({
+ ...prompt,
+ name: value
+ });
+ }}
+ placeholder="Enter prompt name"
+ validate={(value) => {
+ if (value.length === 0) {
+ return { valid: false, errorMessage: "Name cannot be empty" };
+ }
+ if (usedPromptNames.has(value)) {
+ return { valid: false, errorMessage: "This name is already taken" };
+ }
+ return { valid: true };
+ }}
+ />
+
+ >
+ )}
+
+
{
handleUpdate({
...prompt,
- name: value
+ prompt: value
});
}}
- placeholder="Enter prompt name"
- validate={(value) => {
- if (value.length === 0) {
- return { valid: false, errorMessage: "Name cannot be empty" };
- }
- if (usedPromptNames.has(value)) {
- return { valid: false, errorMessage: "This name is already taken" };
- }
- return { valid: true };
- }}
+ placeholder="Edit prompt here..."
+ markdown
+ label="Prompt"
+ multiline
/>
- )}
-
- {
- handleUpdate({
- ...prompt,
- prompt: value
- });
- }}
- placeholder="Edit prompt here..."
- markdown
- label="Prompt"
- multiline
- />
+
;
}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/tool_config.tsx b/apps/rowboat/app/projects/[projectId]/workflow/tool_config.tsx
index 4cbef9c3..1fd05685 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/tool_config.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/tool_config.tsx
@@ -1,9 +1,111 @@
"use client";
import { WorkflowTool } from "@/app/lib/types";
-import { Button, Select, SelectItem, Switch } from "@nextui-org/react";
+import { Accordion, AccordionItem, Button, Checkbox, Select, SelectItem, Switch } from "@nextui-org/react";
import { z } from "zod";
import { ActionButton, Pane } from "./pane";
import { EditableField } from "@/app/lib/components/editable-field";
+import { Divider } from "@nextui-org/react";
+import { Label } from "@/app/lib/components/label";
+import { TrashIcon, XIcon } from "lucide-react";
+import { useState } from "react";
+
+export function ParameterConfig({
+ param,
+ handleUpdate,
+ handleDelete,
+ handleRename
+}: {
+ param: {
+ name: string,
+ description: string,
+ type: string,
+ required: boolean
+ },
+ handleUpdate: (name: string, data: {
+ description: string,
+ type: string,
+ required: boolean
+ }) => void,
+ handleDelete: (name: string) => void,
+ handleRename: (oldName: string, newName: string) => void
+}) {
+ return handleDelete(param.name)}
+ icon={ }
+ >
+ Remove
+
+ ]}
+ >
+
+
{
+ if (newName && newName !== param.name) {
+ handleRename(param.name, newName);
+ }
+ }}
+ />
+
+
+
+
+
+ {
+ handleUpdate(param.name, {
+ ...param,
+ type: Array.from(keys)[0] as string
+ });
+ }}
+ >
+ {['string', 'number', 'boolean', 'array', 'object'].map(type => (
+
+ {type}
+
+ ))}
+
+
+
+
+
+ {
+ handleUpdate(param.name, {
+ ...param,
+ description: desc
+ });
+ }}
+ />
+
+
+
+ {
+ handleUpdate(param.name, {
+ ...param,
+ required: !param.required
+ });
+ }}
+ >
+ Required
+
+
+ ;
+}
export function ToolConfig({
tool,
@@ -16,6 +118,68 @@ export function ToolConfig({
handleUpdate: (tool: z.infer) => void,
handleClose: () => void
}) {
+ const [selectedParams, setSelectedParams] = useState(new Set([]));
+
+ function handleParamRename(oldName: string, newName: string) {
+ const newProperties = { ...tool.parameters!.properties };
+ newProperties[newName] = newProperties[oldName];
+ delete newProperties[oldName];
+
+ const newRequired = [...(tool.parameters?.required || [])];
+ newRequired.splice(newRequired.indexOf(oldName), 1);
+ newRequired.push(newName);
+
+ handleUpdate({
+ ...tool,
+ parameters: { ...tool.parameters!, properties: newProperties, required: newRequired }
+ });
+ }
+
+ function handleParamUpdate(name: string, data: {
+ description: string,
+ type: string,
+ required: boolean
+ }) {
+ const newProperties = { ...tool.parameters!.properties };
+ newProperties[name] = {
+ type: data.type,
+ description: data.description
+ };
+
+ const newRequired = [...(tool.parameters?.required || [])];
+ if (data.required) {
+ newRequired.push(name);
+ } else {
+ newRequired.splice(newRequired.indexOf(name), 1);
+ }
+
+ handleUpdate({
+ ...tool,
+ parameters: {
+ ...tool.parameters!,
+ properties: newProperties,
+ required: newRequired,
+ }
+ });
+ }
+
+ function handleParamDelete(paramName: string) {
+ const newProperties = { ...tool.parameters!.properties };
+ delete newProperties[paramName];
+
+ const newRequired = [...(tool.parameters?.required || [])];
+ newRequired.splice(newRequired.indexOf(paramName), 1);
+
+ handleUpdate({
+ ...tool,
+ parameters: {
+ ...tool.parameters!,
+ properties: newProperties,
+ required: newRequired,
+ }
+ });
+ }
+
return (
+
+
-
- handleUpdate({
- ...tool,
- mockInPlayground: value
- })}
- />
- Mock tool in Playground
-
+
-
-
Parameters:
+
handleUpdate({
+ ...tool,
+ mockInPlayground: value
+ })}
+ >
+ Mock tool in Playground
+
+
+
+
+
+
{Object.entries(tool.parameters?.properties || {}).map(([paramName, param], index) => (
-
-
-
{
- if (newName && newName !== paramName) {
- const newProperties = { ...tool.parameters!.properties };
- newProperties[newName] = newProperties[paramName];
- delete newProperties[paramName];
-
- handleUpdate({
- ...tool,
- parameters: {
- ...tool.parameters!,
- properties: newProperties,
- required: tool.parameters!.required?.map(
- name => name === paramName ? newName : name
- ) || []
- }
- });
- }
- }}
- />
-
- {
- const newProperties = { ...tool.parameters!.properties };
- newProperties[paramName] = {
- ...newProperties[paramName],
- type: Array.from(keys)[0] as string
- };
-
- handleUpdate({
- ...tool,
- parameters: {
- ...tool.parameters!,
- properties: newProperties
- }
- });
- }}
- >
- {['string', 'number', 'boolean', 'array', 'object'].map(type => (
-
- {type}
-
- ))}
-
-
- {
- const newProperties = { ...tool.parameters!.properties };
- newProperties[paramName] = {
- ...newProperties[paramName],
- description: desc
- };
-
- handleUpdate({
- ...tool,
- parameters: {
- ...tool.parameters!,
- properties: newProperties
- }
- });
- }}
- />
-
-
-
- {
- const required = [...(tool.parameters?.required || [])];
- const index = required.indexOf(paramName);
- if (index === -1) {
- required.push(paramName);
- } else {
- required.splice(index, 1);
- }
-
- handleUpdate({
- ...tool,
- parameters: {
- ...tool.parameters!,
- required
- }
- });
- }}
- />
- Required
-
-
-
{
- const newProperties = { ...tool.parameters!.properties };
- delete newProperties[paramName];
-
- handleUpdate({
- ...tool,
- parameters: {
- ...tool.parameters!,
- properties: newProperties,
- required: tool.parameters!.required?.filter(
- name => name !== paramName
- ) || []
- }
- });
- }}
- >
-
-
-
-
-
-
-
- ))}
-
-
-
-
- }
- onClick={() => {
- const newParamName = `param${Object.keys(tool.parameters?.properties || {}).length + 1}`;
- const newProperties = {
- ...(tool.parameters?.properties || {}),
- [newParamName]: {
- type: 'string',
- description: ''
- }
- };
-
- handleUpdate({
- ...tool,
- parameters: {
- type: 'object',
- properties: newProperties,
- required: [...(tool.parameters?.required || []), newParamName]
- }
- });
+
- Add Parameter
-
-
+ handleUpdate={handleParamUpdate}
+ handleDelete={handleParamDelete}
+ handleRename={handleParamRename}
+ />
+ ))}
+
+
+
+ }
+ onClick={() => {
+ const newParamName = `param${Object.keys(tool.parameters?.properties || {}).length + 1}`;
+ const newProperties = {
+ ...(tool.parameters?.properties || {}),
+ [newParamName]: {
+ type: 'string',
+ description: ''
+ }
+ };
+
+ handleUpdate({
+ ...tool,
+ parameters: {
+ type: 'object',
+ properties: newProperties,
+ required: [...(tool.parameters?.required || []), newParamName]
+ }
+ });
+ }}
+ >
+ Add Parameter
+
);