Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 58 additions & 20 deletions webview-ui/src/components/chat/ChatTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ import Thumbnails from "../common/Thumbnails"
import ModeSelector from "./ModeSelector"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
import { VolumeX, Pin, Check } from "lucide-react"
import { IconButton } from "./IconButton"
import { IndexingStatusDot } from "./IndexingStatusBadge"
import { VolumeX, Pin, Check, Image, WandSparkles, SendHorizontal } from "lucide-react"
import { IndexingStatusBadge } from "./IndexingStatusBadge"
import { cn } from "@/lib/utils"
import { usePromptHistory } from "./hooks/usePromptHistory"

Expand Down Expand Up @@ -962,24 +961,49 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
)}

<div className="absolute top-1 right-1 z-30">
<IconButton
iconClass={isEnhancingPrompt ? "codicon-loading" : "codicon-sparkle"}
<button
aria-label={t("chat:enhancePrompt")}
title={t("chat:enhancePrompt")}
disabled={sendingDisabled}
isLoading={isEnhancingPrompt}
onClick={handleEnhancePrompt}
className="opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground"
/>
onClick={!sendingDisabled ? handleEnhancePrompt : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!sendingDisabled && "cursor-pointer",
sendingDisabled &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<WandSparkles className={cn("w-4 h-4", isEnhancingPrompt && "animate-spin")} />
</button>
</div>

<div className="absolute bottom-1 right-1 z-30">
<IconButton
iconClass="codicon-send"
<button
aria-label={t("chat:sendMessage")}
title={t("chat:sendMessage")}
disabled={sendingDisabled}
onClick={onSend}
className="opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground"
/>
onClick={!sendingDisabled ? onSend : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-all duration-150",
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!sendingDisabled && "cursor-pointer",
sendingDisabled &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
)}>
<SendHorizontal className="w-4 h-4" />
</button>
</div>

{!inputValue && (
Expand Down Expand Up @@ -1145,14 +1169,28 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</div>

<div className={cn("flex", "items-center", "gap-0.5", "shrink-0")}>
<IndexingStatusDot />
<IconButton
iconClass="codicon-device-camera"
<IndexingStatusBadge />
<button
aria-label={t("chat:addImages")}
title={t("chat:addImages")}
disabled={shouldDisableImages}
onClick={onSelectImages}
className="mr-1"
/>
onClick={!shouldDisableImages ? onSelectImages : undefined}
className={cn(
"relative inline-flex items-center justify-center",
"bg-transparent border-none p-1.5",
"rounded-md min-w-[28px] min-h-[28px]",
"text-vscode-foreground opacity-85",
"transition-all duration-150",
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
!shouldDisableImages && "cursor-pointer",
shouldDisableImages &&
"opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent",
"mr-1",
)}>
<Image className="w-4 h-4" />
</button>
</div>
</div>
</div>
Expand Down
24 changes: 15 additions & 9 deletions webview-ui/src/components/chat/IndexingStatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React, { useState, useEffect, useMemo } from "react"
import { Database } from "lucide-react"
import { cn } from "@src/lib/utils"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { useTooltip } from "@/hooks/useTooltip"
import { CodeIndexPopover } from "./CodeIndexPopover"
import type { IndexingStatus, IndexingStatusUpdateMessage } from "@roo/ExtensionMessage"

interface IndexingStatusDotProps {
interface IndexingStatusBadgeProps {
className?: string
}

export const IndexingStatusDot: React.FC<IndexingStatusDotProps> = ({ className }) => {
export const IndexingStatusBadge: React.FC<IndexingStatusBadgeProps> = ({ className }) => {
const { t } = useAppTranslation()
const { showTooltip, handleMouseEnter, handleMouseLeave, cleanup } = useTooltip({ delay: 300 })
const [isHovered, setIsHovered] = useState(false)
Expand Down Expand Up @@ -77,23 +78,23 @@ export const IndexingStatusDot: React.FC<IndexingStatusDotProps> = ({ className
handleMouseLeave()
}

// Get status color classes based on status and hover state
// Get status color classes for the badge dot
const getStatusColorClass = () => {
const statusColors = {
Standby: {
default: "bg-vscode-descriptionForeground/40",
hover: "bg-vscode-descriptionForeground/60",
default: "bg-vscode-descriptionForeground/60",
hover: "bg-vscode-descriptionForeground/80",
},
Indexing: {
default: "bg-yellow-500/40 animate-pulse",
default: "bg-yellow-500 animate-pulse",
hover: "bg-yellow-500 animate-pulse",
},
Indexed: {
default: "bg-green-500/40",
default: "bg-green-500",
hover: "bg-green-500",
},
Error: {
default: "bg-red-500/40",
default: "bg-red-500",
hover: "bg-red-500",
},
}
Expand All @@ -117,12 +118,17 @@ export const IndexingStatusDot: React.FC<IndexingStatusDotProps> = ({ className
"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
"active:bg-[rgba(255,255,255,0.1)]",
"cursor-pointer",
className,
)}
aria-label={getTooltipText()}>
{/* File search icon */}
<Database className="w-4 h-4 text-vscode-foreground" />

{/* Status dot badge */}
<span
className={cn(
"inline-block w-2 h-2 rounded-full relative z-10 transition-colors duration-200",
"absolute top-0.5 right-0.5 w-1.5 h-1.5 rounded-full transition-colors duration-200",
getStatusColorClass(),
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ vi.mock("@src/context/ExtensionStateContext")
const getEnhancePromptButton = () => {
return screen.getByRole("button", {
name: (_, element) => {
// Find the button with the sparkle icon
return element.querySelector(".codicon-sparkle") !== null
// Find the button with the wand sparkles icon (Lucide React)
return element.querySelector(".lucide-wand-sparkles") !== null
},
})
}
Expand Down Expand Up @@ -154,8 +154,9 @@ describe("ChatTextArea", () => {
const enhanceButton = getEnhancePromptButton()
fireEvent.click(enhanceButton)

const loadingSpinner = screen.getByText("", { selector: ".codicon-loading" })
expect(loadingSpinner).toBeInTheDocument()
// Check if the WandSparkles icon has the animate-spin class
const animatingIcon = enhanceButton.querySelector(".animate-spin")
expect(animatingIcon).toBeInTheDocument()
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { render, screen, fireEvent, waitFor, act } from "@/utils/test-utils"

import { vscode } from "@src/utils/vscode"

import { IndexingStatusDot } from "../IndexingStatusBadge"
import { IndexingStatusBadge } from "../IndexingStatusBadge"

vi.mock("@/i18n/setup", () => ({
__esModule: true,
Expand Down Expand Up @@ -104,9 +104,9 @@ vi.mock("@/i18n/TranslationContext", () => ({
}),
}))

describe("IndexingStatusDot", () => {
describe("IndexingStatusBadge", () => {
const renderComponent = (props = {}) => {
return render(<IndexingStatusDot {...props} />)
return render(<IndexingStatusBadge {...props} />)
}

beforeEach(() => {
Expand Down
Loading