Compare commits
3 Commits
5cfb7cc38f
...
536cd6a5d1
| Author | SHA1 | Date | |
|---|---|---|---|
| 536cd6a5d1 | |||
| 133f5d417f | |||
| cf43700459 |
@@ -6,4 +6,5 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,19 +155,21 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
anchor="right"
|
anchor="right"
|
||||||
variant="persistent"
|
variant="temporary"
|
||||||
open={open}
|
open={open}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
hideBackdrop
|
hideBackdrop
|
||||||
|
disableScrollLock
|
||||||
|
disableEnforceFocus
|
||||||
sx={{ zIndex: (muiTheme) => muiTheme.zIndex.modal + 100 }}
|
sx={{ zIndex: (muiTheme) => muiTheme.zIndex.modal + 100 }}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
width: { xs: "100%", sm: width },
|
width: { xs: "100%", sm: width },
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
overflow: "visible",
|
overflow: open ? "visible" : "hidden",
|
||||||
zIndex: (muiTheme) => muiTheme.zIndex.modal + 100,
|
zIndex: (muiTheme) => muiTheme.zIndex.modal + 100,
|
||||||
transition: isResizing ? "none" : "width 0.2s cubic-bezier(0, 0, 0.2, 1)",
|
transition: isResizing ? "none" : undefined,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -68,14 +68,6 @@ const toSessionSummary = (session: ChatSessionRecord): ChatSessionSummary => ({
|
|||||||
updatedAt: session.updatedAt,
|
updatedAt: session.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildSessionTitle = (messages: Message[]) => {
|
|
||||||
const firstUserMessage = messages.find((message) => message.role === "user");
|
|
||||||
if (!firstUserMessage) return "新对话";
|
|
||||||
const title = firstUserMessage.content.replace(/\s+/g, " ").trim();
|
|
||||||
if (!title) return "新对话";
|
|
||||||
return title.length > 24 ? `${title.slice(0, 24)}...` : title;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDb = () =>
|
const getDb = () =>
|
||||||
openDB<ChatDB>(CHAT_DB_NAME, CHAT_DB_VERSION, {
|
openDB<ChatDB>(CHAT_DB_NAME, CHAT_DB_VERSION, {
|
||||||
upgrade(db) {
|
upgrade(db) {
|
||||||
@@ -170,7 +162,7 @@ const migrateLegacyLocalStorage = async () => {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const sessionRecord: ChatSessionRecord = {
|
const sessionRecord: ChatSessionRecord = {
|
||||||
id: createId(),
|
id: createId(),
|
||||||
title: buildSessionTitle(legacyState.messages),
|
title: "新对话",
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
sessionId: legacyState.sessionId,
|
sessionId: legacyState.sessionId,
|
||||||
@@ -244,9 +236,8 @@ export const saveActiveChatState = async (
|
|||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const storageSessionId = state.storageSessionId ?? createId();
|
const storageSessionId = state.storageSessionId ?? createId();
|
||||||
const computedTitle = buildSessionTitle(state.messages);
|
|
||||||
const preferredTitle = state.title?.trim();
|
const preferredTitle = state.title?.trim();
|
||||||
const finalTitle = preferredTitle || computedTitle;
|
const finalTitle = preferredTitle || existingSession?.title || "新对话";
|
||||||
const nextRecord: ChatSessionRecord = {
|
const nextRecord: ChatSessionRecord = {
|
||||||
id: storageSessionId,
|
id: storageSessionId,
|
||||||
title: finalTitle,
|
title: finalTitle,
|
||||||
@@ -278,6 +269,26 @@ export const listChatSessions = async (): Promise<ChatSessionSummary[]> => {
|
|||||||
.map(toSessionSummary);
|
.map(toSessionSummary);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateChatSessionTitle = async (
|
||||||
|
storageSessionId: string,
|
||||||
|
title: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
|
const normalizedTitle = title.trim();
|
||||||
|
if (!normalizedTitle) return;
|
||||||
|
|
||||||
|
const db = await getDb();
|
||||||
|
const session = await db.get(SESSION_STORE, storageSessionId);
|
||||||
|
if (!session) return;
|
||||||
|
|
||||||
|
await db.put(SESSION_STORE, {
|
||||||
|
...session,
|
||||||
|
title: normalizedTitle,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const createEmptyChatSession = async (): Promise<LoadedChatState> => {
|
export const createEmptyChatSession = async (): Promise<LoadedChatState> => {
|
||||||
if (typeof window === "undefined") return emptyLoadedChatState();
|
if (typeof window === "undefined") return emptyLoadedChatState();
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
loadActiveChatState,
|
loadActiveChatState,
|
||||||
loadChatSessionById,
|
loadChatSessionById,
|
||||||
saveActiveChatState,
|
saveActiveChatState,
|
||||||
|
updateChatSessionTitle,
|
||||||
} from "../chatStorage";
|
} from "../chatStorage";
|
||||||
|
|
||||||
type UseAgentChatSessionOptions = {
|
type UseAgentChatSessionOptions = {
|
||||||
@@ -110,6 +111,7 @@ export const useAgentChatSession = ({
|
|||||||
const abortRef = useRef<AbortController | null>(null);
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
const sessionIdRef = useRef<string | undefined>(undefined);
|
const sessionIdRef = useRef<string | undefined>(undefined);
|
||||||
const cancelPromiseRef = useRef<Promise<void> | null>(null);
|
const cancelPromiseRef = useRef<Promise<void> | null>(null);
|
||||||
|
const titleUpdateNonceRef = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sessionIdRef.current = sessionId;
|
sessionIdRef.current = sessionId;
|
||||||
@@ -130,6 +132,7 @@ export const useAgentChatSession = ({
|
|||||||
sessionIdRef.current = loadedState.sessionId;
|
sessionIdRef.current = loadedState.sessionId;
|
||||||
hydrationCompletedRef.current = true;
|
hydrationCompletedRef.current = true;
|
||||||
hydrationNonceRef.current += 1;
|
hydrationNonceRef.current += 1;
|
||||||
|
titleUpdateNonceRef.current += 1;
|
||||||
|
|
||||||
setMessages(loadedState.messages);
|
setMessages(loadedState.messages);
|
||||||
setSessionTitle(loadedState.title);
|
setSessionTitle(loadedState.title);
|
||||||
@@ -308,6 +311,19 @@ export const useAgentChatSession = ({
|
|||||||
const nextTitle = event.title.trim();
|
const nextTitle = event.title.trim();
|
||||||
if (nextTitle) {
|
if (nextTitle) {
|
||||||
setSessionTitle(nextTitle);
|
setSessionTitle(nextTitle);
|
||||||
|
const currentStorageSessionId = storageSessionIdRef.current;
|
||||||
|
if (currentStorageSessionId) {
|
||||||
|
const currentNonce = ++titleUpdateNonceRef.current;
|
||||||
|
void updateChatSessionTitle(currentStorageSessionId, nextTitle)
|
||||||
|
.then(() => listChatSessions())
|
||||||
|
.then((sessions) => {
|
||||||
|
if (titleUpdateNonceRef.current !== currentNonce) return;
|
||||||
|
setChatSessions(sessions);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("[GlobalChatbox] Failed to persist session title:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (event.type === "done") {
|
} else if (event.type === "done") {
|
||||||
setMessages((prev) =>
|
setMessages((prev) =>
|
||||||
@@ -431,6 +447,7 @@ export const useAgentChatSession = ({
|
|||||||
setSessionId(undefined);
|
setSessionId(undefined);
|
||||||
sessionIdRef.current = undefined;
|
sessionIdRef.current = undefined;
|
||||||
storageSessionIdRef.current = undefined;
|
storageSessionIdRef.current = undefined;
|
||||||
|
titleUpdateNonceRef.current += 1;
|
||||||
setIsStreaming(false);
|
setIsStreaming(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -445,6 +462,7 @@ export const useAgentChatSession = ({
|
|||||||
const sessions = await listChatSessions();
|
const sessions = await listChatSessions();
|
||||||
|
|
||||||
hydrationNonceRef.current += 1;
|
hydrationNonceRef.current += 1;
|
||||||
|
titleUpdateNonceRef.current += 1;
|
||||||
storageSessionIdRef.current = newState.storageSessionId;
|
storageSessionIdRef.current = newState.storageSessionId;
|
||||||
sessionIdRef.current = newState.sessionId;
|
sessionIdRef.current = newState.sessionId;
|
||||||
setMessages(newState.messages);
|
setMessages(newState.messages);
|
||||||
@@ -469,6 +487,7 @@ export const useAgentChatSession = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
hydrationNonceRef.current += 1;
|
hydrationNonceRef.current += 1;
|
||||||
|
titleUpdateNonceRef.current += 1;
|
||||||
storageSessionIdRef.current = nextState.storageSessionId;
|
storageSessionIdRef.current = nextState.storageSessionId;
|
||||||
sessionIdRef.current = nextState.sessionId;
|
sessionIdRef.current = nextState.sessionId;
|
||||||
setBranchTransition(null);
|
setBranchTransition(null);
|
||||||
@@ -501,6 +520,7 @@ export const useAgentChatSession = ({
|
|||||||
|
|
||||||
if (!nextActiveSessionId) {
|
if (!nextActiveSessionId) {
|
||||||
hydrationNonceRef.current += 1;
|
hydrationNonceRef.current += 1;
|
||||||
|
titleUpdateNonceRef.current += 1;
|
||||||
storageSessionIdRef.current = undefined;
|
storageSessionIdRef.current = undefined;
|
||||||
sessionIdRef.current = undefined;
|
sessionIdRef.current = undefined;
|
||||||
setBranchTransition(null);
|
setBranchTransition(null);
|
||||||
@@ -517,6 +537,7 @@ export const useAgentChatSession = ({
|
|||||||
listChatSessions(),
|
listChatSessions(),
|
||||||
]);
|
]);
|
||||||
hydrationNonceRef.current += 1;
|
hydrationNonceRef.current += 1;
|
||||||
|
titleUpdateNonceRef.current += 1;
|
||||||
storageSessionIdRef.current = nextState.storageSessionId;
|
storageSessionIdRef.current = nextState.storageSessionId;
|
||||||
sessionIdRef.current = nextState.sessionId;
|
sessionIdRef.current = nextState.sessionId;
|
||||||
setBranchTransition(null);
|
setBranchTransition(null);
|
||||||
|
|||||||
@@ -49,3 +49,30 @@ export const getAccessToken = async () => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getUserId = async () => {
|
||||||
|
const session = await getSession();
|
||||||
|
const sessionUserId = typeof session?.user?.id === "string" ? session.user.id : null;
|
||||||
|
if (sessionUserId) {
|
||||||
|
return sessionUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = await getAccessToken();
|
||||||
|
if (!accessToken) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = decodeJwtPayload(accessToken);
|
||||||
|
if (!payload || typeof payload !== "object") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidate =
|
||||||
|
typeof payload.sub === "string"
|
||||||
|
? payload.sub
|
||||||
|
: typeof payload.user_id === "string"
|
||||||
|
? payload.user_id
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
};
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export const streamAgentChat = async ({
|
|||||||
session_id: sessionId,
|
session_id: sessionId,
|
||||||
}),
|
}),
|
||||||
projectHeaderMode: "include",
|
projectHeaderMode: "include",
|
||||||
|
userHeaderMode: "include",
|
||||||
skipAuthRedirect: true,
|
skipAuthRedirect: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -229,6 +230,7 @@ export const abortAgentChat = async (sessionId?: string) => {
|
|||||||
session_id: sessionId,
|
session_id: sessionId,
|
||||||
}),
|
}),
|
||||||
projectHeaderMode: "include",
|
projectHeaderMode: "include",
|
||||||
|
userHeaderMode: "include",
|
||||||
skipAuthRedirect: true,
|
skipAuthRedirect: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -249,6 +251,7 @@ export const forkAgentChat = async (sessionId: string | undefined, keepMessageCo
|
|||||||
keep_message_count: keepMessageCount,
|
keep_message_count: keepMessageCount,
|
||||||
}),
|
}),
|
||||||
projectHeaderMode: "include",
|
projectHeaderMode: "include",
|
||||||
|
userHeaderMode: "include",
|
||||||
skipAuthRedirect: true,
|
skipAuthRedirect: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { getAccessToken } from "@/lib/authToken";
|
import { getAccessToken, getUserId } from "@/lib/authToken";
|
||||||
import { useProjectStore } from "@/store/projectStore";
|
import { useProjectStore } from "@/store/projectStore";
|
||||||
|
|
||||||
export type AuthHeaderMode = "include" | "omit";
|
export type AuthHeaderMode = "include" | "omit";
|
||||||
export type ProjectHeaderMode = "auto" | "include" | "omit";
|
export type ProjectHeaderMode = "auto" | "include" | "omit";
|
||||||
|
export type UserHeaderMode = "include" | "omit";
|
||||||
|
|
||||||
export interface AuthContextHeaderOptions {
|
export interface AuthContextHeaderOptions {
|
||||||
authHeaderMode?: AuthHeaderMode;
|
authHeaderMode?: AuthHeaderMode;
|
||||||
projectHeaderMode?: ProjectHeaderMode;
|
projectHeaderMode?: ProjectHeaderMode;
|
||||||
|
userHeaderMode?: UserHeaderMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldIncludeProjectHeader = (
|
const shouldIncludeProjectHeader = (
|
||||||
@@ -34,6 +36,13 @@ export const applyAuthContextHeaders = async (
|
|||||||
headers.set("Authorization", `Bearer ${accessToken}`);
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.userHeaderMode === "include") {
|
||||||
|
const userId = await getUserId();
|
||||||
|
if (userId) {
|
||||||
|
headers.set("X-User-Id", userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const projectId = useProjectStore.getState().currentProjectId;
|
const projectId = useProjectStore.getState().currentProjectId;
|
||||||
if (
|
if (
|
||||||
projectId &&
|
projectId &&
|
||||||
|
|||||||
Reference in New Issue
Block a user