refactor(frontend): 将会话状态标识从 show_reuse_welcome 重命名为 is_chatting

This commit is contained in:
肖应宇 2026-04-08 11:44:42 +08:00
parent 6d66cdd3f5
commit a8cfe1c42e
7 changed files with 92 additions and 63 deletions

View File

@ -37,6 +37,7 @@ import { textOfMessage } from "@/core/threads/utils";
import { env } from "@/env"; import { env } from "@/env";
import { useSelectedSkillListener } from "@/hooks/use-selected-skill-listener"; import { useSelectedSkillListener } from "@/hooks/use-selected-skill-listener";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { IframeTestPanel } from "@/components/workspace/iframe-test-panel";
export default function ChatPage() { export default function ChatPage() {
const { t } = useI18n(); const { t } = useI18n();
@ -63,7 +64,7 @@ export default function ChatPage() {
showWelcomeStyle, showWelcomeStyle,
} = useThreadChat(); } = useThreadChat();
// 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/show_reuse_welcome 参数。 // 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/is_chatting 参数。
const shouldRenderHistory = !showWelcomeStyle; const shouldRenderHistory = !showWelcomeStyle;
const createNewSession = useMemo(() => isNewThread, [isNewThread]); const createNewSession = useMemo(() => isNewThread, [isNewThread]);
@ -485,8 +486,8 @@ export default function ChatPage() {
} }
setShowExitDialog(false); setShowExitDialog(false);
sendToParent({ sendToParent({
type: POST_MESSAGE_TYPES.SHOW_REUSE_WELCOME, type: POST_MESSAGE_TYPES.IS_CHATTING,
showReuseWelcome: true, isChatting: false,
}); });
resetNewSessionState(); resetNewSessionState();
// 始终复用 query 中的 thread_id。 // 始终复用 query 中的 thread_id。
@ -532,7 +533,7 @@ export default function ChatPage() {
</DevDialog> </DevDialog>
{/* MARK: 开发测试iframe 通信功能测试面板 */} {/* MARK: 开发测试iframe 通信功能测试面板 */}
{/* {process.env.NODE_ENV !== "production" && <IframeTestPanel />} */} {process.env.NODE_ENV !== "production" && <IframeTestPanel />}
</div> </div>
</ThreadContext.Provider> </ThreadContext.Provider>
); );

View File

@ -10,9 +10,9 @@ export function useThreadChat() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const threadIdFromSearchParams = searchParams.get("thread_id")?.trim(); const threadIdFromSearchParams = searchParams.get("thread_id")?.trim();
// showWelcomeStyle的子判断 // showWelcomeStyle的子判断
const showReuseWelcomeFromQuery = (() => { const isChattingFromQuery = (() => {
const showReuseWelcome = searchParams.get("show_reuse_welcome"); const isChatting = searchParams.get("is_chatting");
return showReuseWelcome === "true"; return isChatting === "true";
})(); })();
// 兜底:当 params 还未就绪时,从 pathname 解析 thread_id。 // 兜底:当 params 还未就绪时,从 pathname 解析 thread_id。
const threadIdFromPathname = (() => { const threadIdFromPathname = (() => {
@ -77,7 +77,7 @@ export function useThreadChat() {
const [isNewThread, setIsNewThread] = useState(() => isNewRoute); const [isNewThread, setIsNewThread] = useState(() => isNewRoute);
const [showWelcomeStyle, setShowWelcomeStyle] = useState(() => { const [showWelcomeStyle, setShowWelcomeStyle] = useState(() => {
return isNewRoute || showReuseWelcomeFromQuery; return isNewRoute || !isChattingFromQuery;
}); });
// console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath); // console.log("[useThreadChat] effectiveThreadIdFromPath", effectiveThreadIdFromPath);
@ -94,13 +94,13 @@ export function useThreadChat() {
setIsNewThread(isNewRoute); setIsNewThread(isNewRoute);
// Prefer path thread id, fall back to query thread_id when path is /new. // Prefer path thread id, fall back to query thread_id when path is /new.
setThreadId(threadIdFromPathOrParams); setThreadId(threadIdFromPathOrParams);
setShowWelcomeStyle(isNewRoute || showReuseWelcomeFromQuery); setShowWelcomeStyle(isNewRoute || !isChattingFromQuery);
}, [ }, [
isNewRoute, isNewRoute,
normalizeThreadId, normalizeThreadId,
pathname, pathname,
searchParams, searchParams,
showReuseWelcomeFromQuery, isChattingFromQuery,
threadId, threadId,
threadIdFromPathOrParams, threadIdFromPathOrParams,
]); ]);

View File

@ -46,7 +46,7 @@ export function IframeTestPanel() {
} }
function handleEnterSkillMode() { function handleEnterSkillMode() {
router.push(`?mode=skill&skill_id=123&title=测试技能`); router.push(`?mode=skill`);
addLog("进入 mode=skillURL 已更新"); addLog("进入 mode=skillURL 已更新");
} }
@ -56,8 +56,13 @@ export function IframeTestPanel() {
} }
function handleSendSelectSkill() { function handleSendSelectSkill() {
iframeSkill.sendSelectSkill("skill_001"); iframeSkill.sendSelectSkill(["skill_001"]);
addLog("postMessage → selectSkill (skill_id=skill_001)"); addLog("postMessage → selectSkill (skill_id=['skill_001'])");
}
function handleSendSelectSkillArray() {
iframeSkill.sendSelectSkill(["1246", "1247", "1248"]);
addLog("postMessage → selectSkill (skill_id=['1246','1247','1248'])");
} }
function handleOpenSkillDialog() { function handleOpenSkillDialog() {
@ -67,7 +72,7 @@ export function IframeTestPanel() {
function handleClearSkill() { function handleClearSkill() {
iframeSkill.clearSkill(); iframeSkill.clearSkill();
addLog("clearSkill 已调用postMessage → skill_id=0"); addLog("clearSkill 已调用postMessage → skill_id=[]");
} }
function handleTestClipboardCopy() { function handleTestClipboardCopy() {
@ -76,12 +81,12 @@ export function IframeTestPanel() {
addLog(`copyToClipboard → "${testText.slice(0, 30)}..."`); addLog(`copyToClipboard → "${testText.slice(0, 30)}..."`);
} }
function handleSendShowReuseWelcome(showReuseWelcome: boolean) { function handleSendIsChatting(isChatting: boolean) {
sendToParent({ sendToParent({
type: POST_MESSAGE_TYPES.SHOW_REUSE_WELCOME, type: POST_MESSAGE_TYPES.IS_CHATTING,
showReuseWelcome: showReuseWelcome, isChatting: isChatting,
}); });
addLog(`postMessage → show_reuse_welcome (${showReuseWelcome})`); addLog(`postMessage → is_chatting (${isChatting})`);
} }
function handlePointerDown(event: ReactPointerEvent<HTMLDivElement>) { function handlePointerDown(event: ReactPointerEvent<HTMLDivElement>) {
@ -231,7 +236,15 @@ export function IframeTestPanel() {
variant="ghost" variant="ghost"
onClick={handleSendSelectSkill} onClick={handleSendSelectSkill}
> >
sendSelectSkill (skill_001) sendSelectSkill
</Button>
<Button
size="sm"
className="w-full bg-violet-50 text-xs text-violet-700 hover:bg-violet-100"
variant="ghost"
onClick={handleSendSelectSkillArray}
>
sendSelectSkill
</Button> </Button>
<Button <Button
size="sm" size="sm"
@ -247,7 +260,7 @@ export function IframeTestPanel() {
variant="ghost" variant="ghost"
onClick={handleClearSkill} onClick={handleClearSkill}
> >
clearSkill ( skill_id=0) clearSkill ( skill_id=[])
</Button> </Button>
</div> </div>
</div> </div>
@ -327,17 +340,17 @@ export function IframeTestPanel() {
</div> </div>
</div> </div>
{/* 场景 5show_reuse_welcome */} {/* 场景 5is_chatting */}
<div> <div>
<div className="mb-1 text-xs font-semibold text-gray-500"> <div className="mb-1 text-xs font-semibold text-gray-500">
show_reuse_welcome is_chatting
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
size="sm" size="sm"
className="flex-1 bg-emerald-50 text-xs text-emerald-700 hover:bg-emerald-100" className="flex-1 bg-emerald-50 text-xs text-emerald-700 hover:bg-emerald-100"
variant="ghost" variant="ghost"
onClick={() => handleSendShowReuseWelcome(true)} onClick={() => handleSendIsChatting(true)}
> >
true true
</Button> </Button>
@ -345,7 +358,7 @@ export function IframeTestPanel() {
size="sm" size="sm"
className="flex-1 bg-slate-50 text-xs text-slate-700 hover:bg-slate-100" className="flex-1 bg-slate-50 text-xs text-slate-700 hover:bg-slate-100"
variant="ghost" variant="ghost"
onClick={() => handleSendShowReuseWelcome(false)} onClick={() => handleSendIsChatting(false)}
> >
false false
</Button> </Button>

View File

@ -213,8 +213,8 @@ export function InputBox({
setIsFocused(false); setIsFocused(false);
if (showWelcomeStyle) { if (showWelcomeStyle) {
sendToParent({ sendToParent({
type: POST_MESSAGE_TYPES.SHOW_REUSE_WELCOME, type: POST_MESSAGE_TYPES.IS_CHATTING,
showReuseWelcome: false, isChatting: true,
}); });
} }
onSubmit?.(message); onSubmit?.(message);
@ -491,7 +491,7 @@ export function InputBox({
function SuggestionListContainer({ function SuggestionListContainer({
sendSelectSkill, sendSelectSkill,
}: { }: {
sendSelectSkill: (skill_id: string) => void; sendSelectSkill: (skill_id: string[]) => void;
}) { }) {
return ( return (
<div className="absolute right-0 bottom-0 left-0 z-0 flex translate-y-full items-center justify-center pt-4"> <div className="absolute right-0 bottom-0 left-0 z-0 flex translate-y-full items-center justify-center pt-4">
@ -504,7 +504,7 @@ function SuggestionListContainer({
function SuggestionList({ function SuggestionList({
sendSelectSkill, sendSelectSkill,
}: { }: {
sendSelectSkill: (skill_id: string) => void; sendSelectSkill: (skill_id: string[]) => void;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const { textInput } = usePromptInputController(); const { textInput } = usePromptInputController();
@ -517,9 +517,23 @@ function SuggestionList({
); );
const handleSuggestionClick = useCallback( const handleSuggestionClick = useCallback(
(suggestion: { prompt: string; skill_id?: string }) => { (
// 如果有 skill_id发送给宿主页 suggestion: {
if (suggestion.skill_id) { prompt: string;
skill_id?: string[];
children?: { skill_id: string[] }[];
},
) => {
// 优先从 children 中提取 skill_id 数组,发送给宿主页
const childSkillIds = (suggestion.children ?? [])
.flatMap((item) => item.skill_id)
.map((item) => item.trim())
.filter((id): id is string => Boolean(id));
if (childSkillIds.length > 0) {
sendSelectSkill(childSkillIds);
return;
}
if (suggestion.skill_id && suggestion.skill_id.length > 0) {
sendSelectSkill(suggestion.skill_id); sendSelectSkill(suggestion.skill_id);
return; return;
} }

View File

@ -9,8 +9,8 @@
export const POST_MESSAGE_TYPES = { export const POST_MESSAGE_TYPES = {
// 全屏切换 // 全屏切换
FULLSCREEN: "fullscreen", FULLSCREEN: "fullscreen",
// 是否展示复用欢迎 // 会话是否处于聊天
SHOW_REUSE_WELCOME: "showReuseWelcome", IS_CHATTING: "isChatting",
// 选择预定义 skill // 选择预定义 skill
SELECT_SKILL: "selectSkill", SELECT_SKILL: "selectSkill",
// 打开 skill 选择对话框 // 打开 skill 选择对话框
@ -35,14 +35,14 @@ export interface FullscreenMessage {
fullscreen: boolean; fullscreen: boolean;
} }
export interface ShowReuseWelcomeMessage { export interface IsChattingMessage {
type: typeof POST_MESSAGE_TYPES.SHOW_REUSE_WELCOME; type: typeof POST_MESSAGE_TYPES.IS_CHATTING;
showReuseWelcome: boolean; isChatting: boolean;
} }
export interface SelectSkillMessage { export interface SelectSkillMessage {
type: typeof POST_MESSAGE_TYPES.SELECT_SKILL; type: typeof POST_MESSAGE_TYPES.SELECT_SKILL;
skill_id: string; skill_id: string[];
} }
export interface OpenSkillDialogMessage { export interface OpenSkillDialogMessage {
@ -79,7 +79,7 @@ export function isSelectedSkillMessage(value: unknown): value is SelectedSkillMe
export function sendToParent( export function sendToParent(
message: message:
| FullscreenMessage | FullscreenMessage
| ShowReuseWelcomeMessage | IsChattingMessage
| SelectSkillMessage | SelectSkillMessage
| OpenSkillDialogMessage, | OpenSkillDialogMessage,
): void { ): void {

View File

@ -17,7 +17,7 @@ interface SkillData {
// Hook 返回类型 // Hook 返回类型
interface UseIframeSkillReturn { interface UseIframeSkillReturn {
selectedSkill: SkillData | null; selectedSkill: SkillData | null;
sendSelectSkill: (skill_id: string) => void; sendSelectSkill: (skill_id: string[]) => void;
openSkillDialog: () => void; openSkillDialog: () => void;
clearSkill: () => void; clearSkill: () => void;
} }
@ -25,29 +25,30 @@ interface UseIframeSkillReturn {
export function useIframeSkill(): UseIframeSkillReturn { export function useIframeSkill(): UseIframeSkillReturn {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const skillIdFromQuery = searchParams.get("skill_id");
const titleFromQuery = searchParams.get("title");
const threadIdFromQuery = searchParams.get("thread_id"); const threadIdFromQuery = searchParams.get("thread_id");
const showReuseWelcomeFromQuery = searchParams.get("show_reuse_welcome"); const isChattingFromQuery = searchParams.get("is_chatting");
const lastThreadIdRef = useRef<string | null>(null); const lastThreadIdRef = useRef<string | null>(null);
const [selectedSkill, setSelectedSkill] = useState<SkillData | null>(null); const [selectedSkill, setSelectedSkill] = useState<SkillData | null>(null);
// 1. 监听 query 参数变化 // 1. 监听 query 参数变化(临时禁用)
useEffect(() => { // TODO: 当前 skill 仅通过 iframe postMessage 传递,暂不从 URL 读取 skill_id/title。
if (skillIdFromQuery && titleFromQuery) { // useEffect(() => {
setSelectedSkill({ skill_id: skillIdFromQuery, title: titleFromQuery }); // const skillIdFromQuery = searchParams.get("skill_id");
} // const titleFromQuery = searchParams.get("title");
}, [skillIdFromQuery, titleFromQuery]); // if (skillIdFromQuery && titleFromQuery) {
// setSelectedSkill({ skill_id: skillIdFromQuery, title: titleFromQuery });
// }
// }, [searchParams]);
// 0. 监听 query 中 show_reuse_welcome=false 且带 thread_id 时跳转到 thread 页面 // 0. 监听 query 中 is_chatting=true 且带 thread_id 时跳转到 thread 页面
useEffect(() => { useEffect(() => {
if (!threadIdFromQuery) return; if (!threadIdFromQuery) return;
if (showReuseWelcomeFromQuery !== "false") return; if (isChattingFromQuery !== "true") return;
if (lastThreadIdRef.current === threadIdFromQuery) return; if (lastThreadIdRef.current === threadIdFromQuery) return;
lastThreadIdRef.current = threadIdFromQuery; lastThreadIdRef.current = threadIdFromQuery;
router.replace(`/workspace/chats/${threadIdFromQuery}`); router.replace(`/workspace/chats/${threadIdFromQuery}`);
}, [router, showReuseWelcomeFromQuery, threadIdFromQuery]); }, [isChattingFromQuery, router, threadIdFromQuery]);
// 2. 监听宿主页 postMessage // 2. 监听宿主页 postMessage
useEffect(() => { useEffect(() => {
@ -67,7 +68,7 @@ export function useIframeSkill(): UseIframeSkillReturn {
}, []); }, []);
// 发送选择预定义 skill // 发送选择预定义 skill
const sendSelectSkill = useCallback((skill_id: string) => { const sendSelectSkill = useCallback((skill_id: string[]) => {
const message = { type: POST_MESSAGE_TYPES.SELECT_SKILL, skill_id }; const message = { type: POST_MESSAGE_TYPES.SELECT_SKILL, skill_id };
console.log("[useIframeSkill] sendSelectSkill:", message); console.log("[useIframeSkill] sendSelectSkill:", message);
sendToParent(message); sendToParent(message);
@ -83,12 +84,12 @@ export function useIframeSkill(): UseIframeSkillReturn {
sendToParent(message); sendToParent(message);
}, []); }, []);
// 清除选中并发送 skill_id=0 给主页 // 清除选中并发送 skill_id 数组给主页
const clearSkill = useCallback(() => { const clearSkill = useCallback(() => {
setSelectedSkill(null); setSelectedSkill(null);
// 发送 skill_id=0 给主页,通知取消选择 // 发送空数组给主页,通知取消选择
const message = { type: POST_MESSAGE_TYPES.SELECT_SKILL, skill_id: "0" }; const message = { type: POST_MESSAGE_TYPES.SELECT_SKILL, skill_id: [] };
console.log("[useIframeSkill] clearSkill, sending skill_id=0:", message); console.log("[useIframeSkill] clearSkill, sending skill_id=[]:", message);
sendToParent(message); sendToParent(message);
}, []); }, []);

View File

@ -29,11 +29,11 @@ export function skipIfMissingThread(
export function buildChatUrl({ export function buildChatUrl({
pathThreadId, pathThreadId,
showReuseWelcome, isChatting,
threadId, threadId,
}: { }: {
pathThreadId?: string; pathThreadId?: string;
showReuseWelcome: boolean; isChatting: boolean;
threadId: string; threadId: string;
}) { }) {
const resolvedThreadId = threadId ?? pathThreadId; const resolvedThreadId = threadId ?? pathThreadId;
@ -42,7 +42,7 @@ export function buildChatUrl({
} }
const query = new URLSearchParams(); const query = new URLSearchParams();
query.set("show_reuse_welcome", String(showReuseWelcome)); query.set("is_chatting", String(isChatting));
if (resolvedThreadId) { if (resolvedThreadId) {
query.set("thread_id", resolvedThreadId); query.set("thread_id", resolvedThreadId);
} }
@ -55,20 +55,20 @@ export function buildChatUrl({
export function invalidNewChatUrl() { export function invalidNewChatUrl() {
const query = new URLSearchParams(); const query = new URLSearchParams();
query.set("show_reuse_welcome", "true"); query.set("is_chatting", "false");
return `/workspace/chats/new?${query.toString()}`; return `/workspace/chats/new?${query.toString()}`;
} }
export function newChatEntry(threadId: string) { export function newChatEntry(threadId: string) {
return buildChatUrl({ return buildChatUrl({
showReuseWelcome: true, isChatting: false,
threadId, threadId,
}); });
} }
export function reuseThreadWelcomeEntry(threadId: string) { export function reuseThreadWelcomeEntry(threadId: string) {
return buildChatUrl({ return buildChatUrl({
showReuseWelcome: true, isChatting: false,
threadId, threadId,
}); });
} }
@ -76,7 +76,7 @@ export function reuseThreadWelcomeEntry(threadId: string) {
export function reuseThreadChatEntry(threadId: string) { export function reuseThreadChatEntry(threadId: string) {
return buildChatUrl({ return buildChatUrl({
pathThreadId: threadId, pathThreadId: threadId,
showReuseWelcome: false, isChatting: true,
threadId, threadId,
}); });
} }