+
+
+
+
+
+
+
+
+ 通道与会话切换立即生效,无需保存;通道决定当前视图的消息来源与读写权限。
+
+
void;
onSaveConnection?: (host: string, port: number) => void;
initialTab?: TabId;
+ channels?: Channel[];
+ selectedChannel?: string | null;
+ onSelectChannel?: (id: string) => void;
+ sessions?: SessionSummary[];
+ selectedSessionId?: string | null;
+ onSelectSession?: (id: string) => void;
}
export interface KnownSource {
diff --git a/web/src/hooks/useChat.ts b/web/src/hooks/useChat.ts
index c139a17..b12db08 100644
--- a/web/src/hooks/useChat.ts
+++ b/web/src/hooks/useChat.ts
@@ -1,4 +1,4 @@
-import { useCallback, useMemo, type Dispatch, type SetStateAction } from 'react';
+import { useCallback, useMemo, useRef, type Dispatch, type SetStateAction } from 'react';
import type {
Command,
ChatMessage,
@@ -166,6 +166,10 @@ export function useChat(): UseChatReturn {
});
const scheduler = useSchedulerView();
+ // selectedChannel 的 ref 镜像:handleServerMessage 空依赖闭包中读取当前值,
+ // 用于 channel_list 到达时校正失效的选中通道(如默认 'websocket' 在后端不存在)
+ const selectedChannelRef = useRef(sideData.selectedChannel);
+
// ---- handleServerMessage: 纯路由分发(Tier 1 → Tier 2 → Tier 3) ----
// 所有被调用的方法都是稳定引用(useState setter 或 useCallback([])),
// 因此空依赖数组安全,不会产生过期闭包。
@@ -252,9 +256,20 @@ export function useChat(): UseChatReturn {
sideData.setTodos(message.todos);
return;
- case 'channel_list':
+ case 'channel_list': {
sideData.setChannels(message.channels);
+ // 当前选中通道不在列表中(默认 'websocket' 在后端不存在、或通道被移除)时,
+ // 回退到第一个通道,避免 ConnectionTab 通道下拉空白及 session 请求携带无效通道
+ if (
+ message.channels.length > 0 &&
+ !message.channels.some((c) => c.id === selectedChannelRef.current)
+ ) {
+ const fallback = message.channels[0].id;
+ selectedChannelRef.current = fallback;
+ sideData.setSelectedChannel(fallback);
+ }
return;
+ }
case 'pong':
return;
@@ -300,6 +315,7 @@ export function useChat(): UseChatReturn {
const selectChannel = useCallback(
(channelId: string) => {
if (channelId === sideData.selectedChannel) return;
+ selectedChannelRef.current = channelId;
sideData.setSelectedChannel(channelId);
sessions.setSessions([]);
sessions.setSelectedSessionId(null);