diff --git a/src/people/hiveChat/SimultaneousAttemptsSelector.tsx b/src/people/hiveChat/SimultaneousAttemptsSelector.tsx new file mode 100644 index 000000000..e68963224 --- /dev/null +++ b/src/people/hiveChat/SimultaneousAttemptsSelector.tsx @@ -0,0 +1,225 @@ +import React, { useState, useRef } from 'react'; +import styled from 'styled-components'; +import MaterialIcon from '@material/react-material-icon'; + +interface SimultaneousAttemptsSelectorProps { + value: number; + onChange: (value: number) => void; + disabled?: boolean; +} + +const Container = styled.div` + position: relative; + display: flex; + align-items: center; + margin-right: 12px; +`; + +const DropdownButton = styled.button<{ disabled?: boolean }>` + display: flex; + align-items: center; + justify-content: center; + padding: 8px 8px 8px 16px; + background: transparent; + border: 1px solid #5f6368; + border-radius: 8px; + color: #5f6368; + cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')}; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + height: fit-content; + align-self: center; + + &:hover:not(:disabled) { + background: rgba(95, 99, 104, 0.1); + border-color: #4285f4; + } + + &:disabled { + opacity: 0.6; + border-color: #e4e7eb; + color: #9aa0a6; + } +`; + +const Icon = styled(MaterialIcon)` + font-size: 16px; + margin-right: 4px; +`; + +const NumberText = styled.span` + font-size: 14px; + margin-right: 4px; +`; + +const ArrowIcon = styled(MaterialIcon)` + font-size: 16px; +`; + +const DropdownMenu = styled.div<{ visible: boolean }>` + position: absolute; + bottom: calc(100% + 4px); + left: 0; + background-color: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + z-index: 100; + opacity: ${(props) => (props.visible ? 1 : 0)}; + visibility: ${(props) => (props.visible ? 'visible' : 'hidden')}; + transform: translateY(${(props) => (props.visible ? '0' : '10px')}); + transition: all 0.2s ease; + width: 100%; + min-width: 120px; +`; + +const MenuItem = styled.div<{ active?: boolean }>` + padding: 10px 16px; + color: #3c4043; + font-size: 14px; + cursor: pointer; + transition: background-color 0.1s ease; + background-color: ${(props) => (props.active ? '#f1f3f6' : 'transparent')}; + display: flex; + align-items: center; + + &:hover { + background-color: #f5f8ff; + } +`; + +const Checkmark = styled(MaterialIcon)` + margin-left: auto; + font-size: 18px; + color: #4285f4; +`; + +const Tooltip = styled.div<{ visible: boolean; top: number; left: number }>` + position: fixed; + left: ${(props) => props.left}px; + top: ${(props) => props.top}px; + background: rgba(31, 41, 55, 0.95); + color: #ffffff; + padding: 8px 16px; + border-radius: 6px; + font-size: 15px; + font-weight: 500; + max-width: 200px; + opacity: ${(props) => (props.visible ? 1 : 0)}; + visibility: ${(props) => (props.visible ? 'visible' : 'hidden')}; + transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1); + white-space: nowrap; + pointer-events: none; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.1), + 0 2px 4px -1px rgba(0, 0, 0, 0.06); + z-index: 1000; + transform: translateX(-50%) translateY(-100%) translateY(-8px); + + &::after { + content: ''; + position: absolute; + left: 50%; + bottom: -8px; + transform: translateX(-50%); + border-width: 8px 8px 0 8px; + border-style: solid; + border-color: rgba(31, 41, 55, 0.95) transparent transparent transparent; + } +`; + +const SimultaneousAttemptsSelector: React.FC = ({ + value, + onChange, + disabled = false +}) => { + const [showMenu, setShowMenu] = useState(false); + const [showTooltip, setShowTooltip] = useState(false); + const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 }); + const buttonRef = useRef(null); + + const handleSelect = (selectedValue: number) => { + if (!disabled) { + onChange(selectedValue); + setShowMenu(false); + } + }; + + const toggleMenu = () => { + if (!disabled) { + setShowMenu(!showMenu); + } + }; + + const handleMouseEnter = () => { + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); + setTooltipPosition({ + top: rect.top - 8, + left: rect.left + rect.width / 2 + }); + setShowTooltip(true); + } + }; + + const handleMouseLeave = () => { + setShowTooltip(false); + }; + + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + showMenu && + !event + .composedPath() + .some((el) => el instanceof Element && el.classList.contains('dropdown-area')) + ) { + setShowMenu(false); + } + }; + + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }, [showMenu]); + + return ( + + + + {value} + + + + {showTooltip && ( + + Attempts + + )} + + + handleSelect(1)}> + 1 version + {value === 1 && } + + handleSelect(2)}> + 2 versions + {value === 2 && } + + handleSelect(3)}> + 3 versions + {value === 3 && } + + + + ); +}; + +export default SimultaneousAttemptsSelector; diff --git a/src/people/hiveChat/index.tsx b/src/people/hiveChat/index.tsx index 1fd414dc6..27e895408 100644 --- a/src/people/hiveChat/index.tsx +++ b/src/people/hiveChat/index.tsx @@ -28,6 +28,7 @@ import { ActionArtifactRenderer } from './ActionArtifactRenderer'; import ChatStatusDisplay from './ChatStatusDisplay.tsx'; import StaktrakRecorder from './StaktrakRecorder'; import axios from 'axios'; +import SimultaneousAttemptsSelector from './SimultaneousAttemptsSelector'; import SplashScreen from './ChatSplashScreen'; import { ERROR_MESSAGES } from '../utils/Constants'; @@ -312,6 +313,13 @@ const AttachButton = styled.button<{ disabled: boolean }>` } `; +const SimultaneousAttemptsContainer = styled.div` + display: flex; + align-items: center; + gap: 8px; + margin-right: 12px; +`; + const AttachIcon = styled(MaterialIcon)` font-size: 16px; margin-right: 2px; @@ -595,6 +603,7 @@ export const HiveChatView: React.FC = observer(() => { const [codeArtifact, setCodeArtifacts] = useState(); const [isActionSend, setIsActionSend] = useState(false); const [pdfUrl, setPdfUrl] = useState(''); + const [simultaneousAttempts, setSimultaneousAttempts] = useState(1); const { isEnabled: isVerboseLoggingEnabled } = useFeatureFlag('verbose_logging_sw'); const { isEnabled: isArtifactLoggingEnabled } = useFeatureFlag('log_artefact'); const { isEnabled: isPdfUploadEnabled } = useFeatureFlag('chat_pdf'); @@ -928,7 +937,8 @@ export const HiveChatView: React.FC = observer(() => { 'Build', undefined, pdfUrl, - actionArtifact + actionArtifact, + simultaneousAttempts ); if (sentMessage === undefined) { @@ -1534,7 +1544,8 @@ export const HiveChatView: React.FC = observer(() => { 'Build', undefined, pdfUrl, - actionArtifact + actionArtifact, + simultaneousAttempts ); if (sentMessage) { @@ -2085,6 +2096,11 @@ export const HiveChatView: React.FC = observer(() => { } disabled={isSending || !isWorkspaceComplete || isPATExpired} /> + {isPdfUploadEnabled && ( setIsUploadModalOpen(true)} diff --git a/src/services/index.ts b/src/services/index.ts index ee0834fe3..1890ece42 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -221,7 +221,8 @@ export class ChatService { contextTags?: ContextTag[], pdfUrl?: string, modelSelection?: string, - actionArtifact?: Artifact + actionArtifact?: Artifact, + simultaneousAttempts?: number ): Promise { try { if (!uiStore.meInfo) return undefined; @@ -236,7 +237,8 @@ export class ChatService { workspaceUUID, pdf_url: pdfUrl, modelSelection, - mode + mode, + simultaneous_attempts: simultaneousAttempts || 1 }; if ( diff --git a/src/store/__test__/chatStore.spec.ts b/src/store/__test__/chatStore.spec.ts index 3b523a1f4..016cf3e8e 100644 --- a/src/store/__test__/chatStore.spec.ts +++ b/src/store/__test__/chatStore.spec.ts @@ -136,6 +136,7 @@ describe('ChatHistoryStore', () => { undefined, undefined, 'gpt-4o', + undefined, undefined ); }); @@ -161,6 +162,7 @@ describe('ChatHistoryStore', () => { undefined, undefined, 'claude-3-5-sonnet-latest', + undefined, undefined ); }); @@ -186,6 +188,7 @@ describe('ChatHistoryStore', () => { undefined, undefined, 'o3-mini', + undefined, undefined ); }); diff --git a/src/store/chat.ts b/src/store/chat.ts index 96807b42d..8c9404f6a 100644 --- a/src/store/chat.ts +++ b/src/store/chat.ts @@ -360,6 +360,7 @@ export class ChatHistoryStore implements ChatStore { contextTags?: ContextTag[], pdfUrl?: string, actionArtifact?: Artifact, + simultaneousAttempts?: number, artifacts?: Artifact[] ): Promise { try { @@ -372,7 +373,8 @@ export class ChatHistoryStore implements ChatStore { contextTags, pdfUrl, modelSelection, - actionArtifact + actionArtifact, + simultaneousAttempts ); if (newMessage) { if (!this.chatMessages[chat_id]) {