mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-24 20:30:13 +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/openai": "^0.5.18",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@librechat/agents": "^3.0.0-rc6",
|
||||
"@librechat/agents": "^3.0.0-rc7",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@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 {
|
||||
Tools,
|
||||
Constants,
|
||||
ContentTypes,
|
||||
ToolCallTypes,
|
||||
imageGenTools,
|
||||
|
|
@ -10,6 +11,7 @@ import type { TMessageContentParts, TAttachment } from 'librechat-data-provider'
|
|||
import { OpenAIImageGen, EmptyText, Reasoning, ExecuteCode, AgentUpdate, Text } from './Parts';
|
||||
import { ErrorMessage } from './MessageContent';
|
||||
import RetrievalCall from './RetrievalCall';
|
||||
import AgentHandoff from './AgentHandoff';
|
||||
import CodeAnalyze from './CodeAnalyze';
|
||||
import Container from './Container';
|
||||
import WebSearch from './WebSearch';
|
||||
|
|
@ -118,6 +120,14 @@ const Part = memo(
|
|||
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) {
|
||||
return (
|
||||
<ToolCall
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ interface AgentUpdateProps {
|
|||
|
||||
const AgentUpdate: React.FC<AgentUpdateProps> = ({ currentAgentId }) => {
|
||||
const localize = useLocalize();
|
||||
const agentsMap = useAgentsMapContext() || {};
|
||||
const currentAgent = useMemo(() => agentsMap[currentAgentId], [agentsMap, currentAgentId]);
|
||||
const agentsMap = useAgentsMapContext();
|
||||
const currentAgent = useMemo(() => agentsMap?.[currentAgentId], [agentsMap, currentAgentId]);
|
||||
if (!currentAgentId) {
|
||||
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_key": "Content parameter name (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_deleted": "Successfully deleted 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/openai": "^0.5.18",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@librechat/agents": "^3.0.0-rc6",
|
||||
"@librechat/agents": "^3.0.0-rc7",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
|
|
@ -21909,9 +21909,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@librechat/agents": {
|
||||
"version": "3.0.0-rc6",
|
||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.0.0-rc6.tgz",
|
||||
"integrity": "sha512-MAE+HdoRw/XKWIzhoYOUiJrPjN6xicOiLRlDarYAZe4JewLKV2MuBGhRJW9TCn0kwyvGJsMQkTX8xQIXZw7OuA==",
|
||||
"version": "3.0.0-rc7",
|
||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.0.0-rc7.tgz",
|
||||
"integrity": "sha512-cwXAr6c9knEglcMLuGv4FYQ4U4kJM2jvnyjHxwYRA7JYmaALKi7D5X1NMstWDUrLCOK1UIxGqtEagcUJu6mvCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^0.3.26",
|
||||
|
|
@ -51984,7 +51984,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": "^0.3.72",
|
||||
"@librechat/agents": "^3.0.0-rc6",
|
||||
"@librechat/agents": "^3.0.0-rc7",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||
"axios": "^1.8.2",
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": "^0.3.72",
|
||||
"@librechat/agents": "^3.0.0-rc6",
|
||||
"@librechat/agents": "^3.0.0-rc7",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||
"axios": "^1.8.2",
|
||||
|
|
|
|||
|
|
@ -1566,6 +1566,10 @@ export enum Constants {
|
|||
* This helps inform the UI if the mcp server was previously added.
|
||||
* */
|
||||
mcp_server = 'sys__server__sys',
|
||||
/**
|
||||
* Handoff Tool Name Prefix
|
||||
*/
|
||||
LC_TRANSFER_TO_ = 'lc_transfer_to_',
|
||||
/** Placeholder Agent ID for Ephemeral Agents */
|
||||
EPHEMERAL_AGENT_ID = 'ephemeral',
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue