mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-05 03:02:56 +00:00
hot fix #1
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowRight, Bot, Check, ChevronDown, Paperclip } from "lucide-react";
|
||||
import { useState, useRef, useCallback, useEffect } from "react";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
interface UseAutoResizeTextareaProps {
|
||||
minHeight: number;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
function useAutoResizeTextarea({
|
||||
minHeight,
|
||||
maxHeight,
|
||||
}: UseAutoResizeTextareaProps) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const adjustHeight = useCallback(
|
||||
(reset?: boolean) => {
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
|
||||
if (reset) {
|
||||
textarea.style.height = `${minHeight}px`;
|
||||
return;
|
||||
}
|
||||
|
||||
textarea.style.height = `${minHeight}px`;
|
||||
|
||||
const newHeight = Math.max(
|
||||
minHeight,
|
||||
Math.min(
|
||||
textarea.scrollHeight,
|
||||
maxHeight ?? Number.POSITIVE_INFINITY
|
||||
)
|
||||
);
|
||||
|
||||
textarea.style.height = `${newHeight}px`;
|
||||
},
|
||||
[minHeight, maxHeight]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const textarea = textareaRef.current;
|
||||
if (textarea) {
|
||||
textarea.style.height = `${minHeight}px`;
|
||||
}
|
||||
}, [minHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => adjustHeight();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [adjustHeight]);
|
||||
|
||||
return { textareaRef, adjustHeight };
|
||||
}
|
||||
|
||||
const OPENAI_ICON = (
|
||||
<>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 256 260"
|
||||
aria-label="OpenAI Icon"
|
||||
className="w-4 h-4 dark:hidden block"
|
||||
>
|
||||
<title>OpenAI Icon Light</title>
|
||||
<path d="M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z" />
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 256 260"
|
||||
aria-label="OpenAI Icon"
|
||||
className="w-4 h-4 hidden dark:block"
|
||||
>
|
||||
<title>OpenAI Icon Dark</title>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z"
|
||||
/>
|
||||
</svg>
|
||||
</>
|
||||
);
|
||||
|
||||
export function AI_Prompt() {
|
||||
const [value, setValue] = useState("");
|
||||
const { textareaRef, adjustHeight } = useAutoResizeTextarea({
|
||||
minHeight: 72,
|
||||
maxHeight: 300,
|
||||
});
|
||||
const [selectedModel, setSelectedModel] = useState("GPT-4-1 Mini");
|
||||
|
||||
const AI_MODELS = [
|
||||
"o3-mini",
|
||||
"Gemini 2.5 Flash",
|
||||
"Claude 3.5 Sonnet",
|
||||
"GPT-4-1 Mini",
|
||||
"GPT-4-1",
|
||||
];
|
||||
|
||||
const MODEL_ICONS: Record<string, React.ReactNode> = {
|
||||
"o3-mini": OPENAI_ICON,
|
||||
"Gemini 2.5 Flash": (
|
||||
<svg
|
||||
height="1em"
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Gemini</title>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="lobe-icons-gemini-fill"
|
||||
x1="0%"
|
||||
x2="68.73%"
|
||||
y1="100%"
|
||||
y2="30.395%"
|
||||
>
|
||||
<stop offset="0%" stopColor="#1C7DFF" />
|
||||
<stop offset="52.021%" stopColor="#1C69FF" />
|
||||
<stop offset="100%" stopColor="#F0DCD6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M12 24A14.304 14.304 0 000 12 14.304 14.304 0 0012 0a14.305 14.305 0 0012 12 14.305 14.305 0 00-12 12"
|
||||
fill="url(#lobe-icons-gemini-fill)"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
"Claude 3.5 Sonnet": (
|
||||
<>
|
||||
<svg
|
||||
fill="#000"
|
||||
fillRule="evenodd"
|
||||
className="w-4 h-4 dark:hidden block"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Anthropic Icon Light</title>
|
||||
<path d="M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z" />
|
||||
</svg>
|
||||
<svg
|
||||
fill="#fff"
|
||||
fillRule="evenodd"
|
||||
className="w-4 h-4 hidden dark:block"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Anthropic Icon Dark</title>
|
||||
<path d="M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z" />
|
||||
</svg>
|
||||
</>
|
||||
),
|
||||
"GPT-4-1 Mini": OPENAI_ICON,
|
||||
"GPT-4-1": OPENAI_ICON,
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter" && !e.shiftKey && value.trim()) {
|
||||
e.preventDefault();
|
||||
setValue("");
|
||||
adjustHeight(true);
|
||||
// Here you can add message sending
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-4/6 py-4">
|
||||
<div className="bg-black/5 dark:bg-white/5 rounded-2xl p-1.5">
|
||||
<div className="relative">
|
||||
<div className="relative flex flex-col">
|
||||
<div
|
||||
className="overflow-y-auto"
|
||||
style={{ maxHeight: "400px" }}
|
||||
>
|
||||
<Textarea
|
||||
id="ai-input-15"
|
||||
value={value}
|
||||
placeholder={"What can I do for you?"}
|
||||
className={cn(
|
||||
"w-full rounded-xl rounded-b-none px-4 py-3 bg-black/5 dark:bg-white/5 border-none dark:text-white placeholder:text-black/70 dark:placeholder:text-white/70 resize-none focus-visible:ring-0 focus-visible:ring-offset-0",
|
||||
"min-h-[72px]"
|
||||
)}
|
||||
ref={textareaRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
adjustHeight();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="h-14 bg-black/5 dark:bg-white/5 rounded-b-xl flex items-center">
|
||||
<div className="absolute left-3 right-3 bottom-3 flex items-center justify-between w-[calc(100%-24px)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex items-center gap-1 h-8 pl-1 pr-2 text-xs rounded-md dark:text-white hover:bg-black/10 dark:hover:bg-white/10 focus-visible:ring-1 focus-visible:ring-offset-0 focus-visible:ring-blue-500"
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={selectedModel}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
y: -5,
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
y: 5,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.15,
|
||||
}}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{
|
||||
MODEL_ICONS[
|
||||
selectedModel
|
||||
]
|
||||
}
|
||||
{selectedModel}
|
||||
<ChevronDown className="w-3 h-3 opacity-50" />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className={cn(
|
||||
"min-w-[10rem]",
|
||||
"border-black/10 dark:border-white/10",
|
||||
"bg-gradient-to-b from-white via-white to-neutral-100 dark:from-neutral-950 dark:via-neutral-900 dark:to-neutral-800"
|
||||
)}
|
||||
>
|
||||
{AI_MODELS.map((model) => (
|
||||
<DropdownMenuItem
|
||||
key={model}
|
||||
onSelect={() =>
|
||||
setSelectedModel(model)
|
||||
}
|
||||
className="flex items-center justify-between gap-2"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{MODEL_ICONS[model] || (
|
||||
<Bot className="w-4 h-4 opacity-50" />
|
||||
)}
|
||||
<span>{model}</span>
|
||||
</div>
|
||||
{selectedModel ===
|
||||
model && (
|
||||
<Check className="w-4 h-4 text-blue-500" />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<div className="h-4 w-px bg-black/10 dark:bg-white/10 mx-0.5" />
|
||||
<label
|
||||
className={cn(
|
||||
"rounded-lg p-2 bg-black/5 dark:bg-white/5 cursor-pointer",
|
||||
"hover:bg-black/10 dark:hover:bg-white/10 focus-visible:ring-1 focus-visible:ring-offset-0 focus-visible:ring-blue-500",
|
||||
"text-black/40 dark:text-white/40 hover:text-black dark:hover:text-white"
|
||||
)}
|
||||
aria-label="Attach file"
|
||||
>
|
||||
<input type="file" className="hidden" />
|
||||
<Paperclip className="w-4 h-4 transition-colors" />
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"rounded-lg p-2 bg-black/5 dark:bg-white/5",
|
||||
"hover:bg-black/10 dark:hover:bg-white/10 focus-visible:ring-1 focus-visible:ring-offset-0 focus-visible:ring-blue-500"
|
||||
)}
|
||||
aria-label="Send message"
|
||||
disabled={!value.trim()}
|
||||
onClick={() => {
|
||||
if (!value.trim()) return;
|
||||
setValue("");
|
||||
adjustHeight(true);
|
||||
// Здесь можно добавить отправку сообщения
|
||||
}}
|
||||
>
|
||||
<ArrowRight
|
||||
className={cn(
|
||||
"w-4 h-4 dark:text-white transition-opacity duration-200",
|
||||
value.trim()
|
||||
? "opacity-100"
|
||||
: "opacity-30"
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user