File size: 4,711 Bytes
9b906ea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | import React from "react";
import { Paperclip, Plus } from "lucide-react";
import { useTranslation } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
import { cn } from "#/utils/utils";
import { chatInputIconButtonClassName } from "#/utils/form-control-classes";
import { useOptionalConversationId } from "#/hooks/use-conversation-id";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { useConversationNameContextMenu } from "#/hooks/use-conversation-name-context-menu";
import { ToolsContextMenu } from "#/components/features/controls/tools-context-menu";
import { SystemMessageModal } from "#/components/features/conversation-panel/system-message-modal";
import { SkillsModal } from "#/components/features/conversation-panel/skills-modal";
import { PluginsModal } from "#/components/features/conversation-panel/plugins-modal";
import { HooksModal } from "#/components/features/conversation-panel/hooks-modal";
export interface ChatAddFileButtonProps {
handleFileIconClick: () => void;
disabled?: boolean;
/**
* Offer the "Switch agent profile" submenu. Computed by ChatInputActions:
* only while starting a new conversation (home or a blank conversation) and
* only when the backend has agent profiles (OSS-5735 — once a conversation
* starts, the agent profile is locked).
*/
showAgentProfileSwitch?: boolean;
}
export function ChatAddFileButton({
handleFileIconClick,
disabled = false,
showAgentProfileSwitch = false,
}: ChatAddFileButtonProps) {
const { t } = useTranslation("openhands");
const { conversationId } = useOptionalConversationId();
const { data: conversation } = useActiveConversation();
const [menuOpen, setMenuOpen] = React.useState(false);
const {
handleShowAgentTools,
handleShowSkills,
handleShowPlugins,
handleShowHooks,
systemModalVisible,
setSystemModalVisible,
skillsModalVisible,
setSkillsModalVisible,
pluginsModalVisible,
setPluginsModalVisible,
hooksModalVisible,
setHooksModalVisible,
systemMessage,
shouldShowAgentTools,
shouldShowHooks,
shouldShowPlugins,
} = useConversationNameContextMenu({
conversationId: conversationId ?? undefined,
executionStatus: conversation?.execution_status,
showOptions: true,
onContextMenuToggle: setMenuOpen,
});
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
if (disabled) return;
setMenuOpen((open) => !open);
};
return (
<div className="relative">
<button
type="button"
className={cn(
chatInputIconButtonClassName,
"relative shrink-0 size-6",
disabled
? "cursor-not-allowed text-[var(--oh-text-subtle)]"
: undefined,
menuOpen && !disabled && "text-white bg-white/10",
)}
aria-label={t(I18nKey.CHAT_INTERFACE$PLUS_MENU)}
aria-expanded={menuOpen}
aria-haspopup="menu"
data-testid="chat-plus-button"
onClick={handleClick}
disabled={disabled}
>
<span className="flex h-full w-full items-center justify-center">
<Plus className="h-[13px] w-[13px] shrink-0" strokeWidth={2} />
</span>
</button>
{menuOpen && (
<ToolsContextMenu
onClose={() => setMenuOpen(false)}
showAgentProfileSwitch={showAgentProfileSwitch}
onShowSkills={handleShowSkills}
onShowPlugins={handleShowPlugins}
onShowHooks={handleShowHooks}
onShowAgentTools={handleShowAgentTools}
shouldShowAgentTools={shouldShowAgentTools}
shouldShowHooks={shouldShowHooks}
shouldShowPlugins={shouldShowPlugins}
footerAction={{
testId: "add-files-and-images-button",
icon: (
<Paperclip
className="h-4 w-4 shrink-0"
strokeWidth={2}
aria-hidden
/>
),
label: t(I18nKey.CHAT_INTERFACE$ADD_FILES_AND_IMAGES),
onClick: handleFileIconClick,
}}
/>
)}
<SystemMessageModal
isOpen={systemModalVisible}
onClose={() => setSystemModalVisible(false)}
systemMessage={systemMessage || null}
/>
{skillsModalVisible && (
<SkillsModal onClose={() => setSkillsModalVisible(false)} />
)}
{pluginsModalVisible && (
<PluginsModal onClose={() => setPluginsModalVisible(false)} />
)}
{hooksModalVisible && (
<HooksModal onClose={() => setHooksModalVisible(false)} />
)}
</div>
);
}
|