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
|
||||
// your project has ESLint errors.
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
experimental: {
|
||||
esmExternals: true,
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
|
|
|
|||
|
|
@ -8,12 +8,7 @@
|
|||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
//"strict": true,
|
||||
// ✅ 临时关闭严格检查 ↓↓↓
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": false,
|
||||
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
|
|
@ -40,7 +35,7 @@
|
|||
"name": "next"
|
||||
}
|
||||
],
|
||||
//"strictNullChecks": true
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
|
|
@ -48,7 +43,7 @@
|
|||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
, "auth.ts" ],
|
||||
],
|
||||
"exclude": [
|
||||
"test",
|
||||
"node_modules"
|
||||
|
|
|
|||
Loading…
Reference in New Issue