- Add catalog metadata lookup for better error messages with hints (e.g., for missing manifests or tags) - Persist downloaded model references in saved remote models for future reuse - Replace searchRemoteModels import with getSavedRemoteModels in search route - Enhance pull progress UI to display download bytes, digest, and fallback message when data is unavailable - Improve error payload with model details, URL, and tags count to guide users toward valid tags or models
112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Copy, Edit3, RefreshCw, Check, ChevronDown } from "lucide-react";
|
|
import { useCopyPrefs } from "@/hooks/useCopyPrefs";
|
|
import { stripMarkdown } from "@/hooks/useMarkdown";
|
|
|
|
type MessageActionsProps = {
|
|
content: string;
|
|
disabled?: boolean;
|
|
onEdit?: () => void;
|
|
onRegenerate?: () => void;
|
|
};
|
|
|
|
export function MessageActions({
|
|
content,
|
|
disabled,
|
|
onEdit,
|
|
onRegenerate,
|
|
}: MessageActionsProps) {
|
|
const [copied, setCopied] = useState(false);
|
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
const { copyMode, setCopyMode } = useCopyPrefs();
|
|
|
|
const copy = async (text: string) => {
|
|
await navigator.clipboard.writeText(text);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 1500);
|
|
};
|
|
|
|
const handleCopyDefault = () => {
|
|
const textToCopy =
|
|
copyMode === "markdown" ? content : stripMarkdown(content);
|
|
copy(textToCopy);
|
|
};
|
|
|
|
const handleModeSelect = (mode: "markdown" | "text") => {
|
|
setCopyMode(mode);
|
|
const textToCopy = mode === "markdown" ? content : stripMarkdown(content);
|
|
copy(textToCopy);
|
|
setDropdownOpen(false);
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center gap-1 opacity-70 pointer-events-auto">
|
|
<button
|
|
type="button"
|
|
className="rounded px-2 py-1 text-xs hover:bg-[var(--surface)]"
|
|
onClick={handleCopyDefault}
|
|
disabled={disabled}
|
|
aria-label="Copiar"
|
|
>
|
|
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
|
|
</button>
|
|
|
|
<div className="relative">
|
|
<button
|
|
type="button"
|
|
className="rounded px-1 py-1 text-xs hover:bg-[var(--surface)]"
|
|
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
disabled={disabled}
|
|
aria-label="Selecionar formato"
|
|
>
|
|
<ChevronDown className="h-3 w-3" />
|
|
</button>
|
|
|
|
{dropdownOpen && (
|
|
<div className="absolute right-0 z-10 mt-1 rounded border border-[var(--border)] bg-[var(--background)] shadow-lg">
|
|
<button
|
|
type="button"
|
|
className="block w-full px-3 py-2 text-left text-xs hover:bg-[var(--surface)]"
|
|
onClick={() => handleModeSelect("markdown")}
|
|
>
|
|
Markdown
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="block w-full px-3 py-2 text-left text-xs hover:bg-[var(--surface)]"
|
|
onClick={() => handleModeSelect("text")}
|
|
>
|
|
Texto
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{onEdit && (
|
|
<button
|
|
type="button"
|
|
className="rounded px-2 py-1 text-xs hover:bg-[var(--surface)]"
|
|
onClick={onEdit}
|
|
disabled={disabled}
|
|
aria-label="Edit message"
|
|
>
|
|
<Edit3 className="h-3 w-3" /> Edit
|
|
</button>
|
|
)}
|
|
{onRegenerate && (
|
|
<button
|
|
type="button"
|
|
className="rounded px-2 py-1 text-xs hover:bg-[var(--surface)]"
|
|
onClick={onRegenerate}
|
|
disabled={disabled}
|
|
aria-label="Regenerate"
|
|
>
|
|
<RefreshCw className="h-3 w-3" /> Retry
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|