Skip to content
Open
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
79 changes: 46 additions & 33 deletions frontend/components/ChatInputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
DropdownMenuTrigger,
} from "@/frontend/components/ui/dropdown-menu";
import { FaTools } from "react-icons/fa";
import { ConditionalTooltip } from "./ui/conditional-tooltip";

// Extended UIMessage type to include attachments
type ExtendedUIMessage = UIMessage & {
Expand Down Expand Up @@ -1419,31 +1420,36 @@ function PureInputField({
<div className="absolute right-2 top-2 flex items-center gap-2">
{/* Enhance Button */}
{!isImageGenMode && input.trim().length > 0 && !isGuest && (
<button
type="button"
onClick={handleEnhancePrompt}
disabled={
isEnhancing ||
status === "streaming" ||
status === "submitted" ||
!input.trim()
}
className={cn(
"p-2.5 rounded-lg flex items-center justify-center cursor-pointer transition-all",
"bg-primary/10 hover:bg-primary/20 text-primary",
"border border-primary/20 hover:border-primary/30",
"disabled:opacity-50 disabled:cursor-not-allowed",
"shadow-sm hover:shadow-md"
)}
aria-label="Enhance prompt"
title="Enhance your prompt with AI"
<ConditionalTooltip
content="Enhance prompt"
showTooltip={!isEnhancing}
side="top"
>
{isEnhancing ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<SparklesIcon />
)}
</button>
<button
type="button"
onClick={handleEnhancePrompt}
disabled={
isEnhancing ||
status === "streaming" ||
status === "submitted" ||
!input.trim()
}
className={cn(
"p-2.5 rounded-lg flex items-center justify-center cursor-pointer transition-all",
"bg-primary/10 hover:bg-primary/20 text-primary",
"border border-primary/20 hover:border-primary/30",
"disabled:opacity-50 disabled:cursor-not-allowed",
"shadow-sm hover:shadow-md"
)}
aria-label="Enhance prompt"
>
{isEnhancing ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<SparklesIcon />
)}
</button>
</ConditionalTooltip>
)}

{/* Voice Input Button */}
Expand Down Expand Up @@ -1524,16 +1530,23 @@ function PureInputField({
onOpenChange={setIsToolsDropdownOpen}
>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className={cn(
"flex items-center rounded-lg text-xs font-medium transition-all duration-200",
"hover:bg-accent hover:text-primary h-8 w-8 md:h-10 md:w-10"
)}
<ConditionalTooltip
className="text-xs p-2"
content="Tools"
side="top"
showTooltip={!isToolsDropdownOpen}
>
<FaTools className="h-3 w-3 md:h-4 md:w-4 text-primary" />
</Button>
<Button
variant="ghost"
size="sm"
className={cn(
"flex items-center rounded-lg text-xs font-medium transition-all duration-200",
"hover:bg-accent hover:text-primary h-8 w-8 md:h-10 md:w-10"
)}
>
<FaTools className="h-3 w-3 md:h-4 md:w-4 text-primary" />
</Button>
</ConditionalTooltip>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
Expand Down
133 changes: 88 additions & 45 deletions frontend/components/ChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { PanelLeft } from "@/frontend/components/ui/icons/panel-left";
import { AnimateIcon } from "@/frontend/components/ui/icons/icon";
import FreeTierShowcase from "./FreeTierShowcase";
import CapybaraIcon from "./ui/CapybaraIcon";
import { ConditionalTooltip } from "./ui/conditional-tooltip";

interface ChatInterfaceProps {
threadId: string;
Expand Down Expand Up @@ -470,7 +471,11 @@ export default function ChatInterface({
.split("|")
.filter((img): img is string => {
// Filter out empty strings and only keep valid HTTP(S) URLs
return typeof img === "string" && img.trim().length > 0 && /^https?:\/\//.test(img.trim());
return (
typeof img === "string" &&
img.trim().length > 0 &&
/^https?:\/\//.test(img.trim())
);
})
.slice(0, 15);
return images;
Expand All @@ -481,7 +486,10 @@ export default function ChatInterface({
};

const extractedImgs = extractImagesFromContent(aiMessage.content);
if (extractedImgs.length > 0 && extractedImgs.every((img) => typeof img === "string")) {
if (
extractedImgs.length > 0 &&
extractedImgs.every((img) => typeof img === "string")
) {
aiMessage.webSearchImgs = extractedImgs;
setMessages((prev) => {
const updated = prev.map((m) =>
Expand Down Expand Up @@ -2142,27 +2150,41 @@ export default function ChatInterface({
: "border-none mr-6 rounded-md py-2"
)}
>
<ShareButton
threadId={threadId}
variant={isDarkTheme ? "outline" : "secondary"}
className="text-primary"
/>
<ConditionalTooltip
content="Share conversation"
showTooltip={true}
side="bottom"
className="p-2 text-xs"
>
<ShareButton
threadId={threadId}
variant={isDarkTheme ? "outline" : "secondary"}
className="text-primary"
/>
</ConditionalTooltip>

<Button
onClick={handleToggleNavigator}
variant={isDarkTheme ? "outline" : "secondary"}
size="icon"
className={cn(
"focus:outline-none focus:ring-0 shadow-sm rounded-md "
)}
aria-label={
isNavigatorVisible
? "Hide message browser"
: "Show message browser"
}
<ConditionalTooltip
content="Toggle message browser"
showTooltip={!isNavigatorVisible}
side="bottom"
className="p-2 text-xs"
>
<MessageCircleIcon className="h-5 w-5 text-primary" />
</Button>
<Button
onClick={handleToggleNavigator}
variant={isDarkTheme ? "outline" : "secondary"}
size="icon"
className={cn(
"focus:outline-none focus:ring-0 shadow-sm rounded-md "
)}
aria-label={
isNavigatorVisible
? "Hide message browser"
: "Show message browser"
}
>
<MessageCircleIcon className="h-5 w-5 text-primary" />
</Button>
</ConditionalTooltip>

<ThemeToggleButton variant="inline" />
</div>
Expand Down Expand Up @@ -2578,43 +2600,64 @@ const AppPanelTrigger = () => {
}`}
>
<AnimateIcon animateOnHover>
<Button
size="icon"
variant={isDarkTheme ? "outline" : "secondary"}
onClick={handleToggle}
aria-label="Toggle sidebar"
<ConditionalTooltip
content="Toggle sidebar"
showTooltip={state === "collapsed"}
side="bottom"
className="p-2 text-xs"
>
<PanelLeft className={"text-primary"} />
</Button>
<Button
size="icon"
variant={isDarkTheme ? "outline" : "secondary"}
onClick={handleToggle}
aria-label="Toggle sidebar"
>
<PanelLeft className={"text-primary"} />
</Button>
</ConditionalTooltip>
</AnimateIcon>

<div className="rounded-md md:block">
<Button
onClick={handleOpenSearch}
size="icon"
variant={isDarkTheme ? "outline" : "secondary"}
className={` ${state === "collapsed" ? "ml-2" : "hidden"}`}
<ConditionalTooltip
content="Search"
showTooltip={state === "collapsed"}
side="bottom"
className="p-2 text-xs"
>
<SearchIcon className="text-primary" />
</Button>
<Button
onClick={handleOpenSearch}
size="icon"
variant={isDarkTheme ? "outline" : "secondary"}
className={` ${state === "collapsed" ? "ml-2" : "hidden"}`}
>
<SearchIcon className="text-primary" />
</Button>
</ConditionalTooltip>
<GlobalSearchDialog
isOpen={isSearchDialogOpen}
onClose={handleCloseSearch}
/>
</div>

<div className="rounded-md">
<Button
onClick={() => {
if (location.pathname !== "/chat") navigate("/chat");
}}
disabled={location.pathname === "/chat"}
size="icon"
variant={isDarkTheme ? "outline" : "secondary"}
className={` ${state === "collapsed" ? "ml-2" : "hidden"}`}
<ConditionalTooltip
content="New Chat"
showTooltip={state === "collapsed"}
side="bottom"
className="p-2 text-xs"
>
<PlusIcon className="h-5 w-5 text-primary" />
</Button>
<Button
onClick={() => {
if (location.pathname !== "/chat") navigate("/chat");
}}
disabled={location.pathname === "/chat"}
size="icon"
variant={isDarkTheme ? "outline" : "secondary"}
className={` ${state === "collapsed" ? "ml-2" : "hidden"}`}
>
<PlusIcon className="h-5 w-5 text-primary" />
</Button>
</ConditionalTooltip>
</div>
</div>
);
Expand Down
64 changes: 30 additions & 34 deletions frontend/components/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { cn } from "@/lib/utils";
import { toast } from "./ui/Toast";
import { devError } from "@/lib/logger";
import { LinkIcon } from "./ui/icons/LinkIcon";
import { ConditionalTooltip } from "./ui/conditional-tooltip";

interface FileUploadProps {
onFilesUploaded: (attachments: FileAttachment[]) => void;
Expand Down Expand Up @@ -231,41 +232,36 @@ export default function FileUpload({
/>

{/* Upload button - Enhanced with better visual feedback */}
<Button
type="button"
variant="ghost"
size="icon"
onClick={handleButtonClick}
disabled={disabled || uploadingFiles.length > 0}
className={cn(
"relative h-6 w-6 rounded-xl transition-all duration-200 mobile-touch",
uploadingFiles.length > 0
? "bg-primary/10 text-primary"
: "hover:bg-muted/80 hover:scale-105 active:scale-95"
)}
// title={
// uploadingFiles.length > 0
// ? "Uploading files..."
// : "Upload files (Images, PDFs, text, and Word documents)"
// }
<ConditionalTooltip
content="Upload files"
showTooltip={uploadingFiles.length === 0}
side="top"
className="p-2 text-xs"
>
{uploadingFiles.length > 0 ? (
<div className="relative">
<Loader2 className="w-4 h-4 animate-spin text-primary" />
{/* Pulsing background effect */}
<div className="absolute inset-0 bg-primary/20 rounded-full animate-ping" />
</div>
) : (
<LinkIcon className="w-4 h-4 text-primary transition-transform group-hover:rotate-12" />
)}

{/* Upload count indicator */}
{/* {uploadingFiles.length > 0 && (
<div className="absolute -top-1 -right-1 bg-primary text-primary-foreground text-xs rounded-full w-5 h-5 flex items-center justify-center font-medium animate-pulse">
{uploadingFiles.length}
</div>
)} */}
</Button>
<Button
type="button"
variant="ghost"
size="icon"
onClick={handleButtonClick}
disabled={disabled || uploadingFiles.length > 0}
className={cn(
"relative h-6 w-6 rounded-xl transition-all duration-200 mobile-touch",
uploadingFiles.length > 0
? "bg-primary/10 text-primary"
: "hover:bg-muted/80 hover:scale-105 active:scale-95"
)}
>
{uploadingFiles.length > 0 ? (
<div className="relative">
<Loader2 className="w-4 h-4 animate-spin text-primary" />
{/* Pulsing background effect */}
<div className="absolute inset-0 bg-primary/20 rounded-full animate-ping" />
</div>
) : (
<LinkIcon className="w-4 h-4 text-primary transition-transform group-hover:rotate-12" />
)}
</Button>
</ConditionalTooltip>

{/* Drag and drop overlay - Enhanced responsive design */}
{isDragOver && (
Expand Down
Loading