hts/apps/blogai/components/header.tsx

504 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import React from 'react'
import { Suspense } from 'react'
import { auth } from '@/auth'
import { clearChats } from '@/app/actions'
import { Sidebar } from '@/components/sidebar'
import { SidebarList } from '@/components/sidebar-list'
import { IconSeparator } from '@/components/ui/icons'
import { SidebarFooter } from '@/components/sidebar-footer'
import { ClearHistory } from '@/components/clear-history'
import { UserMenu, UserData } from '@/components/user-menu'
import { LoginButton } from '@/components/login-button'
import {
Tooltip,
TooltipContent,
TooltipTrigger
} from '@/components/ui/tooltip'
import { Badge } from '@/components/ui/badge'
import { ConnectButton } from '@/components/connect-button'
import { SettingsDropDown } from './settings-drop-down'
import { useLocalStorage } from '@/lib/hooks/use-local-storage'
import { Button } from './ui/button'
import Image from 'next/image';
import logoImage from '@/components/images/logo.png';
import { useRouter } from 'next/navigation'
// import { Button } from './ui/button'
import { Flex, Text } from '@radix-ui/themes';
import Link from 'next/link'
import { useTranslation } from 'react-i18next'
import { message } from 'antd'
import { LogoAI } from '@/components/chat'
import { useState } from "react";
import { Trash2 } from "lucide-react";
import { useEffect } from "react";
import {
BadgeInfo,
Tags,
CalendarClock,
Building2
} from "lucide-react";
export function Header() {
const router = useRouter();
const { t } = useTranslation();
const [userData, setUserData] = useLocalStorage(
'UserData',
{
auth_token: "",
id: 1,
login_ip: "",
login_time: 0,
role: "",
user_name: "",
version: ""
} as UserData
)
const soonFunc = () => {
message.info(t("soon"))
}
//w-11/12 sm:w-5/6 md:w-3/4 lg:w-2/3 xl:w-3/5 2xl:w-1/2
return (
<header className="sticky top-0 z-50 flex shrink-0 items-center justify-between bg-background px-4 py-[1.5rem] w-[80%] mx-auto">
<a href="/">
<LogoAI className="flex text-center ml-0" />
</a>
<div className="flex items-center justify-between ">
<div className="flex items-center ">
{(!!userData && !!userData.user_name) ? (
<div className='flex text-[#1A1A1A]'>
<UserMenu user={userData} />
</div>
) : (
<div className='flex gap-4 grid-cols-2 text-[#1A1A1A]'>
<Button variant="ghost" className='text-base bg-[#f4f4f5] hover:bg-[#e5e7eb]'>
<Link href="/#subscribe-target"
>
{t('subscribe.subscribe')}
</Link>
</Button>
<LoginButton
variant="ghost"
// variant="link"
showGithubIcon={true}
text={t('login')}
className="-ml-2 text-base bg-[#f4f4f5] hover:bg-[#e5e7eb]"
/>
</div>
)}
</div>
</div>
</header>
)
}
// ✅ 图标显示逻辑现在是这样的:
// data.icon = "🚀" 👉 显示 emoji
// data.icon = "/images/icon.png" 👉 显示图片;
// data.icon = undefined/null 👉 显示默认 "Deploy" 字样。
export function DetailPageHeader({ data }: { data: any }) {
const [loading, setLoading] = useState(false);
const [statusText, setStatusText] = useState(data?.statusText || "加载中...");
const [progress, setProgress] = useState("0%");
const [showDelete, setShowDelete] = useState(true); // 默认可以删除
// const handleClick = async (source: "icon" | "info") => {
// setLoading(true);
// setStatusText(source === "icon" ? "正在处理图标操作..." : "正在处理信息操作...");
// try {
// // 从 localStorage 获取用户信息
// const userData = JSON.parse(localStorage.getItem("UserData") || "null");
// if (!userData || !userData.user_name) {
// setStatusText("未登录,正在跳转登录页面...");
// window.location.href = "/auth/sign-in/";
// return;
// }
// const userName = userData.user_name;
// // 从组件 props 里的 data 中取 id
// const id = data?.id;
// if (!id) {
// setStatusText("数据缺失:找不到组件 ID");
// return;
// }
// await fetch("/api/v1/deploy/deploy", {
// method: "POST",
// headers: { "Content-Type": "application/json" },
// body: JSON.stringify({
// id,
// user_name: userName,
// }),
// });
// await new Promise((resolve) => setTimeout(resolve, 1000));
// setStatusText(`操作成功:来自${source === "icon" ? "图标" : "信息区域"}`);
// } catch (err) {
// console.error("请求出错:", err);
// setStatusText("操作失败");
// } finally {
// setLoading(false);
// }
// };
// ✅ 删除函数
const handleClick = async (source: "icon" | "info") => {
setLoading(true);
setStatusText(source === "icon" ? "正在处理图标操作..." : "正在处理信息操作...");
try {
// 从 localStorage 获取用户信息
const userData = JSON.parse(localStorage.getItem("UserData") || "null");
if (!userData || !userData.user_name) {
setStatusText("未登录,正在跳转登录页面...");
window.location.href = "/auth/sign-in/";
return;
}
const userName = userData.user_name;
// 从组件 props 里的 data 中取 id
const id = data?.id;
if (!id) {
setStatusText("数据缺失:找不到组件 ID");
return;
}
// 发起部署请求
const res = await fetch("/api/v1/deploy/deploy", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id,
user_name: userName,
}),
});
if (!res.ok) {
throw new Error(`HTTP 请求失败:${res.status}`);
}
const json = await res.json();
if (json?.header?.code === 0) {
setStatusText(`${json.header.message || "操作成功"}`);
} else {
setStatusText(`${json.header.message || "操作失败(后端返回错误)"}`);
}
} catch (err) {
console.error("请求出错:", err);
setStatusText("操作失败,请检查网络或服务状态");
} finally {
setLoading(false);
}
};
const handleDelete = () => {
if (loading) return;
const confirmed = window.confirm("确定要删除模型吗?");
if (confirmed) {
setLoading(true);
// 模拟删除流程(实际调用 API 或其他逻辑)
// await deleteComponent(data.id);
console.log("模型已删除");
setLoading(false);
}
};
useEffect(() => {
const fetchDeployStatus = async () => {
try {
const result = await fetch("/api/v1/deploy/status", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: data?.id }),
}).then((res) => res.json());
console.log("==================>result:", result);
if (!result || result.header?.code !== 1006) {
setStatusText(result?.header?.message || "操作失败(后端返回错误)");
setShowDelete(false); // 非 1006 隐藏删除按钮
return;
}
// 如果成功返回
setStatusText(result.header.message || "部署成功");
if (result.header?.code === 1006) {
setShowDelete(false); // code === 1006 不可删除,隐藏按钮
setStatusText("");
setProgress("0%");
} else {
setShowDelete(true); // 其他状态可删除
setStatusText(result.header.message || "部署中");
setProgress(result.data?.progress || "0%");
}
// 如果还想设置进度的话,这里也可以 setProgress(result.data.progress || '0%')
} catch (err) {
console.error("请求部署状态失败:", err);
setStatusText("请求失败");
setShowDelete(false);
}
};
fetchDeployStatus();
}, [data?.id]); // id 变化时重新获取
// 处理图标路径
const isImagePath =
typeof data?.icon === "string" &&
(data.icon.startsWith("http") || data.icon.startsWith("/"));
const resolvedIconSrc =
isImagePath && !data.icon.startsWith("http")
? process.env.NEXT_PUBLIC_CLIENT_IMAGE_URL + data.icon
: data.icon;
return (
<div className="sticky top-0 z-30 bg-white">
<div className="mt-4 mb-1 px-6 lg:px-8 w-11/12 lg:w-2/3 xl:w-3/5 mx-auto">
<div className="flex items-start space-x-6">
{/* 左图标(可点) */}
<button
className="group flex items-center justify-center w-24 h-24 md:w-32 md:h-32 border transition"
onClick={() => handleClick("icon")}
disabled={loading}
>
{isImagePath ? (
<img
src={resolvedIconSrc}
alt="icon"
className="
w-full h-full object-contain
transition-all duration-200
group-hover:border-2 group-hover:border-blue-500
group-active:border-2 group-active:border-green-500
group-hover:scale-105 group-active:scale-95
"
/>
) : (
data?.icon || "Deploy"
)}
</button>
{/* 信息 + 删除按钮:左右分布 */}
<div className="flex justify-between flex-1 items-end">
{/* 信息区域 */}
<div className="text-sm leading-7 space-y-1.5">
<div className="flex items-center gap-2">
<BadgeInfo size={16} /> {data?.name || "未命名组件"}
</div>
<div className="flex items-center gap-2">
<Tags size={16} /> {data?.category || "未知"}
</div>
<div className="flex items-center gap-2">
<CalendarClock size={16} /> {data?.updated_at || "未提供"}
</div>
<div className="flex items-center gap-2">
<Building2 size={16} /> {data?.company || "未知公司"}
</div>
</div>
{/* 条件显示删除按钮 */}
{showDelete && (
<button
onClick={handleDelete}
className="hover:text-gray-700 transition self-end"
disabled={loading}
title="删除"
>
<Trash2 size={20} />
</button>
)}
</div>
</div>
{/* 状态条 */}
{(data?.progress !== "0%" || statusText) && (
<div className="w-full mt-4 bg-gray-200 h-6">
<div
className="bg-blue-500 h-full text-white text-center text-sm flex items-center justify-center transition-all duration-300 px-2 overflow-hidden whitespace-nowrap text-ellipsis"
style={{ width: data?.progress || "60%" }}
>
{loading ? "操作中..." : statusText}
</div>
</div>
)}
</div>
</div>
);
}
// export function Header() {
// const router = useRouter();
// const { t } = useTranslation();
// const [userData, setUserData] = useLocalStorage(
// 'UserData',
// {
// auth_token: "",
// id: 1,
// login_ip: "",
// login_time: 0,
// role: "",
// user_name: "",
// version: ""
// } as UserData
// )
// const soonFunc = () => {
// message.info(t("soon"))
// }
// return (
// <header className="sticky top-0 z-50 flex w-full shrink-0 items-center justify-between bg-background px-4 py-[1.5rem]">
// {/* <MobileLinks className="lg:hidden" />
// <DesktopLinks className="hidden lg:flex" /> */}
// <div className="lg:hidden" ></div>
// <div className="hidden lg:flex" >
// {/* <Button variant="ghost" className='text-base font-bold hover:bg-[#e5e7eb]' onClick={soonFunc}>
// {t("header.course")}
// </Button>
// <Button variant="ghost" className='text-base font-bold hover:bg-[#e5e7eb]' onClick={soonFunc}>
// {t("header.models")}
// </Button>
// <Button variant="ghost" className='text-base font-bold hover:bg-[#e5e7eb]' onClick={soonFunc}>
// {t("header.resources")}
// </Button> */}
// </div>
// {/* <Image src={logoImage} height={40} alt="show" className="cursor-pointer" onClick={() => {
// router.push("/")
// // toast.success('coming soon')
// }} /> */}
// <LogoAI
// className='flex text-center m-auto'
// />
// <div className="flex items-center justify-between ">
// <div className="flex items-center ">
// {/* <IconSeparator className="size-6 text-muted-foreground/50" /> */}
// {(!!userData && !!userData.user_name) ? (
// <div className='flex text-[#1A1A1A]'>
// {/* <Button variant="ghost" className='text-base bg-[#f4f4f5] hover:bg-[#e5e7eb]'
// onClick={() => {
// router.push("/subscribe")
// }}
// > {t("subscribe.subscribe")}</Button> */}
// <UserMenu user={userData} />
// </div>
// ) : (
// <div className='flex gap-4 grid-cols-2 text-[#1A1A1A]'>
// <Button variant="ghost" className='text-base bg-[#f4f4f5] hover:bg-[#e5e7eb]'>
// <Link href="/#subscribe-target"
// >
// {t('subscribe.subscribe')}
// </Link>
// </Button>
// <LoginButton
// variant="ghost"
// // variant="link"
// showGithubIcon={true}
// text={t('login')}
// className="-ml-2 text-base bg-[#f4f4f5] hover:bg-[#e5e7eb]"
// />
// </div>
// )}
// </div>
// </div>
// </header>
// )
// }
export function NavBack() {
const router = useRouter();
const { t } = useTranslation();
// const query = router.query; // 假设页面 URL 是 /mypage?id=123
console.log("query", router)
return (
<nav className="fixed top-[1rem] container flex items-center justify-between " >
<Link href="/">
{t("home")}
</Link>
</nav>
)
}
export function TimeP({
date,
}: {
date: string
}) {
const router = useRouter();
const { t } = useTranslation();
return (
<p className=" text-left text-[1rem] text-[#808080]">{date} {t("JellyAI")}</p>
)
}