@@ -58,10 +58,21 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) {
5858 ? geminiApiKey
5959 : "" ;
6060
61- // 生成唯一的会话 ID
62- const [ chatId ] = useState (
63- ( ) => `chat-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` ,
64- ) ;
61+ // 按 slug 从 localStorage 读取或生成持久化会话 ID
62+ // 同一文档页关闭后再打开依然复用同一 chatId,保持会话连续性
63+ const [ chatId ] = useState < string > ( ( ) => {
64+ // SSR 阶段无法访问 localStorage,生成占位 ID(不影响 DOM,不产生 hydration 警告)
65+ if ( typeof window === "undefined" ) {
66+ return `chat-ssr-${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` ;
67+ }
68+ const key = `chat_id:${ pageContext . slug ?? "__global__" } ` ;
69+ const stored = localStorage . getItem ( key ) ;
70+ if ( stored ) return stored ;
71+ const newId = `chat-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` ;
72+ localStorage . setItem ( key , newId ) ;
73+ return newId ;
74+ } ) ;
75+
6576 const chatRuntimeId = useMemo (
6677 ( ) => `${ chatId } :${ provider } :${ hashTransportConfig ( apiKey ) } ` ,
6778 [ chatId , provider , apiKey ] ,
@@ -77,6 +88,16 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) {
7788 apiKey,
7889 chatId,
7990 } ,
91+ // 在每次请求时动态读取 satoken,避免用户登录前创建 transport 导致 token 为空
92+ fetch : async ( url , init ) => {
93+ const token =
94+ typeof window !== "undefined"
95+ ? localStorage . getItem ( "satoken" )
96+ : null ;
97+ const headers = new Headers ( init ?. headers ) ;
98+ if ( token ) headers . set ( "x-satoken" , token ) ;
99+ return fetch ( url , { ...init , headers } ) ;
100+ } ,
80101 } ) ,
81102 [ pageContext , provider , apiKey , chatId ] ,
82103 ) ;
@@ -97,12 +118,22 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) {
97118 const fetchedWelcomeRef = useRef ( false ) ;
98119
99120 // 埋点上报函数
121+ // x-satoken 由服务端验证身份,不在 body 里传 userId(服务端自己解析)
100122 const logAnalyticsEvent = useCallback (
101123 async ( eventType : string , eventData ?: Record < string , unknown > ) => {
102124 try {
125+ const token =
126+ typeof window !== "undefined"
127+ ? localStorage . getItem ( "satoken" )
128+ : null ;
129+ const headers : Record < string , string > = {
130+ "Content-Type" : "application/json" ,
131+ } ;
132+ if ( token ) headers [ "x-satoken" ] = token ;
133+
103134 await fetch ( "/api/analytics" , {
104135 method : "POST" ,
105- headers : { "Content-Type" : "application/json" } ,
136+ headers,
106137 body : JSON . stringify ( {
107138 eventType,
108139 eventData : {
0 commit comments