Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions src/web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// 根组件:auth + 路由 + 全局 WS 订阅

import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { Routes, Route, useNavigate, useLocation } from "react-router";
import { useViewportHeight } from "./hooks/useViewportHeight";
import { useAuth } from "./hooks/useAuth";
Expand All @@ -15,6 +15,7 @@ import { NewInstanceModal } from "./components/ui/NewInstanceModal";
import { LoginPage } from "./components/LoginPage";
import { ToastContainer, showToast } from "./components/ui/Toast";
import type { InstanceId } from "../../protocol/types";
import { isBusy } from "./utils/status";
import { useTranslation } from "react-i18next";

/** 从 URL pathname 派生当前选中的实例 ID */
Expand Down Expand Up @@ -73,14 +74,23 @@ export default function App() {
);

// ── Shutdown ──
// 追踪主动 shutdown 的实例,区分主动退出与意外断连
const shuttingDownRef = useRef<Set<InstanceId>>(new Set());

const handleShutdown = useCallback(
(id: InstanceId) => {
send({ type: "shutdown", payload: { instanceId: id } });
if (id === selectedInstanceId) {
navigate("/");
// busy 时拒绝退出
const instance = useInstances
.getState()
.instances.find((i) => i.id === id);
if (instance && isBusy(instance.status)) {
showToast(t("sidebar.shutdownBusy"));
return;
}
shuttingDownRef.current.add(id);
send({ type: "shutdown", payload: { instanceId: id } });
},
[send, selectedInstanceId, navigate],
[send, t],
);

// 新建实例弹窗
Expand All @@ -98,7 +108,12 @@ export default function App() {
if (!instanceListReady || !selectedInstanceId) return;
const exists = instances.some((i) => i.id === selectedInstanceId);
if (!exists) {
showToast(t("eventStream.instanceNotFound"));
// 主动 shutdown 引起的消失不弹 toast
if (shuttingDownRef.current.has(selectedInstanceId)) {
shuttingDownRef.current.delete(selectedInstanceId);
} else {
showToast(t("eventStream.instanceNotFound"));
}
navigate("/");
}
}, [instanceListReady, selectedInstanceId, instances, navigate, t]);
Expand Down
6 changes: 0 additions & 6 deletions src/web/src/components/ui/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,13 @@ export function ContextMenu({ position, onClose, children }: ContextMenuProps) {
stableClose();
}
}
function handleScroll() {
stableClose();
}

document.addEventListener("mousedown", handleMouseDown);
document.addEventListener("contextmenu", handleContextMenu);
document.addEventListener("keydown", handleKeyDown);
window.addEventListener("scroll", handleScroll, true);
return () => {
document.removeEventListener("mousedown", handleMouseDown);
document.removeEventListener("contextmenu", handleContextMenu);
document.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("scroll", handleScroll, true);
};
}, [stableClose]);

Expand Down
1 change: 1 addition & 0 deletions src/web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const en: LocaleResource = {
noInstances: "No pi instances connected",
settings: "Settings",
shutdown: "Shutdown instance",
shutdownBusy: "Instance is running, please wait before shutting down",
statusStreaming: "Streaming",
statusCompacting: "Compacting",
statusIdle: "Idle",
Expand Down
1 change: 1 addition & 0 deletions src/web/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const zhCN = {
noInstances: "暂无 pi 实例连接",
settings: "设置",
shutdown: "退出实例",
shutdownBusy: "实例正在执行中,请等待完成后再退出",
statusStreaming: "生成中",
statusCompacting: "压缩中",
statusIdle: "空闲",
Expand Down