This commit is contained in:
parent
479852fff0
commit
c7c6561bc9
|
|
@ -0,0 +1,682 @@
|
||||||
|
'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 { 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, useRef, } from "react";
|
||||||
|
|
||||||
|
import { Trash2, CloudDownload } 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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
import { getRuntimeEnv } from "@/lib/ipconfig";
|
||||||
|
|
||||||
|
export async function getWsBase() {
|
||||||
|
let ip = await getRuntimeEnv("SUPABASE_URL");
|
||||||
|
if (!ip) throw new Error("SUPABASE_URL 获取失败,无法构建 wsBase");
|
||||||
|
|
||||||
|
let wsProtocol = "ws";
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
// ✅ 客户端环境(浏览器执行)
|
||||||
|
if (window.location.protocol === "https:") {
|
||||||
|
wsProtocol = "wss";
|
||||||
|
ip = window.location.hostname; // ✅ 用 hostname 避免 HTTPS + IP 的 TLS 报错
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ✅ SSR 环境,延迟导入 headers(不能放顶层)
|
||||||
|
const { headers } = await import("next/headers");
|
||||||
|
const hdrs = headers();
|
||||||
|
|
||||||
|
const forwardedProto = hdrs.get("x-forwarded-proto");
|
||||||
|
const hostHeader = hdrs.get("host");
|
||||||
|
|
||||||
|
if (forwardedProto === "https") {
|
||||||
|
wsProtocol = "wss";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostHeader) {
|
||||||
|
// ✅ host 可能带端口,需处理
|
||||||
|
ip = hostHeader.includes(":") ? hostHeader.split(":")[0] : hostHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalUrl = `${wsProtocol}://${ip}/api/v1/deploy/ws`;
|
||||||
|
console.log("✅ [WebSocket] Final URL:", finalUrl);
|
||||||
|
return finalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function DetailPageHeader({ data }: { data: any }) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [statusText, setStatusText] = useState(data?.statusText || "加载中...");
|
||||||
|
const [progress, setProgress] = useState(data?.progress || "0%");
|
||||||
|
const [progressBarColor, setProgressBarColor] = useState("bg-blue-500"); // ✅ 默认蓝色
|
||||||
|
const [showProgressBar, setShowProgressBar] = useState(false);
|
||||||
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
|
const [hasWSConnected, setHasWSConnected] = useState(false);
|
||||||
|
const [statusLoaded, setStatusLoaded] = useState(false);
|
||||||
|
const [canDeploy, setCanDeploy] = useState(true);
|
||||||
|
|
||||||
|
const [currentStatus, setCurrentStatus] = useState(""); // 当前部署状态:running / stopped
|
||||||
|
const [switchLoading, setSwitchLoading] = useState(false); // 控制按钮 loading 状态
|
||||||
|
|
||||||
|
const [downloadPercent, setDownloadPercent] = useState("0"); // 下载百分比(0 ~ 100)
|
||||||
|
const [showDownloadBar, setShowDownloadBar] = useState(false);
|
||||||
|
|
||||||
|
const socketRef = useRef<WebSocket | null>(null);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
console.log("...........model_parameter =", data?.model_parameter);
|
||||||
|
|
||||||
|
// const hasNonEmptyExtraData = data?.extra_data && typeof data.extra_data === 'object' && !Array.isArray(data.extra_data) && Object.keys(data.extra_data).length > 0 data?.model_parameter !== 0 &&
|
||||||
|
// data?.model_parameter !== "";
|
||||||
|
const hasNonEmptyExtraData =
|
||||||
|
data?.extra_data &&
|
||||||
|
typeof data.extra_data === 'object' &&
|
||||||
|
!Array.isArray(data.extra_data) &&
|
||||||
|
Object.keys(data.extra_data).length > 0 &&
|
||||||
|
data?.model_parameter !== 0 &&
|
||||||
|
data?.model_parameter !== "";
|
||||||
|
|
||||||
|
|
||||||
|
const initWebSocket = async (userName: string, id: number) => {
|
||||||
|
if (socketRef.current) socketRef.current.close();
|
||||||
|
|
||||||
|
|
||||||
|
//const wsBase = process.env.NEXT_PUBLIC_CLIENT_BASE_WS;
|
||||||
|
const wsBase = await getWsBase();
|
||||||
|
const socket = new WebSocket(`${wsBase}/status/${userName}/${id}`);
|
||||||
|
socketRef.current = socket;
|
||||||
|
|
||||||
|
socket.onopen = () => console.log("WebSocket 已连接");
|
||||||
|
|
||||||
|
socket.onmessage = (event) => {
|
||||||
|
const msg = event.data;
|
||||||
|
console.log("收到进度信息:", msg);
|
||||||
|
setStatusText(msg);
|
||||||
|
|
||||||
|
const percentMatch = msg.match(/(\d+)%/);
|
||||||
|
if (percentMatch && percentMatch[1]) {
|
||||||
|
setProgress(percentMatch[1] + "%");
|
||||||
|
|
||||||
|
if (percentMatch[1] === "100") {
|
||||||
|
console.log("部署完成 ✅,重新拉取状态!");
|
||||||
|
fetchDeployStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setProgressBarColor("bg-blue-500"); // 保持默认颜色
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = (err) => {
|
||||||
|
console.error("WebSocket 出错:", err);
|
||||||
|
setStatusText(t("deploy.ws_error"));
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = () => {
|
||||||
|
console.log("🔌 WebSocket 已关闭");
|
||||||
|
setHasWSConnected(false);
|
||||||
|
socketRef.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
setHasWSConnected(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = async (source: "icon" | "info") => {
|
||||||
|
setLoading(true);
|
||||||
|
setStatusText(source === "icon" ? "正在处理图标操作..." : "正在处理信息操作...");
|
||||||
|
setShowProgressBar(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userData = JSON.parse(localStorage.getItem("UserData") || "null");
|
||||||
|
if (!userData?.user_name) {
|
||||||
|
setStatusText("未登录,跳转中...");
|
||||||
|
window.location.href = "/auth/sign-in/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userName = userData.user_name;
|
||||||
|
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 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
if (json?.header?.code === 0) {
|
||||||
|
setStatusText("部署已启动,监听中...");
|
||||||
|
setProgressBarColor("bg-blue-500");
|
||||||
|
initWebSocket(userName, id);
|
||||||
|
} else {
|
||||||
|
setStatusText(json.header.message || "操作失败(后端)");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("请求出错:", err);
|
||||||
|
setStatusText("请求失败");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (loading) return;
|
||||||
|
|
||||||
|
const confirmed = window.confirm(t("deploy.confirm_delete"));
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setStatusText(t("deploy.deleting"));
|
||||||
|
setProgressBarColor("bg-gray-400");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userData = JSON.parse(localStorage.getItem("UserData") || "null");
|
||||||
|
const userName = userData?.user_name;
|
||||||
|
const id = data?.id;
|
||||||
|
|
||||||
|
if (!userName || !id) {
|
||||||
|
setStatusText(t("deploy.missing_user_or_id"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 60_000); // ⏰ 60秒超时
|
||||||
|
|
||||||
|
const res = await fetch("/api/v1/deploy/delete", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
signal: controller.signal,
|
||||||
|
body: JSON.stringify({ user_name: userName, n_id: id }),
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId); // ✅ 成功返回前清除 timeout
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP 请求失败: ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
if (json?.header?.code === 0) {
|
||||||
|
setStatusText(t("deploy.deletion_success"));
|
||||||
|
setProgress("0%");
|
||||||
|
setShowDelete(false); // 删除成功后隐藏按钮
|
||||||
|
await fetchDeployStatus(); // ✅ 删除后刷新真实状态
|
||||||
|
} else {
|
||||||
|
setStatusText(json?.header?.message || "删除失败(后端返回错误)");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if ((err as any).name === "AbortError") {
|
||||||
|
setStatusText(t("deploy.timeout"));
|
||||||
|
} else {
|
||||||
|
console.error("删除请求出错:", err);
|
||||||
|
setStatusText(t("deploy.deletion_failed_network"));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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("====> deploy status result:", result);
|
||||||
|
setStatusLoaded(true);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
setStatusText(t("deploy.empty_response"));
|
||||||
|
setShowDelete(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = result?.header?.code;
|
||||||
|
const status = result?.data?.data?.status;
|
||||||
|
setCurrentStatus(status || ""); //...............................................
|
||||||
|
const userData = JSON.parse(localStorage.getItem("UserData") || "null");
|
||||||
|
const userName = userData?.user_name;
|
||||||
|
const id = data?.id;
|
||||||
|
|
||||||
|
console.log("🟡 状态码 code =", code, "状态 status =", status);
|
||||||
|
|
||||||
|
if (code === 1006) {
|
||||||
|
setStatusText(t("deploy.not_deployed"));
|
||||||
|
setShowDelete(false);
|
||||||
|
setProgress("0%");
|
||||||
|
setShowProgressBar(false);
|
||||||
|
setCanDeploy(true); // ✅ 允许部署
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "deploying" && userName && id && !hasWSConnected) {
|
||||||
|
setStatusText(t("deploy.connecting"));
|
||||||
|
initWebSocket(userName, id);
|
||||||
|
setShowProgressBar(true);
|
||||||
|
setCanDeploy(false); // ✅ 正在部署中,禁止点击
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "running" || status === "stopped") {
|
||||||
|
console.log("✅ 允许删除(status =", status, ")");
|
||||||
|
setShowDelete(true);
|
||||||
|
setProgress("100%");
|
||||||
|
setShowProgressBar(true);
|
||||||
|
setCanDeploy(false); // ✅ 已部署/已停止,不允许再次 deploy
|
||||||
|
|
||||||
|
if (status === "running") {
|
||||||
|
setStatusText(t("deploy.running"));
|
||||||
|
setProgressBarColor("bg-green-500");
|
||||||
|
} else if (status === "stopped") {
|
||||||
|
setStatusText(t("deploy.stopped"));
|
||||||
|
setProgressBarColor("bg-gray-400");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("❌ 不允许删除(status =", status, ")");
|
||||||
|
setShowDelete(false);
|
||||||
|
setProgressBarColor("bg-blue-500");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("获取状态失败:", err);
|
||||||
|
setStatusText(t("deploy.fetch_failed"));
|
||||||
|
setShowDelete(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleSwitchStatus = async () => {
|
||||||
|
if (!data?.id) return;
|
||||||
|
|
||||||
|
setSwitchLoading(true);
|
||||||
|
const userData = JSON.parse(localStorage.getItem("UserData") || "null");
|
||||||
|
const userName = userData?.user_name;
|
||||||
|
|
||||||
|
const id = data?.id;
|
||||||
|
|
||||||
|
if (!userName || !id) {
|
||||||
|
setStatusText("缺少组件 ID 或用户信息");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userName) {
|
||||||
|
setStatusText("未登录,跳转中...");
|
||||||
|
window.location.href = "/auth/sign-in/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = currentStatus === "running" ? "/api/v1/deploy/stop" : "/api/v1/deploy/start";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ user_name: userName, n_id: id }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
if (json?.header?.code === 0) {
|
||||||
|
setStatusText(currentStatus === "running" ? "已停止" : "已启动");
|
||||||
|
fetchDeployStatus(); // ✅ 状态切换成功后刷新
|
||||||
|
} else {
|
||||||
|
setStatusText(json?.header?.message || "操作失败(后端)");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("切换请求失败:", err);
|
||||||
|
setStatusText("网络异常");
|
||||||
|
} finally {
|
||||||
|
setSwitchLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDeployStatus();
|
||||||
|
}, [data?.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (socketRef.current) {
|
||||||
|
socketRef.current.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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"
|
||||||
|
className={`group flex items-center justify-center w-24 h-24 md:w-32 md:h-32 border transition ${(!data?.model_parameter) ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
|
onClick={() => handleClick("icon")}
|
||||||
|
disabled={loading || !canDeploy || !data?.model_parameter}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-[5px] self-end">
|
||||||
|
{hasNonEmptyExtraData && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
console.log("🟢 点击了更新按钮,extra_data = ", data.extra_data);
|
||||||
|
// TODO: 后续处理逻辑写在这里
|
||||||
|
}}
|
||||||
|
className="hover:text-gray-700 transition self-end text-sm border border-gray-300 rounded px-2 py-1 bg-white"
|
||||||
|
title="更新"
|
||||||
|
>
|
||||||
|
🔄 更新
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{statusLoaded && (currentStatus === "running" || currentStatus === "stopped") && (
|
||||||
|
<button
|
||||||
|
onClick={handleSwitchStatus}
|
||||||
|
disabled={switchLoading || loading}
|
||||||
|
className="hover:text-gray-700 transition self-end text-sm border border-gray-300 rounded px-2 py-1 bg-white"
|
||||||
|
title={currentStatus === "running" ? "停止运行" : "启动运行"}
|
||||||
|
>
|
||||||
|
{switchLoading
|
||||||
|
? "处理中..."
|
||||||
|
: currentStatus === "running"
|
||||||
|
? "⏹ 停止"
|
||||||
|
: "▶️ 启动"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{statusLoaded && showDelete && (
|
||||||
|
<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
className="hover:text-gray-700 transition self-end"
|
||||||
|
disabled={loading}
|
||||||
|
title="删除"
|
||||||
|
>
|
||||||
|
<Trash2 size={20} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!data?.model_parameter && (
|
||||||
|
<div className="flex flex-col items-center gap-[2px]">
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const download_url = data?.extra_data?.download_url;
|
||||||
|
const callback_url = data?.extra_data?.callback_url;
|
||||||
|
const digest = data?.extra_data?.digest;
|
||||||
|
const version = data?.extra_data?.version;
|
||||||
|
const size = data?.extra_data?.size;
|
||||||
|
const date1 = data?.extra_data?.date;
|
||||||
|
const id = data?.org_id;
|
||||||
|
|
||||||
|
|
||||||
|
if (!download_url || !size || !id) {
|
||||||
|
message.warning("缺少必要的下载参数");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("📥 调用下载接口...", { url: download_url, size, id });
|
||||||
|
|
||||||
|
const res = await fetch("https://ai.szaiai.com/api/v1/cloud/download", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
date: date1,
|
||||||
|
url: download_url,
|
||||||
|
callback_url: callback_url,
|
||||||
|
digest: digest,
|
||||||
|
version: version,
|
||||||
|
size: size,
|
||||||
|
id: id,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await res.json();
|
||||||
|
console.log("✅ 下载接口返回:", result);
|
||||||
|
|
||||||
|
if (result?.header?.code === 0) {
|
||||||
|
const percentStr = result?.data?.percent ?? "0";
|
||||||
|
const percentNum = parseFloat(percentStr);
|
||||||
|
const percent = (percentNum * 100).toFixed(1);
|
||||||
|
|
||||||
|
setDownloadPercent(percent);
|
||||||
|
setShowDownloadBar(true);
|
||||||
|
|
||||||
|
message.success(`📦 下载进度:${percent}%`);
|
||||||
|
} else {
|
||||||
|
message.warning(`❌ 下载失败:${result?.header?.message || "未知错误"}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ 下载请求异常:", err);
|
||||||
|
message.error("请求出错,无法下载");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="hover:text-gray-700 transition self-end"
|
||||||
|
title="下载模型文件"
|
||||||
|
>
|
||||||
|
<CloudDownload size={20} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
{showDownloadBar && (
|
||||||
|
<div className="w-[20px] h-[4px] bg-gray-200 rounded overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-blue-500 transition-all duration-300"
|
||||||
|
style={{ width: `${downloadPercent}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showProgressBar && (progress !== "0%" || statusText) && (
|
||||||
|
<div className="relative w-full bg-gray-200 h-6 rounded overflow-hidden">
|
||||||
|
{/* 蓝色进度条 */}
|
||||||
|
<div
|
||||||
|
className={`${progressBarColor} h-full transition-all duration-300`}
|
||||||
|
style={{ width: typeof progress === "string" ? progress : `${progress}%` }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 浮动在中间的文字层 */}
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center text-sm font-medium text-white">
|
||||||
|
{loading ? t("deploy.processing") : statusText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -32,6 +32,9 @@ const nextConfig = {
|
||||||
// Warning: This allows production builds to successfully complete even if
|
// Warning: This allows production builds to successfully complete even if
|
||||||
// your project has ESLint errors.
|
// your project has ESLint errors.
|
||||||
ignoreDuringBuilds: true,
|
ignoreDuringBuilds: true,
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
esmExternals: true,
|
||||||
},
|
},
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,7 @@
|
||||||
],
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
//"strict": true,
|
"strict": true,
|
||||||
// ✅ 临时关闭严格检查 ↓↓↓
|
|
||||||
"strict": false,
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"strictNullChecks": false,
|
|
||||||
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
|
@ -40,7 +35,7 @@
|
||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
//"strictNullChecks": true
|
"strictNullChecks": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
|
|
@ -48,7 +43,7 @@
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts"
|
||||||
, "auth.ts" ],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"test",
|
"test",
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue