Polish chat input UX (#388)
This commit is contained in:
@@ -132,10 +132,7 @@ export class PageObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async openContextFilesPicker() {
|
async openContextFilesPicker() {
|
||||||
const contextButton = this.page.getByRole("button", {
|
const contextButton = this.page.getByTestId("codebase-context-button");
|
||||||
name: "Context",
|
|
||||||
exact: true,
|
|
||||||
});
|
|
||||||
await contextButton.click();
|
await contextButton.click();
|
||||||
return new ContextFilesPickerDialog(this.page, async () => {
|
return new ContextFilesPickerDialog(this.page, async () => {
|
||||||
await contextButton.click();
|
await contextButton.click();
|
||||||
|
|||||||
@@ -8,10 +8,17 @@ export function ChatInputControls({
|
|||||||
showContextFilesPicker?: boolean;
|
showContextFilesPicker?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="pb-2 flex gap-2">
|
<div className="flex">
|
||||||
<ModelPicker />
|
<ModelPicker />
|
||||||
{showContextFilesPicker && <ContextFilesPicker />}
|
<div className="w-2"></div>
|
||||||
<ProModeSelector />
|
<ProModeSelector />
|
||||||
|
<div className="w-1"></div>
|
||||||
|
{showContextFilesPicker && (
|
||||||
|
<>
|
||||||
|
<ContextFilesPicker />
|
||||||
|
<div className="w-0.5"></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
import { FileCode, InfoIcon, Trash2 } from "lucide-react";
|
import { InfoIcon, Settings2, Trash2 } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -84,12 +84,22 @@ export function ContextFilesPicker() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<PopoverTrigger asChild>
|
<Tooltip>
|
||||||
<Button variant="ghost" className="gap-2">
|
<TooltipTrigger asChild>
|
||||||
<FileCode className="size-4" />
|
<PopoverTrigger asChild>
|
||||||
<span>Context</span>
|
<Button
|
||||||
</Button>
|
variant="ghost"
|
||||||
</PopoverTrigger>
|
className="has-[>svg]:px-2"
|
||||||
|
size="sm"
|
||||||
|
data-testid="codebase-context-button"
|
||||||
|
>
|
||||||
|
<Settings2 className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Codebase Context</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<PopoverContent className="w-96" align="start">
|
<PopoverContent className="w-96" align="start">
|
||||||
<div className="relative space-y-4">
|
<div className="relative space-y-4">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -123,25 +123,34 @@ export function ModelPicker() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||||
<DropdownMenuTrigger asChild>
|
<Tooltip>
|
||||||
<Button
|
<TooltipTrigger asChild>
|
||||||
variant="outline"
|
<DropdownMenuTrigger asChild>
|
||||||
size="sm"
|
<Button
|
||||||
className="flex items-center gap-2 h-8"
|
variant="outline"
|
||||||
>
|
size="sm"
|
||||||
<span>
|
className="flex items-center gap-2 h-8 max-w-[160px] px-2"
|
||||||
{modelDisplayName === "Auto" && (
|
>
|
||||||
<>
|
<span className="truncate">
|
||||||
<span className="text-xs text-muted-foreground">
|
{modelDisplayName === "Auto" && (
|
||||||
Model:
|
<>
|
||||||
</span>{" "}
|
<span className="text-xs text-muted-foreground">
|
||||||
</>
|
Model:
|
||||||
)}
|
</span>{" "}
|
||||||
{modelDisplayName}
|
</>
|
||||||
</span>
|
)}
|
||||||
</Button>
|
{modelDisplayName}
|
||||||
</DropdownMenuTrigger>
|
</span>
|
||||||
<DropdownMenuContent className="w-64" align="start">
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{modelDisplayName}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-64"
|
||||||
|
align="start"
|
||||||
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<DropdownMenuLabel>Cloud Models</DropdownMenuLabel>
|
<DropdownMenuLabel>Cloud Models</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function ProModeSelector() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-2 h-8 border-primary/50 bg-primary/10 hover:bg-primary/20 font-medium shadow-sm shadow-primary/10 transition-all hover:shadow-md hover:shadow-primary/15"
|
className="has-[>svg]:px-2 flex items-center gap-1.5 h-8 border-primary/50 hover:bg-primary/10 font-medium shadow-sm shadow-primary/10 transition-all hover:shadow-md hover:shadow-primary/15"
|
||||||
>
|
>
|
||||||
<Sparkles className="h-4 w-4 text-primary" />
|
<Sparkles className="h-4 w-4 text-primary" />
|
||||||
<span className="text-primary font-medium">Pro</span>
|
<span className="text-primary font-medium">Pro</span>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
SendIcon,
|
|
||||||
StopCircleIcon,
|
StopCircleIcon,
|
||||||
X,
|
X,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@@ -15,8 +14,9 @@ import {
|
|||||||
Database,
|
Database,
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
ChevronsDownUp,
|
ChevronsDownUp,
|
||||||
BarChart2,
|
|
||||||
Paperclip,
|
Paperclip,
|
||||||
|
ChartColumnIncreasing,
|
||||||
|
SendHorizontalIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
@@ -313,28 +313,10 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
|||||||
disabled={isStreaming}
|
disabled={isStreaming}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* File attachment button */}
|
|
||||||
<button
|
|
||||||
onClick={handleAttachmentClick}
|
|
||||||
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
|
|
||||||
disabled={isStreaming}
|
|
||||||
title="Attach files"
|
|
||||||
>
|
|
||||||
<Paperclip size={20} />
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref={fileInputRef}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
className="hidden"
|
|
||||||
multiple
|
|
||||||
accept=".jpg,.jpeg,.png,.gif,.webp,.txt,.md,.js,.ts,.html,.css,.json,.csv"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isStreaming ? (
|
{isStreaming ? (
|
||||||
<button
|
<button
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
className="px-2 py-2 mt-1 mr-2 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg"
|
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg"
|
||||||
title="Cancel generation"
|
title="Cancel generation"
|
||||||
>
|
>
|
||||||
<StopCircleIcon size={20} />
|
<StopCircleIcon size={20} />
|
||||||
@@ -343,23 +325,62 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!inputValue.trim() && attachments.length === 0}
|
disabled={!inputValue.trim() && attachments.length === 0}
|
||||||
className="px-2 py-2 mt-1 mr-2 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
|
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
|
||||||
title="Send message"
|
title="Send message"
|
||||||
>
|
>
|
||||||
<SendIcon size={20} />
|
<SendHorizontalIcon size={20} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="pl-2 pr-1 flex items-center justify-between">
|
<div className="pl-2 pr-1 flex items-center justify-between pb-2">
|
||||||
<ChatInputControls showContextFilesPicker={true} />
|
<div className="flex items-center">
|
||||||
<button
|
<ChatInputControls showContextFilesPicker={true} />
|
||||||
onClick={() => setShowTokenBar(!showTokenBar)}
|
{/* File attachment button */}
|
||||||
className="flex items-center px-2 py-1 text-xs text-muted-foreground hover:bg-muted rounded"
|
<TooltipProvider>
|
||||||
title={showTokenBar ? "Hide token usage" : "Show token usage"}
|
<Tooltip>
|
||||||
>
|
<TooltipTrigger asChild>
|
||||||
<BarChart2 size={14} className="mr-1" />
|
<Button
|
||||||
{showTokenBar ? "Hide tokens" : "Tokens"}
|
variant="ghost"
|
||||||
</button>
|
onClick={handleAttachmentClick}
|
||||||
|
disabled={isStreaming}
|
||||||
|
title="Attach files"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Paperclip size={20} />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Attach files</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="hidden"
|
||||||
|
multiple
|
||||||
|
accept=".jpg,.jpeg,.png,.gif,.webp,.txt,.md,.js,.ts,.html,.css,.json,.csv"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowTokenBar(!showTokenBar)}
|
||||||
|
variant="ghost"
|
||||||
|
className={`has-[>svg]:px-2 ${
|
||||||
|
showTokenBar ? "text-purple-500 bg-purple-100" : ""
|
||||||
|
}`}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<ChartColumnIncreasing size={14} />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{showTokenBar ? "Hide token usage" : "Show token usage"}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
{/* TokenBar is only displayed when showTokenBar is true */}
|
{/* TokenBar is only displayed when showTokenBar is true */}
|
||||||
{showTokenBar && <TokenBar chatId={chatId} />}
|
{showTokenBar && <TokenBar chatId={chatId} />}
|
||||||
|
|||||||
Reference in New Issue
Block a user