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
44 changes: 22 additions & 22 deletions client/webui/frontend/src/lib/components/chat/SessionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ import { useNavigate } from "react-router-dom";

import { Trash2, Check, X, Pencil, MessageCircle, FolderInput, MoreHorizontal, PanelsTopLeft, Loader2 } from "lucide-react";

import { useChatContext, useConfigContext } from "@/lib/hooks";
import { api } from "@/lib/api";
import { getErrorMessage } from "@/lib/utils/api";
import { formatTimestamp } from "@/lib/utils/format";
import { Button } from "@/lib/components/ui/button";
import { Badge } from "@/lib/components/ui/badge";
import { Spinner } from "@/lib/components/ui/spinner";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/lib/components/ui/select";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/lib/components/ui/tooltip";
import { MoveSessionDialog } from "@/lib/components/chat/MoveSessionDialog";
import { SessionSearch } from "@/lib/components/chat/SessionSearch";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/lib/components/ui/dropdown-menu";
import { useChatContext, useConfigContext } from "@/lib/hooks";
import type { Project, Session } from "@/lib/types";
import { formatTimestamp, getErrorMessage } from "@/lib/utils";
import { MoveSessionDialog, ProjectBadge, SessionSearch } from "@/lib/components/chat";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Spinner,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/lib/components/ui";

interface PaginatedSessionsResponse {
data: Session[];
Expand Down Expand Up @@ -310,7 +319,7 @@ export const SessionList: React.FC<SessionListProps> = ({ projects = [] }) => {
<ul>
{filteredSessions.map(session => (
<li key={session.id} className="group my-2 pr-4">
<div className={`flex items-center gap-2 rounded px-2 py-2 ${session.id === sessionId ? "bg-muted" : ""}`}>
<div className={`flex items-center gap-2 rounded-sm px-2 py-2 ${session.id === sessionId ? "bg-muted dark:bg-muted/50" : ""}`}>
{editingSessionId === session.id ? (
<input
ref={inputRef}
Expand Down Expand Up @@ -342,16 +351,7 @@ export const SessionList: React.FC<SessionListProps> = ({ projects = [] }) => {
</div>
<span className="text-muted-foreground truncate text-xs">{formatSessionDate(session.updatedTime)}</span>
</div>
{session.projectName && (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="outline" className="bg-primary/10 border-primary/30 text-primary max-w-[120px] flex-shrink-0 justify-start px-2 py-0.5 text-xs font-semibold shadow-sm">
<span className="block truncate">{session.projectName}</span>
</Badge>
</TooltipTrigger>
<TooltipContent>{session.projectName}</TooltipContent>
</Tooltip>
)}
{session.projectName && <ProjectBadge text={session.projectName} />}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This badge was used in 4 places so extracted it to a component.

</div>
</button>
)}
Expand Down
16 changes: 6 additions & 10 deletions client/webui/frontend/src/lib/components/chat/SessionSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useState, useCallback, useEffect } from "react";
import { Search, X } from "lucide-react";
import { Input } from "@/lib/components/ui/input";
import { Button } from "@/lib/components/ui/button";
import { Badge } from "@/lib/components/ui/badge";
import { useDebounce } from "@/lib/hooks/useDebounce";
import type { Session } from "@/lib/types";

import { api } from "@/lib/api";
import { ProjectBadge } from "@/lib/components/chat";
import { Button, Input } from "@/lib/components/ui";
import { useDebounce } from "@/lib/hooks";
import type { Session } from "@/lib/types";

interface SessionSearchProps {
onSessionSelect: (sessionId: string) => void;
Expand Down Expand Up @@ -98,11 +98,7 @@ export const SessionSearch = ({ onSessionSelect, projectId }: SessionSearchProps
<button key={session.id} onClick={() => handleSessionClick(session.id)} className="hover:bg-accent hover:text-accent-foreground w-full rounded-sm px-3 py-2 text-left text-sm">
<div className="mb-1 flex items-center justify-between gap-2">
<div className="flex-1 truncate font-medium">{session.name || "Untitled Session"}</div>
{session.projectName && (
<Badge variant="outline" className="bg-primary/10 border-primary/30 text-primary flex-shrink-0 px-2 py-0.5 text-xs font-semibold shadow-sm">
{session.projectName}
</Badge>
)}
{session.projectName && <ProjectBadge text={session.projectName} />}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use extracted badge.

</div>
<div className="text-muted-foreground text-xs">{new Date(session.updatedTime).toLocaleDateString()}</div>
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useState, useEffect } from "react";
import { Download, ChevronDown, Trash, Info, ChevronUp, CircleAlert } from "lucide-react";
import { Download, ChevronDown, Trash, Info, ChevronUp, CircleAlert, Pencil } from "lucide-react";

import { Button, Spinner, Badge } from "@/lib/components/ui";
import { FileIcon } from "../file/FileIcon";
import { Button, Spinner } from "@/lib/components/ui";
import { cn } from "@/lib/utils";

import { FileIcon, ProjectBadge } from "../file";

const ErrorState: React.FC<{ message: string }> = ({ message }) => (
<div className="w-full rounded-lg border border-[var(--color-error-w100)] bg-[var(--color-error-wMain-50)] p-3">
<div className="text-sm text-[var(--color-error-wMain)]">Error: {message}</div>
Expand All @@ -26,6 +27,7 @@ export interface ArtifactBarProps {
onDelete?: () => void;
onInfo?: () => void;
onExpand?: () => void;
onEdit?: () => void;
};
// For creation progress
bytesTransferred?: number;
Expand Down Expand Up @@ -211,11 +213,7 @@ export const ArtifactBar: React.FC<ArtifactBarProps> = ({
{hasDescription ? displayDescription : filename.length > 50 ? `${filename.substring(0, 47)}...` : filename}
</div>
{/* Project badge */}
{source === "project" && (
<Badge variant="outline" className="bg-primary/10 border-primary/30 text-primary px-2 py-0.5 text-xs font-semibold shadow-sm">
Project
</Badge>
)}
{source === "project" && <ProjectBadge />}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use extracted badge

</div>

{/* Secondary line: Filename (if description shown) or status */}
Expand Down Expand Up @@ -298,6 +296,24 @@ export const ArtifactBar: React.FC<ArtifactBarProps> = ({
</Button>
)}

{status === "completed" && actions?.onEdit && !isDeleted && (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add edit option to artifact bar:

image

<Button
variant="ghost"
size="icon"
onClick={e => {
e.stopPropagation();
try {
actions.onEdit?.();
} catch (error) {
console.error("Edit failed:", error);
}
}}
tooltip="Edit Description"
>
<Pencil className="h-4 w-4" />
</Button>
)}

{status === "completed" && actions?.onDelete && !isDeleted && (
<Button
variant="ghost"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { useProjectContext } from "@/lib/providers";
import type { FileAttachment } from "@/lib/types";
import { api } from "@/lib/api";
import { downloadFile, parseArtifactUri } from "@/lib/utils/download";
import { formatBytes, formatRelativeTime } from "@/lib/utils/format";
import { Spinner } from "@/lib/components/ui/spinner";

import { MessageBanner } from "../../common";
import { ContentRenderer } from "../preview/ContentRenderer";
import { getFileContent, getRenderType } from "../preview/previewUtils";
import { ArtifactBar } from "../artifact/ArtifactBar";
import { ArtifactTransitionOverlay } from "../artifact/ArtifactTransitionOverlay";
import { Spinner } from "../../ui";
import { FileDetails } from "./FileDetails";

type ArtifactMessageProps = (
| {
Expand Down Expand Up @@ -417,32 +417,7 @@ export const ArtifactMessage: React.FC<ArtifactMessageProps> = props => {
const infoContent = useMemo(() => {
if (!isInfoExpanded || !artifact) return null;

return (
<div className="space-y-2 text-sm">
{artifact.description && (
<div>
<span className="text-secondary-foreground">Description:</span>
<div className="mt-1">{artifact.description}</div>
</div>
)}
<div className="grid grid-cols-2 gap-2">
<div>
<span className="text-secondary-foreground">Size:</span>
<div>{formatBytes(artifact.size)}</div>
</div>
<div>
<span className="text-secondary-foreground">Modified:</span>
<div>{formatRelativeTime(artifact.last_modified)}</div>
</div>
</div>
{artifact.mime_type && (
<div>
<span className="text-secondary-foreground">Type:</span>
<div>{artifact.mime_type}</div>
</div>
)}
</div>
);
return <FileDetails description={artifact.description ?? undefined} size={artifact.size} lastModified={artifact.last_modified} mimeType={artifact.mime_type} />;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use extracted file details.

}, [isInfoExpanded, artifact]);

// Determine what content to show in expanded area - can show both info and content
Expand Down
39 changes: 39 additions & 0 deletions client/webui/frontend/src/lib/components/chat/file/FileDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";

import { formatBytes, formatRelativeTime } from "@/lib/utils/format";

interface FileDetailsProps {
description?: string;
size: number;
lastModified: string;
mimeType?: string;
}

export const FileDetails: React.FC<FileDetailsProps> = ({ description, size, lastModified, mimeType }) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted FileDetails (used in multiple places)

image

return (
<div className="space-y-2 text-sm">
{description && (
<div>
<span className="text-secondary-foreground">Description:</span>
<div className="mt-1">{description}</div>
</div>
)}
<div className="grid grid-cols-2 gap-2">
<div>
<span className="text-secondary-foreground">Size:</span>
<div>{formatBytes(size)}</div>
</div>
<div>
<span className="text-secondary-foreground">Modified:</span>
<div>{formatRelativeTime(lastModified)}</div>
</div>
</div>
{mimeType && (
<div>
<span className="text-secondary-foreground">Type:</span>
<div>{mimeType}</div>
</div>
)}
</div>
);
};
15 changes: 15 additions & 0 deletions client/webui/frontend/src/lib/components/chat/file/FileLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { File } from "lucide-react";

export const FileLabel = ({ fileName, fileSize }: { fileName: string; fileSize: number }) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted FileLabel (used in multiple places)

image

return (
<div className="flex items-center gap-3">
<File className="text-muted-foreground size-5 shrink-0" />
<div className="overflow-hidden">
<div className="truncate" title={fileName}>
{fileName}
</div>
<div className="text-muted-foreground text-xs">{(fileSize / 1024).toFixed(1)} KB</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Badge, Tooltip, TooltipContent, TooltipTrigger } from "@/lib";

export const ProjectBadge = ({ text = "Project", className = "" }: { text?: string; className?: string }) => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted ProjectBadge (used in multiple places)

image

return (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="default" className={`max-w-[120px] ${className}`}>
<span className="block truncate font-semibold">{text}</span>
</Badge>
</TooltipTrigger>
<TooltipContent>{text}</TooltipContent>
</Tooltip>
);
};
2 changes: 2 additions & 0 deletions client/webui/frontend/src/lib/components/chat/file/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from "./ArtifactMessage";
export * from "./FileBadge";
export * from "./FileDetails";
export * from "./FileIcon";
export * from "./FileMessage";
export * from "./fileUtils";
export * from "./ProjectBadge";
3 changes: 3 additions & 0 deletions client/webui/frontend/src/lib/components/chat/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export { AudioRecorder } from "./AudioRecorder";
export { ChatInputArea } from "./ChatInputArea";
export { ChatMessage } from "./ChatMessage";
export { ChatSessionDeleteDialog } from "./ChatSessionDeleteDialog";
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add missing exports.

export { ChatSessionDialog } from "./ChatSessionDialog";
export { ChatSessions } from "./ChatSessions";
export { ChatSidePanel } from "./ChatSidePanel";
export { LoadingMessageRow } from "./LoadingMessageRow";
Expand All @@ -9,4 +11,5 @@ export { MoveSessionDialog } from "./MoveSessionDialog";
export { VariableDialog } from "./VariableDialog";
export { SessionSearch } from "./SessionSearch";
export { MessageHoverButtons } from "./MessageHoverButtons";
export * from "./file";
export * from "./selection";
Loading
Loading