diff --git a/apps/rowboat/app/lib/components/atmentions.ts b/apps/rowboat/app/lib/components/atmentions.ts
index 8a17a39d..0d52adf1 100644
--- a/apps/rowboat/app/lib/components/atmentions.ts
+++ b/apps/rowboat/app/lib/components/atmentions.ts
@@ -23,6 +23,7 @@ export function createAtMentions({ agents, prompts, tools, currentAgentName }: C
atMentions.push({
id,
value: id,
+ label: `Agent: ${a.name}`,
denotationChar: "@", // Add required properties for Match type
link: id,
target: "_self"
@@ -35,6 +36,7 @@ export function createAtMentions({ agents, prompts, tools, currentAgentName }: C
atMentions.push({
id,
value: id,
+ label: `Prompt: ${prompt.name}`,
denotationChar: "@",
link: id,
target: "_self"
@@ -47,6 +49,7 @@ export function createAtMentions({ agents, prompts, tools, currentAgentName }: C
atMentions.push({
id,
value: id,
+ label: `Tool: ${tool.name}`,
denotationChar: "@",
link: id,
target: "_self"
diff --git a/apps/rowboat/app/lib/components/editable-field.tsx b/apps/rowboat/app/lib/components/editable-field.tsx
index 9a91e5e3..2f12c584 100644
--- a/apps/rowboat/app/lib/components/editable-field.tsx
+++ b/apps/rowboat/app/lib/components/editable-field.tsx
@@ -7,6 +7,7 @@ import { Label } from "./label";
import dynamic from "next/dynamic";
import { Match } from "./mentions_editor";
import { SparklesIcon } from "lucide-react";
+import { useEntitySelection } from "../../projects/[projectId]/workflow/workflow_editor";
const MentionsEditor = dynamic(() => import('./mentions_editor'), { ssr: false });
interface EditableFieldProps {
@@ -30,6 +31,7 @@ interface EditableFieldProps {
show: boolean;
setShow: (show: boolean) => void;
};
+ onMentionNavigate?: (type: 'agent' | 'tool' | 'prompt', name: string) => void;
}
export function EditableField({
@@ -50,6 +52,7 @@ export function EditableField({
error,
inline = false,
showGenerateButton,
+ onMentionNavigate,
}: EditableFieldProps) {
const [isEditing, setIsEditing] = useState(false);
const [localValue, setLocalValue] = useState(value);
@@ -73,6 +76,18 @@ export function EditableField({
setIsEditing(false);
});
+ let contextMentionNavigate: { onSelectAgent: (name: string) => void; onSelectTool: (name: string) => void; onSelectPrompt: (name: string) => void; } | undefined;
+ try {
+ contextMentionNavigate = useEntitySelection();
+ } catch {}
+ const handleMentionNavigate = onMentionNavigate || ((type, name) => {
+ if (contextMentionNavigate) {
+ if (type === 'agent') contextMentionNavigate.onSelectAgent(name);
+ else if (type === 'tool') contextMentionNavigate.onSelectTool(name);
+ else if (type === 'prompt') contextMentionNavigate.onSelectPrompt(name);
+ }
+ });
+
const commonProps = {
autoFocus: true,
value: localValue,
@@ -236,10 +251,10 @@ export function EditableField({
{value ? (
<>
{markdown &&
-
+
}
{!markdown &&
-
+
}
>
) : (
diff --git a/apps/rowboat/app/lib/components/markdown-content.tsx b/apps/rowboat/app/lib/components/markdown-content.tsx
index ad4c54ac..724f36b2 100644
--- a/apps/rowboat/app/lib/components/markdown-content.tsx
+++ b/apps/rowboat/app/lib/components/markdown-content.tsx
@@ -4,10 +4,12 @@ import { Match } from './mentions_editor';
export default function MarkdownContent({
content,
- atValues = []
+ atValues = [],
+ onMentionNavigate,
}: {
content: string;
atValues?: Match[];
+ onMentionNavigate?: (type: 'agent' | 'tool' | 'prompt', name: string) => void;
}) {
return
atValue.id === label);
+ const handleMentionClick = (e: React.MouseEvent) => {
+ if (onMentionNavigate && type && name) {
+ e.preventDefault();
+ onMentionNavigate(type, name);
+ }
+ };
if (atValues.length > 0 && invalid) {
return (
-
- @{label} (!)
+
+ {displayLabel} (!)
);
}
return (
-
- @{label}
+
+ {displayLabel}
);
}
diff --git a/apps/rowboat/app/lib/components/mentions_editor.tsx b/apps/rowboat/app/lib/components/mentions_editor.tsx
index 54c80f53..e7eb3fd8 100644
--- a/apps/rowboat/app/lib/components/mentions_editor.tsx
+++ b/apps/rowboat/app/lib/components/mentions_editor.tsx
@@ -11,6 +11,7 @@ export type Match = {
id: string;
value: string;
invalid?: boolean;
+ label?: string;
[key: string]: string | boolean | undefined;
};
@@ -18,7 +19,7 @@ class CustomMentionBlot extends MentionBlot {
static render(data: any) {
const element = document.createElement('span');
element.className = data.invalid ? 'invalid' : '';
- element.textContent = data.invalid ? `${data.value} (!)` : data.value;
+ element.textContent = data.invalid ? `${data.label || data.value} (!)` : (data.label || data.value);
return element;
}
}
@@ -154,7 +155,7 @@ export default function MentionEditor({
renderItem: (item: Match) => {
const div = document.createElement('div');
div.className = "px-2 py-1 bg-white text-blue-800 hover:bg-blue-100 cursor-pointer";
- div.textContent = item.id;
+ div.textContent = item.label || item.id;
return div;
},
}
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
index 897f20e4..ea817764 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
@@ -553,6 +553,19 @@ function reducer(state: State, action: Action): State {
return newState;
}
+// Context for entity selection
+export const EntitySelectionContext = createContext<{
+ onSelectAgent: (name: string) => void;
+ onSelectTool: (name: string) => void;
+ onSelectPrompt: (name: string) => void;
+} | null>(null);
+
+export function useEntitySelection() {
+ const ctx = useContext(EntitySelectionContext);
+ if (!ctx) throw new Error('useEntitySelection must be used within EntitySelectionContext');
+ return ctx;
+}
+
export function WorkflowEditor({
dataSources,
workflow,
@@ -819,276 +832,284 @@ export function WorkflowEditor({
setIsInitialState(false);
}
- return
-
-
-
-
-
-
-
-
-
- {state.present.publishing &&
}
- {isLive &&
-
- Live
-
}
- {!isLive &&
}
- {/* Download JSON icon button, with tooltip, to the left of the menu */}
-
-
-
-
-
+ return (
+
+
+
+
+
+
-
-
-
+
-
- {
- if (key === 'switch') {
- handleShowSelector();
- }
- if (key === 'clone') {
- handleCloneVersion(state.present.workflow._id);
- }
- }}
- >
- }
- className="gap-x-2"
- >
- View versions
-
+
+
+ {state.present.publishing &&
}
+ {isLive &&
+
+ Live
+
}
+ {!isLive &&
}
+ {/* Download JSON icon button, with tooltip, to the left of the menu */}
+
+
+
+
+
+
+
+
+
+
+
+ {
+ if (key === 'switch') {
+ handleShowSelector();
+ }
+ if (key === 'clone') {
+ handleCloneVersion(state.present.workflow._id);
+ }
+ }}
+ >
+ }
+ className="gap-x-2"
+ >
+ View versions
+
-
}
- className="gap-x-2"
+
}
+ className="gap-x-2"
+ >
+ Clone this version
+
+
+
+
+
+ {showCopySuccess &&
}
+
+ {isLive &&
+
+
+ This version is locked. You cannot make changes. Changes applied through copilot will
notbe reflected.
+
+
-
- {showCopySuccess &&
}
-
- {isLive &&
-
-
- This version is locked. You cannot make changes. Changes applied through copilot will
notbe reflected.
+
+
+
}
+ {!isLive && <>
+
+
+
}
+ data-tour-target="deploy"
+ >
+ Deploy
+
+
+ >}
-
-
-
}
- {!isLive && <>
-
-
-
}
- data-tour-target="deploy"
- >
- Deploy
-
-
- >}
-
-
-
-
-
-
-
-
-
-
- {state.present.selection?.type === "agent" && agent.name === state.present.selection!.name)}`}
- projectId={state.present.workflow.projectId}
- workflow={state.present.workflow}
- agent={state.present.workflow.agents.find((agent) => agent.name === state.present.selection!.name)!}
- usedAgentNames={new Set(state.present.workflow.agents.filter((agent) => agent.name !== state.present.selection!.name).map((agent) => agent.name))}
- agents={state.present.workflow.agents}
- tools={state.present.workflow.tools}
- projectTools={projectTools}
- prompts={state.present.workflow.prompts}
- dataSources={dataSources}
- handleUpdate={handleUpdateAgent.bind(null, state.present.selection.name)}
- handleClose={handleUnselectAgent}
- useRag={useRag}
- triggerCopilotChat={triggerCopilotChat}
- eligibleModels={eligibleModels === "*" ? "*" : eligibleModels.agentModels}
- />}
- {state.present.selection?.type === "tool" && (() => {
- const selectedTool = state.present.workflow.tools.find(
- (tool) => tool.name === state.present.selection!.name
- ) || projectTools.find(
- (tool) => tool.name === state.present.selection!.name
- );
- return tool.name !== state.present.selection!.name).map((tool) => tool.name),
- ...projectTools.filter((tool) => tool.name !== state.present.selection!.name).map((tool) => tool.name)
- ])}
- handleUpdate={handleUpdateTool.bind(null, state.present.selection.name)}
- handleClose={handleUnselectTool}
- />;
- })()}
- {state.present.selection?.type === "prompt" && prompt.name === state.present.selection!.name)!}
- agents={state.present.workflow.agents}
- tools={state.present.workflow.tools}
- 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={handleUpdatePrompt.bind(null, state.present.selection.name)}
- handleClose={handleUnselectPrompt}
- />}
-
- {showCopilot && (
- <>
+
+
+
+
+
+
setCopilotWidth(size)}
+ minSize={20}
+ defaultSize={showCopilot ? PANEL_RATIOS.chatApp : PANEL_RATIOS.chatApp + PANEL_RATIOS.copilot}
+ className="overflow-auto"
>
- 0 ? {
- type: 'chat',
- messages: chatMessages
- } : undefined
- }
+ messageSubscriber={updateChatMessages}
+ mcpServerUrls={mcpServerUrls}
+ toolWebhookUrl={toolWebhookUrl}
isInitialState={isInitialState}
- dataSources={dataSources}
+ onPanelClick={handlePlaygroundClick}
+ projectTools={projectTools}
/>
+ {state.present.selection?.type === "agent" && agent.name === state.present.selection!.name)}`}
+ projectId={state.present.workflow.projectId}
+ workflow={state.present.workflow}
+ agent={state.present.workflow.agents.find((agent) => agent.name === state.present.selection!.name)!}
+ usedAgentNames={new Set(state.present.workflow.agents.filter((agent) => agent.name !== state.present.selection!.name).map((agent) => agent.name))}
+ agents={state.present.workflow.agents}
+ tools={state.present.workflow.tools}
+ projectTools={projectTools}
+ prompts={state.present.workflow.prompts}
+ dataSources={dataSources}
+ handleUpdate={handleUpdateAgent.bind(null, state.present.selection.name)}
+ handleClose={handleUnselectAgent}
+ useRag={useRag}
+ triggerCopilotChat={triggerCopilotChat}
+ eligibleModels={eligibleModels === "*" ? "*" : eligibleModels.agentModels}
+ />}
+ {state.present.selection?.type === "tool" && (() => {
+ const selectedTool = state.present.workflow.tools.find(
+ (tool) => tool.name === state.present.selection!.name
+ ) || projectTools.find(
+ (tool) => tool.name === state.present.selection!.name
+ );
+ return tool.name !== state.present.selection!.name).map((tool) => tool.name),
+ ...projectTools.filter((tool) => tool.name !== state.present.selection!.name).map((tool) => tool.name)
+ ])}
+ handleUpdate={handleUpdateTool.bind(null, state.present.selection.name)}
+ handleClose={handleUnselectTool}
+ />;
+ })()}
+ {state.present.selection?.type === "prompt" && prompt.name === state.present.selection!.name)!}
+ agents={state.present.workflow.agents}
+ tools={state.present.workflow.tools}
+ 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={handleUpdatePrompt.bind(null, state.present.selection.name)}
+ handleClose={handleUnselectPrompt}
+ />}
- >
- )}
-
- {USE_PRODUCT_TOUR && showTour && (
- setShowTour(false)}
- />
- )}
- ;
+ {showCopilot && (
+ <>
+
+ setCopilotWidth(size)}
+ >
+ 0 ? {
+ type: 'chat',
+ messages: chatMessages
+ } : undefined
+ }
+ isInitialState={isInitialState}
+ dataSources={dataSources}
+ />
+
+ >
+ )}
+
+ {USE_PRODUCT_TOUR && showTour && (
+ setShowTour(false)}
+ />
+ )}
+
+
+ );
}