mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-11 10:32:37 +01:00
feat: Agent handoff UI
This commit is contained in:
parent
6e0e47d5dd
commit
e6baecb985
8 changed files with 118 additions and 9 deletions
|
|
@ -49,7 +49,7 @@
|
||||||
"@langchain/google-vertexai": "^0.2.13",
|
"@langchain/google-vertexai": "^0.2.13",
|
||||||
"@langchain/openai": "^0.5.18",
|
"@langchain/openai": "^0.5.18",
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@librechat/agents": "^3.0.0-rc6",
|
"@librechat/agents": "^3.0.0-rc7",
|
||||||
"@librechat/api": "*",
|
"@librechat/api": "*",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
|
|
|
||||||
91
client/src/components/Chat/Messages/Content/AgentHandoff.tsx
Normal file
91
client/src/components/Chat/Messages/Content/AgentHandoff.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { EModelEndpoint, Constants } from 'librechat-data-provider';
|
||||||
|
import { ChevronDown } from 'lucide-react';
|
||||||
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
|
import MessageIcon from '~/components/Share/MessageIcon';
|
||||||
|
import { useAgentsMapContext } from '~/Providers';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
interface AgentHandoffProps {
|
||||||
|
name: string;
|
||||||
|
args: string | Record<string, unknown>;
|
||||||
|
output?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AgentHandoff: React.FC<AgentHandoffProps> = ({ name, args: _args = '' }) => {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const agentsMap = useAgentsMapContext();
|
||||||
|
const [showInfo, setShowInfo] = useState(false);
|
||||||
|
|
||||||
|
/** Extracted agent ID from tool name (e.g., "lc_transfer_to_agent_gUV0wMb7zHt3y3Xjz-8_4" -> "agent_gUV0wMb7zHt3y3Xjz-8_4") */
|
||||||
|
const targetAgentId = useMemo(() => {
|
||||||
|
if (typeof name !== 'string' || !name.startsWith(Constants.LC_TRANSFER_TO_)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return name.replace(Constants.LC_TRANSFER_TO_, '');
|
||||||
|
}, [name]);
|
||||||
|
|
||||||
|
const targetAgent = useMemo(() => {
|
||||||
|
if (!targetAgentId || !agentsMap) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return agentsMap[targetAgentId];
|
||||||
|
}, [agentsMap, targetAgentId]);
|
||||||
|
|
||||||
|
const args = useMemo(() => {
|
||||||
|
if (typeof _args === 'string') {
|
||||||
|
return _args;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(_args, null, 2);
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, [_args]) as string;
|
||||||
|
|
||||||
|
const hasInfo = useMemo(() => (args?.length ?? 0) > 0, [args]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-3">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-pointer items-center gap-2.5 text-sm text-text-secondary',
|
||||||
|
hasInfo && 'transition-colors hover:text-text-primary',
|
||||||
|
)}
|
||||||
|
onClick={() => hasInfo && setShowInfo(!showInfo)}
|
||||||
|
>
|
||||||
|
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
|
||||||
|
<MessageIcon
|
||||||
|
message={
|
||||||
|
{
|
||||||
|
endpoint: EModelEndpoint.agents,
|
||||||
|
isCreatedByUser: false,
|
||||||
|
} as TMessage
|
||||||
|
}
|
||||||
|
agent={targetAgent || undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>{localize('com_ui_transferred_to')}</span>
|
||||||
|
<span className="select-none font-medium text-text-primary">
|
||||||
|
{targetAgent?.name || localize('com_ui_agent')}
|
||||||
|
</span>
|
||||||
|
{hasInfo && (
|
||||||
|
<ChevronDown
|
||||||
|
className={cn('ml-1 h-3 w-3 transition-transform', showInfo && 'rotate-180')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{hasInfo && showInfo && (
|
||||||
|
<div className="ml-8 mt-2 rounded-md bg-surface-secondary p-3 text-xs">
|
||||||
|
<div className="mb-1 font-medium text-text-secondary">
|
||||||
|
{localize('com_ui_handoff_instructions')}:
|
||||||
|
</div>
|
||||||
|
<pre className="overflow-x-auto whitespace-pre-wrap text-text-primary">{args}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AgentHandoff;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
Tools,
|
Tools,
|
||||||
|
Constants,
|
||||||
ContentTypes,
|
ContentTypes,
|
||||||
ToolCallTypes,
|
ToolCallTypes,
|
||||||
imageGenTools,
|
imageGenTools,
|
||||||
|
|
@ -10,6 +11,7 @@ import type { TMessageContentParts, TAttachment } from 'librechat-data-provider'
|
||||||
import { OpenAIImageGen, EmptyText, Reasoning, ExecuteCode, AgentUpdate, Text } from './Parts';
|
import { OpenAIImageGen, EmptyText, Reasoning, ExecuteCode, AgentUpdate, Text } from './Parts';
|
||||||
import { ErrorMessage } from './MessageContent';
|
import { ErrorMessage } from './MessageContent';
|
||||||
import RetrievalCall from './RetrievalCall';
|
import RetrievalCall from './RetrievalCall';
|
||||||
|
import AgentHandoff from './AgentHandoff';
|
||||||
import CodeAnalyze from './CodeAnalyze';
|
import CodeAnalyze from './CodeAnalyze';
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import WebSearch from './WebSearch';
|
import WebSearch from './WebSearch';
|
||||||
|
|
@ -118,6 +120,14 @@ const Part = memo(
|
||||||
isLast={isLast}
|
isLast={isLast}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (isToolCall && toolCall.name?.startsWith(Constants.LC_TRANSFER_TO_)) {
|
||||||
|
return (
|
||||||
|
<AgentHandoff
|
||||||
|
args={toolCall.args ?? ''}
|
||||||
|
name={toolCall.name || ''}
|
||||||
|
output={toolCall.output ?? ''}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else if (isToolCall) {
|
} else if (isToolCall) {
|
||||||
return (
|
return (
|
||||||
<ToolCall
|
<ToolCall
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ interface AgentUpdateProps {
|
||||||
|
|
||||||
const AgentUpdate: React.FC<AgentUpdateProps> = ({ currentAgentId }) => {
|
const AgentUpdate: React.FC<AgentUpdateProps> = ({ currentAgentId }) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const agentsMap = useAgentsMapContext() || {};
|
const agentsMap = useAgentsMapContext();
|
||||||
const currentAgent = useMemo(() => agentsMap[currentAgentId], [agentsMap, currentAgentId]);
|
const currentAgent = useMemo(() => agentsMap?.[currentAgentId], [agentsMap, currentAgentId]);
|
||||||
if (!currentAgentId) {
|
if (!currentAgentId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -664,6 +664,10 @@
|
||||||
"com_ui_agent_handoff_prompt_placeholder": "Tell this agent what content to generate and pass to the handoff agent. You need to add something here to enable this feature",
|
"com_ui_agent_handoff_prompt_placeholder": "Tell this agent what content to generate and pass to the handoff agent. You need to add something here to enable this feature",
|
||||||
"com_ui_agent_handoff_prompt_key": "Content parameter name (default: 'instructions')",
|
"com_ui_agent_handoff_prompt_key": "Content parameter name (default: 'instructions')",
|
||||||
"com_ui_agent_handoff_prompt_key_placeholder": "Label the content passed (default: 'instructions')",
|
"com_ui_agent_handoff_prompt_key_placeholder": "Label the content passed (default: 'instructions')",
|
||||||
|
"com_ui_transferring_to_agent": "Transferring to {{0}}",
|
||||||
|
"com_ui_transferred_to_agent": "Transferred to {{0}}",
|
||||||
|
"com_ui_transferred_to": "Transferred to",
|
||||||
|
"com_ui_handoff_instructions": "Handoff instructions",
|
||||||
"com_ui_agent_delete_error": "There was an error deleting the agent",
|
"com_ui_agent_delete_error": "There was an error deleting the agent",
|
||||||
"com_ui_agent_deleted": "Successfully deleted agent",
|
"com_ui_agent_deleted": "Successfully deleted agent",
|
||||||
"com_ui_agent_duplicate_error": "There was an error duplicating the agent",
|
"com_ui_agent_duplicate_error": "There was an error duplicating the agent",
|
||||||
|
|
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -64,7 +64,7 @@
|
||||||
"@langchain/google-vertexai": "^0.2.13",
|
"@langchain/google-vertexai": "^0.2.13",
|
||||||
"@langchain/openai": "^0.5.18",
|
"@langchain/openai": "^0.5.18",
|
||||||
"@langchain/textsplitters": "^0.1.0",
|
"@langchain/textsplitters": "^0.1.0",
|
||||||
"@librechat/agents": "^3.0.0-rc6",
|
"@librechat/agents": "^3.0.0-rc7",
|
||||||
"@librechat/api": "*",
|
"@librechat/api": "*",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
|
|
@ -21909,9 +21909,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@librechat/agents": {
|
"node_modules/@librechat/agents": {
|
||||||
"version": "3.0.0-rc6",
|
"version": "3.0.0-rc7",
|
||||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.0.0-rc6.tgz",
|
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.0.0-rc7.tgz",
|
||||||
"integrity": "sha512-MAE+HdoRw/XKWIzhoYOUiJrPjN6xicOiLRlDarYAZe4JewLKV2MuBGhRJW9TCn0kwyvGJsMQkTX8xQIXZw7OuA==",
|
"integrity": "sha512-cwXAr6c9knEglcMLuGv4FYQ4U4kJM2jvnyjHxwYRA7JYmaALKi7D5X1NMstWDUrLCOK1UIxGqtEagcUJu6mvCA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@langchain/anthropic": "^0.3.26",
|
"@langchain/anthropic": "^0.3.26",
|
||||||
|
|
@ -51984,7 +51984,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@langchain/core": "^0.3.72",
|
"@langchain/core": "^0.3.72",
|
||||||
"@librechat/agents": "^3.0.0-rc6",
|
"@librechat/agents": "^3.0.0-rc7",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@langchain/core": "^0.3.72",
|
"@langchain/core": "^0.3.72",
|
||||||
"@librechat/agents": "^3.0.0-rc6",
|
"@librechat/agents": "^3.0.0-rc7",
|
||||||
"@librechat/data-schemas": "*",
|
"@librechat/data-schemas": "*",
|
||||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
|
|
|
||||||
|
|
@ -1566,6 +1566,10 @@ export enum Constants {
|
||||||
* This helps inform the UI if the mcp server was previously added.
|
* This helps inform the UI if the mcp server was previously added.
|
||||||
* */
|
* */
|
||||||
mcp_server = 'sys__server__sys',
|
mcp_server = 'sys__server__sys',
|
||||||
|
/**
|
||||||
|
* Handoff Tool Name Prefix
|
||||||
|
*/
|
||||||
|
LC_TRANSFER_TO_ = 'lc_transfer_to_',
|
||||||
/** Placeholder Agent ID for Ephemeral Agents */
|
/** Placeholder Agent ID for Ephemeral Agents */
|
||||||
EPHEMERAL_AGENT_ID = 'ephemeral',
|
EPHEMERAL_AGENT_ID = 'ephemeral',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue