mafzaal commited on
Commit
8190e40
·
1 Parent(s): 57dd0f3

Implement cookie-less session management and user ID handling for Hugging Face Spaces compatibility

Browse files

- Introduced cookie-less session management by storing user IDs in localStorage and transmitting them via HTTP headers and URL parameters.
- Updated FastAPI endpoints to retrieve user IDs from headers and query parameters, ensuring backward compatibility with cookie-based implementations.
- Enhanced the RAG pipeline to accept user IDs for tracking in Hugging Face deployments.
- Modified frontend to include user ID in all API requests using an axios interceptor, improving session management across the application.
- Updated documentation to reflect changes in session management and user ID handling.

HUGGINGFACE.md CHANGED
@@ -16,6 +16,7 @@ This RAG (Retrieval-Augmented Generation) Chat application allows users to:
16
  - **Interactive Chat:** Ask questions about the uploaded documents
17
  - **Knowledge Quiz:** Test your understanding with automatically generated quizzes
18
  - **Thinking/Answer Format:** See the AI's reasoning process with expandable "thinking" sections
 
19
 
20
  ## Deployment Instructions
21
 
@@ -63,6 +64,16 @@ You can customize your deployment by modifying:
63
  - `api/main.py` - Backend API endpoints
64
  - `app/frontend/src/` - Frontend React components
65
 
 
 
 
 
 
 
 
 
 
 
66
  ## Troubleshooting
67
 
68
  If you encounter issues:
 
16
  - **Interactive Chat:** Ask questions about the uploaded documents
17
  - **Knowledge Quiz:** Test your understanding with automatically generated quizzes
18
  - **Thinking/Answer Format:** See the AI's reasoning process with expandable "thinking" sections
19
+ - **Cookie-less Session Management:** Uses localStorage and HTTP headers for user sessions instead of cookies for better HuggingFace Spaces compatibility
20
 
21
  ## Deployment Instructions
22
 
 
64
  - `api/main.py` - Backend API endpoints
65
  - `app/frontend/src/` - Frontend React components
66
 
67
+ ## Notes on Session Management
68
+
69
+ This application implements cookie-less session management specifically for Hugging Face Spaces:
70
+
71
+ - User IDs are stored in the browser's localStorage instead of cookies
72
+ - User identification is transmitted via HTTP headers and URL parameters
73
+ - The application maintains backward compatibility with cookie-based implementations
74
+
75
+ This approach ensures the application works smoothly in HuggingFace Spaces which has limitations with cookie handling.
76
+
77
  ## Troubleshooting
78
 
79
  If you encounter issues:
api/main.py CHANGED
@@ -139,28 +139,22 @@ class GenerateQuizResponse(BaseModel):
139
 
140
  # Helper function to get or create a user ID
141
  def get_or_create_user_id(request: Request, response: Response) -> str:
142
- # Try to get user ID from cookie
143
- user_id = request.cookies.get("user_id")
 
 
 
 
144
 
145
  # If no user ID exists, create a new one
146
  if not user_id:
147
  user_id = str(uuid.uuid4())
148
- # Set cookie with long expiration (1 year)
149
- expires = int(time.time()) + 31536000 # 1 year in seconds
150
- response.set_cookie(
151
- key="user_id",
152
- value=user_id,
153
- expires=expires,
154
- path="/",
155
- httponly=True,
156
- samesite="lax"
157
- )
158
-
159
  # Initialize with default prompts
160
- user_prompts[user_id] = {
161
- "system_template": DEFAULT_SYSTEM_TEMPLATE,
162
- "user_template": DEFAULT_USER_TEMPLATE
163
- }
 
164
 
165
  return user_id
166
 
@@ -318,7 +312,7 @@ async def query(request: QueryRequest):
318
  )
319
 
320
  # Run the query
321
- result = await retrieval_pipeline.arun_pipeline(request.query)
322
 
323
  # Process the result and return the response
324
  response_text = ""
@@ -348,11 +342,11 @@ async def stream_query(request: QueryRequest):
348
  )
349
 
350
  # Run the query
351
- result = await retrieval_pipeline.arun_pipeline(request.query)
352
 
353
  async def generate():
354
  async for chunk in result["response"]:
355
- yield f"data: {json.dumps({'chunk': chunk, 'session_id': session_id})}\n\n"
356
 
357
  return StreamingResponse(
358
  generate(),
@@ -391,14 +385,14 @@ async def stream_query_get(
391
  )
392
 
393
  # Run the query
394
- result = await retrieval_pipeline.arun_pipeline(query)
395
 
396
  async def generate():
397
  async for chunk in result["response"]:
398
- yield f"data: {json.dumps({'chunk': chunk, 'session_id': session_id})}\n\n"
399
 
400
  # Send an event to signal completion
401
- yield f"event: complete\ndata: {json.dumps({'session_id': session_id})}\n\n"
402
 
403
  return StreamingResponse(
404
  generate(),
@@ -799,7 +793,7 @@ class RetrievalAugmentedQAPipeline:
799
  self.system_role_prompt = SystemRolePrompt(system_template)
800
  self.user_role_prompt = UserRolePrompt(user_template)
801
 
802
- async def arun_pipeline(self, user_query: str):
803
  context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
804
 
805
  context_prompt = ""
 
139
 
140
  # Helper function to get or create a user ID
141
  def get_or_create_user_id(request: Request, response: Response) -> str:
142
+ # Try to get user ID from header first
143
+ user_id = request.headers.get("X-User-ID")
144
+
145
+ # Then try to get from query parameter
146
+ if not user_id:
147
+ user_id = request.query_params.get("user_id")
148
 
149
  # If no user ID exists, create a new one
150
  if not user_id:
151
  user_id = str(uuid.uuid4())
 
 
 
 
 
 
 
 
 
 
 
152
  # Initialize with default prompts
153
+ if user_id not in user_prompts:
154
+ user_prompts[user_id] = {
155
+ "system_template": DEFAULT_SYSTEM_TEMPLATE,
156
+ "user_template": DEFAULT_USER_TEMPLATE
157
+ }
158
 
159
  return user_id
160
 
 
312
  )
313
 
314
  # Run the query
315
+ result = await retrieval_pipeline.arun_pipeline(request.query, user_id)
316
 
317
  # Process the result and return the response
318
  response_text = ""
 
342
  )
343
 
344
  # Run the query
345
+ result = await retrieval_pipeline.arun_pipeline(request.query, user_id)
346
 
347
  async def generate():
348
  async for chunk in result["response"]:
349
+ yield f"data: {json.dumps({'chunk': chunk, 'session_id': session_id, 'user_id': user_id})}\n\n"
350
 
351
  return StreamingResponse(
352
  generate(),
 
385
  )
386
 
387
  # Run the query
388
+ result = await retrieval_pipeline.arun_pipeline(query, user_id)
389
 
390
  async def generate():
391
  async for chunk in result["response"]:
392
+ yield f"data: {json.dumps({'chunk': chunk, 'session_id': session_id, 'user_id': user_id})}\n\n"
393
 
394
  # Send an event to signal completion
395
+ yield f"event: complete\ndata: {json.dumps({'session_id': session_id, 'user_id': user_id})}\n\n"
396
 
397
  return StreamingResponse(
398
  generate(),
 
793
  self.system_role_prompt = SystemRolePrompt(system_template)
794
  self.user_role_prompt = UserRolePrompt(user_template)
795
 
796
+ async def arun_pipeline(self, user_query: str, user_id: str):
797
  context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
798
 
799
  context_prompt = ""
api/services/pipeline.py CHANGED
@@ -21,7 +21,17 @@ class RetrievalAugmentedQAPipeline:
21
  self.system_role_prompt = SystemRolePrompt(system_template)
22
  self.user_role_prompt = UserRolePrompt(user_template)
23
 
24
- async def arun_pipeline(self, user_query: str):
 
 
 
 
 
 
 
 
 
 
25
  context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
26
 
27
  context_prompt = ""
@@ -37,4 +47,10 @@ class RetrievalAugmentedQAPipeline:
37
  async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
38
  yield chunk
39
 
40
- return {"response": generate_response(), "context": context_list}
 
 
 
 
 
 
 
21
  self.system_role_prompt = SystemRolePrompt(system_template)
22
  self.user_role_prompt = UserRolePrompt(user_template)
23
 
24
+ async def arun_pipeline(self, user_query: str, user_id: str = None):
25
+ """
26
+ Run the RAG pipeline to answer a user query
27
+
28
+ Args:
29
+ user_query: The user's question
30
+ user_id: Optional user ID for tracking in HuggingFace deployment
31
+
32
+ Returns:
33
+ Dictionary containing response generator and context
34
+ """
35
  context_list = self.vector_db_retriever.search_by_text(user_query, k=4)
36
 
37
  context_prompt = ""
 
47
  async for chunk in self.llm.astream([formatted_system_prompt, formatted_user_prompt]):
48
  yield chunk
49
 
50
+ result = {"response": generate_response(), "context": context_list}
51
+
52
+ # Include user_id in result if provided (for HuggingFace deployment)
53
+ if user_id:
54
+ result["user_id"] = user_id
55
+
56
+ return result
api/utils/user.py CHANGED
@@ -5,7 +5,7 @@ from .prompts import user_prompts, DEFAULT_SYSTEM_TEMPLATE, DEFAULT_USER_TEMPLAT
5
 
6
  def get_or_create_user_id(request: Request, response: Response) -> str:
7
  """
8
- Get or create a user ID
9
 
10
  Args:
11
  request: FastAPI request object
@@ -14,27 +14,21 @@ def get_or_create_user_id(request: Request, response: Response) -> str:
14
  Returns:
15
  User ID (either existing or newly created)
16
  """
17
- # Try to get user ID from cookie
18
- user_id = request.cookies.get("user_id")
 
 
 
 
19
 
20
  # If no user ID exists, create a new one
21
  if not user_id:
22
  user_id = str(uuid.uuid4())
23
- # Set cookie with long expiration (1 year)
24
- expires = int(time.time()) + 31536000 # 1 year in seconds
25
- response.set_cookie(
26
- key="user_id",
27
- value=user_id,
28
- expires=expires,
29
- path="/",
30
- httponly=True,
31
- samesite="lax"
32
- )
33
-
34
  # Initialize with default prompts
35
- user_prompts[user_id] = {
36
- "system_template": DEFAULT_SYSTEM_TEMPLATE,
37
- "user_template": DEFAULT_USER_TEMPLATE
38
- }
 
39
 
40
  return user_id
 
5
 
6
  def get_or_create_user_id(request: Request, response: Response) -> str:
7
  """
8
+ Get or create a user ID without using cookies to support HuggingFace deployments
9
 
10
  Args:
11
  request: FastAPI request object
 
14
  Returns:
15
  User ID (either existing or newly created)
16
  """
17
+ # Try to get user ID from header first
18
+ user_id = request.headers.get("X-User-ID")
19
+
20
+ # Then try to get from query parameter
21
+ if not user_id:
22
+ user_id = request.query_params.get("user_id")
23
 
24
  # If no user ID exists, create a new one
25
  if not user_id:
26
  user_id = str(uuid.uuid4())
 
 
 
 
 
 
 
 
 
 
 
27
  # Initialize with default prompts
28
+ if user_id not in user_prompts:
29
+ user_prompts[user_id] = {
30
+ "system_template": DEFAULT_SYSTEM_TEMPLATE,
31
+ "user_template": DEFAULT_USER_TEMPLATE
32
+ }
33
 
34
  return user_id
app/frontend/src/App.js CHANGED
@@ -8,9 +8,11 @@ import { ThemeProvider } from './components/ui/theme-provider';
8
  import { ThemeToggle } from './components/ui/theme-toggle';
9
  import { SettingsDialog } from './components/ui/settings-dialog';
10
  import { getVersionString, fetchApiVersion } from './utils/version';
11
- import { identifyUser } from './utils/user';
12
  import PromptEditor from './components/ui/PromptEditor';
13
- import CookieConsent from './components/ui/CookieConsent';
 
 
14
 
15
  function App() {
16
  const [sessionId, setSessionId] = useState('');
@@ -219,8 +221,6 @@ function App() {
219
  </div>
220
  </div>
221
  </footer>
222
-
223
- <CookieConsent />
224
  </div>
225
  </ThemeProvider>
226
  );
 
8
  import { ThemeToggle } from './components/ui/theme-toggle';
9
  import { SettingsDialog } from './components/ui/settings-dialog';
10
  import { getVersionString, fetchApiVersion } from './utils/version';
11
+ import { identifyUser, setupUserIdInterceptor } from './utils/user';
12
  import PromptEditor from './components/ui/PromptEditor';
13
+
14
+ // Setup the axios interceptor to include user ID in all requests
15
+ setupUserIdInterceptor();
16
 
17
  function App() {
18
  const [sessionId, setSessionId] = useState('');
 
221
  </div>
222
  </div>
223
  </footer>
 
 
224
  </div>
225
  </ThemeProvider>
226
  );
app/frontend/src/utils/user.js CHANGED
@@ -11,11 +11,19 @@ let cachedUserId = null;
11
  */
12
  export const identifyUser = async () => {
13
  try {
14
- // Return cached user ID if available
 
 
 
15
  if (cachedUserId) {
16
  return cachedUserId;
17
  }
18
 
 
 
 
 
 
19
  // Get user ID from the server
20
  const response = await axios.get('/identify');
21
  const userId = response.data.user_id;
@@ -23,6 +31,9 @@ export const identifyUser = async () => {
23
  // Cache the user ID for future use
24
  cachedUserId = userId;
25
 
 
 
 
26
  return userId;
27
  } catch (error) {
28
  console.error('Error identifying user:', error);
@@ -36,10 +47,39 @@ export const identifyUser = async () => {
36
  * @returns {string|null} The cached user ID
37
  */
38
  export const getCachedUserId = () => {
 
 
 
 
39
  return cachedUserId;
40
  };
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  export default {
43
  identifyUser,
44
- getCachedUserId
 
45
  };
 
11
  */
12
  export const identifyUser = async () => {
13
  try {
14
+ // First check localStorage for existing user ID
15
+ const storedUserId = localStorage.getItem('user_id');
16
+
17
+ // Return cached or stored user ID if available
18
  if (cachedUserId) {
19
  return cachedUserId;
20
  }
21
 
22
+ if (storedUserId) {
23
+ cachedUserId = storedUserId;
24
+ return storedUserId;
25
+ }
26
+
27
  // Get user ID from the server
28
  const response = await axios.get('/identify');
29
  const userId = response.data.user_id;
 
31
  // Cache the user ID for future use
32
  cachedUserId = userId;
33
 
34
+ // Store in localStorage for persistence across sessions
35
+ localStorage.setItem('user_id', userId);
36
+
37
  return userId;
38
  } catch (error) {
39
  console.error('Error identifying user:', error);
 
47
  * @returns {string|null} The cached user ID
48
  */
49
  export const getCachedUserId = () => {
50
+ if (!cachedUserId) {
51
+ // Try to get from localStorage if not in memory
52
+ cachedUserId = localStorage.getItem('user_id');
53
+ }
54
  return cachedUserId;
55
  };
56
 
57
+ /**
58
+ * Configure axios to automatically include user ID in requests
59
+ */
60
+ export const setupUserIdInterceptor = () => {
61
+ axios.interceptors.request.use(function (config) {
62
+ const userId = getCachedUserId();
63
+ if (userId) {
64
+ // Add user ID as a header
65
+ config.headers['X-User-ID'] = userId;
66
+
67
+ // Add user ID as a URL parameter for GET requests
68
+ if (config.method === 'get') {
69
+ config.params = config.params || {};
70
+ if (!config.params.user_id) {
71
+ config.params.user_id = userId;
72
+ }
73
+ }
74
+ }
75
+ return config;
76
+ }, function (error) {
77
+ return Promise.reject(error);
78
+ });
79
+ };
80
+
81
  export default {
82
  identifyUser,
83
+ getCachedUserId,
84
+ setupUserIdInterceptor
85
  };