feat(agents): 智能体归属/发布操作前增加「账号已切换」提醒
- 新增 useLiveAccount 钩子:跟踪「实时」登录账号(随 storage/focus 刷新), 并与组件挂载时捕获的账号对比;token 存 localStorage 会被同浏览器其他标签页 共享,另一标签页登录会静默替换本页 API 所用账号 - agent-card / agent-detail-panel / publish-square-picker-dialog / NewAgentPage 接入该钩子,切换账号后对归属/发布类操作做提醒,避免误归属 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2b6424b3ff
commit
16ba5deb0e
5 files changed
+221
-7
No files matched your search
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { getAccountDisplayName, getStoredAuth } from "@/core/auth";
|
||||
import { isEmbedActive } from "@/core/embed/embed-session";
|
||||
|
||||
const AUTH_STORAGE_KEY = "deerflow.auth";
|
||||
|
||||
export interface LiveAccount {
|
||||
/** 实时登录用户 id(随 storage/focus 刷新)。null = 未登录。 */
|
||||
userId: string | null;
|
||||
/** 实时显示名(邮箱 @ 前缀,否则 user_id)。 */
|
||||
displayName: string;
|
||||
/** 组件首次挂载时捕获的用户 id。 */
|
||||
mountUserId: string | null;
|
||||
/** 组件首次挂载时捕获的显示名。 */
|
||||
mountDisplayName: string;
|
||||
/**
|
||||
* 当前登录账号与挂载时不一致 —— 即本视图加载后登录被切换过(通常是在另一个
|
||||
* 标签页登录了别的账号)。归属/发布类操作必须据此提醒,避免误归属。
|
||||
*/
|
||||
switched: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪「实时」登录账号,而不是挂载时缓存的值。
|
||||
*
|
||||
* 鉴权 token 存在 localStorage(非嵌入态下被同浏览器所有标签页共享),所以在
|
||||
* 另一个标签页登录会**静默替换**本标签页 API 调用所用的 token。任何会决定归属的
|
||||
* 界面(创建/发布智能体)都必须显示请求实际将以哪个账号执行,并在会话中途被切换
|
||||
* 账号时给出提示 —— 否则会出现「用 A 账号创建却归到 B 账号名下」。
|
||||
*/
|
||||
export function useLiveAccount(): LiveAccount {
|
||||
const mountRef = useRef<{ id: string | null; name: string } | null>(null);
|
||||
if (mountRef.current === null) {
|
||||
const auth = getStoredAuth();
|
||||
mountRef.current = {
|
||||
id: auth?.user_id ?? null,
|
||||
name: getAccountDisplayName(auth),
|
||||
};
|
||||
}
|
||||
const [live, setLive] = useState(() => mountRef.current!);
|
||||
|
||||
useEffect(() => {
|
||||
function refresh() {
|
||||
const auth = getStoredAuth();
|
||||
const id = auth?.user_id ?? null;
|
||||
const name = getAccountDisplayName(auth);
|
||||
setLive((prev) =>
|
||||
prev.id === id && prev.name === name ? prev : { id, name },
|
||||
);
|
||||
}
|
||||
// 嵌入(iframe)态鉴权走按标签页的 sessionStorage、不跨标签,收不到 storage 事件
|
||||
// —— 只挂在正常态;focus/visibilitychange 兜底刷新两种形态都有效。
|
||||
function onStorage(e: StorageEvent) {
|
||||
if (e.key === AUTH_STORAGE_KEY) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
if (!isEmbedActive()) {
|
||||
window.addEventListener("storage", onStorage);
|
||||
}
|
||||
window.addEventListener("focus", refresh);
|
||||
document.addEventListener("visibilitychange", refresh);
|
||||
refresh();
|
||||
return () => {
|
||||
window.removeEventListener("storage", onStorage);
|
||||
window.removeEventListener("focus", refresh);
|
||||
document.removeEventListener("visibilitychange", refresh);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const mount = mountRef.current!;
|
||||
return {
|
||||
userId: live.id,
|
||||
displayName: live.name,
|
||||
mountUserId: mount.id,
|
||||
mountDisplayName: mount.name,
|
||||
switched: live.id !== null && mount.id !== null && live.id !== mount.id,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user