2025-05-28 12:26:47 -07:00
|
|
|
import React from "react";
|
feat: Update chat UI to be Open SWE specific (#76) (#91)
* init repo-selector
* add branch selector UI, extend GH hook w branch info
* add reconnectOnMount: true resumeStream, remove repo description from selector, fix styling
* styling repo + branch
* udpate getBranch calls to include targetRepo arg
* format
* add search to repo + branch selectors
* add settings icon link to /github for dev convenience
* add default branch selection
* format
* minor cleanup
* init task list UI demo
* individual task component
* tasks real data
* task visual improvement
* persisting tasks, navigate to thread onClick
* task UI
* TaskSidebar, Navigate to tasks
* org tasks by thread
* group by thread
* improve sidebar, task-list, rm github modal on refresh
* collapse sidebar works when viewing a thread
* remove micro-tasks expansion from threads component, change taskId to taskIndex for persistence + simplicity
* init config sidebar ui
* open and close config sidebar
* add ref for getAllTasks + add graph_id metadata
* format
* create threadItem component
* format
* add threadUtils
* centralize + refactor types
* format
* improve thread status, realtime updates
* fix scroll issue
* remove temperature from config-sidebar
* Change model via sidebar
* fix flashing on updates
* init plan UI
* centralize types
* self CR: refactoring, cleanup, remove comments, add todos
* CR: clean comments + logs, fix repo selector bug
* fix: Update example env file in web (#77)
* disable auto scroll on pageload
* reposelector: use default branch not main master
* remove archived tab temp, default branch listed first in branchSelector dropdown
* auto select first repo
* improve status indicator
* refactor status indicator
* simplify status implementation to use Langgraph
* util moved from provider to thread-utils
* linting, improve task completion logic
* feat: Support followup requests (#60)
* feat: Support followup requests
* cr
* cr
* fix
* account for pr already exists
* remove task provider
* fix: Better prompting and context provided for codebase structure awareness (#85)
* fix: Better prompting and context provided for codebase structure awareness
* cr
* linting + skeleton loader
* fix: branch selector, add github api pagination
* small branch update
* update task completion status realtime
* threadItem stream remove flashing/polling
* feat: Create a shared package (#89)
* feat: Create a shared package
* formatting
* fix: drop passing target repo to getBranchName
* drop docs
* use shared package for config fields in config sidebar
* fix: styling and structure for repo/branch selectors
* fix: use shared types/objects for config fields
* default key for configs
* fix: improved styling for threads
* fix renering num tasks
* disable repo branch selector when chat started
* cr
* cr
* bump deps
* fix: rendering plan in interrupt
* cr
* cr
---------
Co-authored-by: Dylan Boudro <121908331+starmorph@users.noreply.github.com>
2025-06-08 17:29:40 -07:00
|
|
|
import { File, X as XIcon } from "lucide-react";
|
2025-05-28 12:26:47 -07:00
|
|
|
import type { Base64ContentBlock } from "@langchain/core/messages";
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
import Image from "next/image";
|
|
|
|
|
export interface MultimodalPreviewProps {
|
|
|
|
|
block: Base64ContentBlock;
|
|
|
|
|
removable?: boolean;
|
|
|
|
|
onRemove?: () => void;
|
|
|
|
|
className?: string;
|
|
|
|
|
size?: "sm" | "md" | "lg";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const MultimodalPreview: React.FC<MultimodalPreviewProps> = ({
|
|
|
|
|
block,
|
|
|
|
|
removable = false,
|
|
|
|
|
onRemove,
|
|
|
|
|
className,
|
|
|
|
|
size = "md",
|
|
|
|
|
}) => {
|
|
|
|
|
// Image block
|
|
|
|
|
if (
|
|
|
|
|
block.type === "image" &&
|
|
|
|
|
block.source_type === "base64" &&
|
|
|
|
|
typeof block.mime_type === "string" &&
|
|
|
|
|
block.mime_type.startsWith("image/")
|
|
|
|
|
) {
|
|
|
|
|
const url = `data:${block.mime_type};base64,${block.data}`;
|
|
|
|
|
let imgClass: string = "rounded-md object-cover h-16 w-16 text-lg";
|
|
|
|
|
if (size === "sm") imgClass = "rounded-md object-cover h-10 w-10 text-base";
|
|
|
|
|
if (size === "lg") imgClass = "rounded-md object-cover h-24 w-24 text-xl";
|
|
|
|
|
return (
|
|
|
|
|
<div className={cn("relative inline-block", className)}>
|
|
|
|
|
<Image
|
|
|
|
|
src={url}
|
|
|
|
|
alt={String(block.metadata?.name || "uploaded image")}
|
|
|
|
|
className={imgClass}
|
|
|
|
|
width={size === "sm" ? 16 : size === "md" ? 32 : 48}
|
|
|
|
|
height={size === "sm" ? 16 : size === "md" ? 32 : 48}
|
|
|
|
|
/>
|
|
|
|
|
{removable && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="absolute top-1 right-1 z-10 rounded-full bg-gray-500 text-white hover:bg-gray-700"
|
|
|
|
|
onClick={onRemove}
|
|
|
|
|
aria-label="Remove image"
|
|
|
|
|
>
|
|
|
|
|
<XIcon className="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PDF block
|
|
|
|
|
if (
|
|
|
|
|
block.type === "file" &&
|
|
|
|
|
block.source_type === "base64" &&
|
|
|
|
|
block.mime_type === "application/pdf"
|
|
|
|
|
) {
|
|
|
|
|
const filename =
|
|
|
|
|
block.metadata?.filename || block.metadata?.name || "PDF file";
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"relative flex items-start gap-2 rounded-md border bg-gray-100 px-3 py-2",
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex flex-shrink-0 flex-col items-start justify-start">
|
|
|
|
|
<File
|
|
|
|
|
className={cn(
|
|
|
|
|
"text-teal-700",
|
|
|
|
|
size === "sm" ? "h-5 w-5" : "h-7 w-7",
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<span
|
|
|
|
|
className={cn("min-w-0 flex-1 text-sm break-all text-gray-800")}
|
|
|
|
|
style={{ wordBreak: "break-all", whiteSpace: "pre-wrap" }}
|
|
|
|
|
>
|
|
|
|
|
{String(filename)}
|
|
|
|
|
</span>
|
|
|
|
|
{removable && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="ml-2 self-start rounded-full bg-gray-200 p-1 text-teal-700 hover:bg-gray-300"
|
|
|
|
|
onClick={onRemove}
|
|
|
|
|
aria-label="Remove PDF"
|
|
|
|
|
>
|
|
|
|
|
<XIcon className="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback for unknown types
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 rounded-md border bg-gray-100 px-3 py-2 text-gray-500",
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<File className="h-5 w-5 flex-shrink-0" />
|
|
|
|
|
<span className="truncate text-xs">Unsupported file type</span>
|
|
|
|
|
{removable && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="ml-2 rounded-full bg-gray-200 p-1 text-gray-500 hover:bg-gray-300"
|
|
|
|
|
onClick={onRemove}
|
|
|
|
|
aria-label="Remove file"
|
|
|
|
|
>
|
|
|
|
|
<XIcon className="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|