14a00774d9
- 新增 web 能力并挂载 platform/web(web_search/fetch_url) - 实时查询强制 web skill,router fallback 与 await session Finish - Session Broker 覆盖率/指标、stream replay 与相关单测/E2E 脚本 Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
4.1 KiB
TypeScript
134 lines
4.1 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
import { useChat } from '../context/ChatProvider';
|
|
import { buildSelectedAssetGreeting } from '../utils/mindspaceChatContext';
|
|
import { shouldShowChatMessage } from '../utils/message';
|
|
import type { Message, MindSpaceChatContext, MindSpaceSaveCategory, PortalUser } from '../types';
|
|
import { SpaceChatFab } from './SpaceChatFab';
|
|
import { SpaceChatPanel } from './SpaceChatPanel';
|
|
|
|
const SELECTED_ASSET_GREETING_ID = 'mindspace-selected-asset-greeting';
|
|
|
|
function buildSelectedAssetGreetingMessage(
|
|
asset: NonNullable<MindSpaceChatContext['selectedAssets']>[number],
|
|
): Message {
|
|
const text = buildSelectedAssetGreeting(asset);
|
|
return {
|
|
id: SELECTED_ASSET_GREETING_ID,
|
|
role: 'assistant',
|
|
created: Math.floor(Date.now() / 1000),
|
|
content: [{ type: 'text', text }],
|
|
metadata: {
|
|
userVisible: true,
|
|
agentVisible: false,
|
|
displayText: text,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function MindSpaceSpaceChat({
|
|
context,
|
|
user,
|
|
onOpenFullChat,
|
|
onPageSaved,
|
|
open: controlledOpen,
|
|
onOpenChange,
|
|
variant = 'default',
|
|
chatBridge,
|
|
hideOpenFullChat = false,
|
|
title,
|
|
}: {
|
|
context: MindSpaceChatContext;
|
|
user: PortalUser;
|
|
onOpenFullChat: () => void;
|
|
onPageSaved?: (result: {
|
|
kind: 'page' | 'category';
|
|
pageId?: string;
|
|
categoryCode?: MindSpaceSaveCategory;
|
|
}) => void;
|
|
open?: boolean;
|
|
onOpenChange?: (open: boolean) => void;
|
|
variant?: 'default' | 'overlay';
|
|
chatBridge?: import('../hooks/usePageEditSubChat').PageEditSubChatBridge;
|
|
hideOpenFullChat?: boolean;
|
|
title?: string;
|
|
}) {
|
|
const { chatState, pendingTool, newSession, switchSession, session, messages } = useChat();
|
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
const open = controlledOpen ?? internalOpen;
|
|
const setOpen = onOpenChange ?? setInternalOpen;
|
|
const wasOpenRef = useRef(false);
|
|
const restoreSessionIdRef = useRef<string | null>(null);
|
|
const messagesRef = useRef(messages);
|
|
messagesRef.current = messages;
|
|
|
|
useEffect(() => {
|
|
if (chatBridge) {
|
|
wasOpenRef.current = open;
|
|
return;
|
|
}
|
|
|
|
const wasOpen = wasOpenRef.current;
|
|
if (open && !wasOpen) {
|
|
restoreSessionIdRef.current = session?.id ?? null;
|
|
newSession();
|
|
} else if (!open && wasOpen) {
|
|
if (messagesRef.current.length === 0 && restoreSessionIdRef.current) {
|
|
switchSession(restoreSessionIdRef.current);
|
|
}
|
|
restoreSessionIdRef.current = null;
|
|
}
|
|
wasOpenRef.current = open;
|
|
}, [chatBridge, newSession, open, session?.id, switchSession]);
|
|
|
|
useEffect(() => {
|
|
if (chatBridge) return;
|
|
return () => {
|
|
if (messagesRef.current.length === 0 && restoreSessionIdRef.current) {
|
|
switchSession(restoreSessionIdRef.current);
|
|
}
|
|
};
|
|
}, [chatBridge, switchSession]);
|
|
|
|
const prefillMessages = useMemo(() => {
|
|
if (chatBridge || !open) return [];
|
|
if (messages.some((message) => message.role === 'user' && shouldShowChatMessage(message))) return [];
|
|
const selected = context.selectedAssets ?? [];
|
|
if (selected.length !== 1) return [];
|
|
return [buildSelectedAssetGreetingMessage(selected[0])];
|
|
}, [chatBridge, context.selectedAssets, messages, open]);
|
|
|
|
const wrapperClass =
|
|
variant === 'overlay' ? 'space-chat-stack space-chat-stack-overlay' : 'space-chat-stack';
|
|
|
|
return (
|
|
<div className={wrapperClass}>
|
|
{open ? (
|
|
<button
|
|
type="button"
|
|
className="space-chat-backdrop"
|
|
aria-label="收起对话"
|
|
onClick={() => setOpen(false)}
|
|
/>
|
|
) : null}
|
|
<SpaceChatFab
|
|
open={open}
|
|
streaming={chatState === 'waiting' || chatState === 'streaming'}
|
|
pendingTool={Boolean(pendingTool)}
|
|
onClick={() => setOpen(!open)}
|
|
/>
|
|
<SpaceChatPanel
|
|
open={open}
|
|
context={context}
|
|
user={user}
|
|
onClose={() => setOpen(false)}
|
|
onOpenFullChat={onOpenFullChat}
|
|
onPageSaved={onPageSaved}
|
|
chatBridge={chatBridge}
|
|
hideOpenFullChat={hideOpenFullChat}
|
|
title={title}
|
|
prefillMessages={prefillMessages}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|