jinruiyang Claude Opus 4.5 commited on
Commit
8f481e9
·
1 Parent(s): d1394bb

Add Google Sheets storage for persistent feedback

Browse files

- Add sheets_storage.py module for Google Sheets integration
- Modify demo.py to save ratings and notes to Google Sheets
- Add gspread and google-auth dependencies

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (3) hide show
  1. ir/demo.py +53 -4
  2. ir/sheets_storage.py +196 -0
  3. requirements.txt +2 -0
ir/demo.py CHANGED
@@ -19,6 +19,18 @@ import base64
19
 
20
  import gradio as gr
21
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # ============================================================
23
  # UAE Theme Assets
24
  # ============================================================
@@ -854,7 +866,28 @@ def save_entity_rating(
854
  "page": current_page,
855
  }
856
 
857
- # Save to file - use /data for HF Spaces persistence
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
858
  if Path("/data").exists():
859
  feedback_file = Path("/data/demo_feedback.json")
860
  else:
@@ -872,8 +905,8 @@ def save_entity_rating(
872
  with open(feedback_file, "w", encoding="utf-8") as f:
873
  json.dump(all_feedback, f, ensure_ascii=False, indent=2)
874
 
875
- emoji = "👍" if "relevant" in rating or "helpful" in rating else "👎"
876
- return f"{emoji} Rated #{rank} {entity_name[:20]}... as {rating}"
877
 
878
  except Exception as e:
879
  return f"❌ Save failed: {str(e)}"
@@ -904,6 +937,21 @@ def save_notes_feedback(
904
  "notes": notes,
905
  }
906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
907
  if Path("/data").exists():
908
  feedback_file = Path("/data/demo_feedback.json")
909
  else:
@@ -921,7 +969,8 @@ def save_notes_feedback(
921
  with open(feedback_file, "w", encoding="utf-8") as f:
922
  json.dump(all_feedback, f, ensure_ascii=False, indent=2)
923
 
924
- return f" Notes saved! Total: {len(all_feedback)} entries"
 
925
 
926
  except Exception as e:
927
  return f"❌ Save failed: {str(e)}"
 
19
 
20
  import gradio as gr
21
 
22
+ # Google Sheets storage for persistent feedback
23
+ try:
24
+ from . import sheets_storage
25
+ SHEETS_ENABLED = sheets_storage.is_sheets_enabled()
26
+ except ImportError:
27
+ try:
28
+ import sheets_storage
29
+ SHEETS_ENABLED = sheets_storage.is_sheets_enabled()
30
+ except ImportError:
31
+ SHEETS_ENABLED = False
32
+ sheets_storage = None
33
+
34
  # ============================================================
35
  # UAE Theme Assets
36
  # ============================================================
 
866
  "page": current_page,
867
  }
868
 
869
+ emoji = "👍" if "relevant" in rating or "helpful" in rating else "👎"
870
+
871
+ # Try Google Sheets first (persistent)
872
+ if SHEETS_ENABLED and sheets_storage:
873
+ try:
874
+ success = sheets_storage.save_rating_to_sheets(
875
+ query=query,
876
+ category=category or "Not selected",
877
+ entity_id=entity_id,
878
+ entity_name=entity_name,
879
+ rank=rank,
880
+ score=score,
881
+ rating=rating,
882
+ page=current_page,
883
+ client_ip=client_ip
884
+ )
885
+ if success:
886
+ return f"{emoji} Rated #{rank} {entity_name[:20]}... as {rating} (saved to cloud)"
887
+ except Exception as e:
888
+ print(f"Google Sheets save failed: {e}")
889
+
890
+ # Fallback to local file
891
  if Path("/data").exists():
892
  feedback_file = Path("/data/demo_feedback.json")
893
  else:
 
905
  with open(feedback_file, "w", encoding="utf-8") as f:
906
  json.dump(all_feedback, f, ensure_ascii=False, indent=2)
907
 
908
+ storage_note = " (local only)" if not SHEETS_ENABLED else ""
909
+ return f"{emoji} Rated #{rank} {entity_name[:20]}... as {rating}{storage_note}"
910
 
911
  except Exception as e:
912
  return f"❌ Save failed: {str(e)}"
 
937
  "notes": notes,
938
  }
939
 
940
+ # Try Google Sheets first (persistent)
941
+ if SHEETS_ENABLED and sheets_storage:
942
+ try:
943
+ success = sheets_storage.save_notes_to_sheets(
944
+ query=query,
945
+ category=category or "Not selected",
946
+ notes=notes,
947
+ client_ip=client_ip
948
+ )
949
+ if success:
950
+ return f"✅ Notes saved to cloud!"
951
+ except Exception as e:
952
+ print(f"Google Sheets save failed: {e}")
953
+
954
+ # Fallback to local file
955
  if Path("/data").exists():
956
  feedback_file = Path("/data/demo_feedback.json")
957
  else:
 
969
  with open(feedback_file, "w", encoding="utf-8") as f:
970
  json.dump(all_feedback, f, ensure_ascii=False, indent=2)
971
 
972
+ storage_note = " (local only)" if not SHEETS_ENABLED else ""
973
+ return f"✅ Notes saved!{storage_note} Total: {len(all_feedback)} entries"
974
 
975
  except Exception as e:
976
  return f"❌ Save failed: {str(e)}"
ir/sheets_storage.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Google Sheets storage for UAE KB feedback data.
3
+
4
+ This module provides persistent storage for demo feedback using Google Sheets,
5
+ which allows easy viewing and analysis of user feedback data.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ from datetime import datetime
11
+ from typing import Optional, Dict, Any, List
12
+ import logging
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Google Sheets configuration
17
+ SHEET_ID = "1wwnpQRx32nbmApNK1LyvzUDIVx-tW4mpwgRPLvjm4gw"
18
+ RATINGS_WORKSHEET = "ratings"
19
+ NOTES_WORKSHEET = "notes"
20
+
21
+ # Lazy-loaded client
22
+ _sheets_client = None
23
+ _spreadsheet = None
24
+
25
+
26
+ def _get_credentials():
27
+ """Get Google credentials from environment variable."""
28
+ creds_json = os.environ.get("GOOGLE_SHEETS_CREDENTIALS")
29
+ if not creds_json:
30
+ logger.warning("GOOGLE_SHEETS_CREDENTIALS not set")
31
+ return None
32
+
33
+ try:
34
+ from google.oauth2.service_account import Credentials
35
+ creds_dict = json.loads(creds_json)
36
+ scopes = [
37
+ "https://www.googleapis.com/auth/spreadsheets",
38
+ "https://www.googleapis.com/auth/drive"
39
+ ]
40
+ return Credentials.from_service_account_info(creds_dict, scopes=scopes)
41
+ except Exception as e:
42
+ logger.error(f"Failed to load credentials: {e}")
43
+ return None
44
+
45
+
46
+ def _get_spreadsheet():
47
+ """Get or create the Google Sheets client."""
48
+ global _spreadsheet
49
+
50
+ if _spreadsheet is not None:
51
+ return _spreadsheet
52
+
53
+ try:
54
+ import gspread
55
+ creds = _get_credentials()
56
+ if creds is None:
57
+ return None
58
+
59
+ client = gspread.authorize(creds)
60
+ _spreadsheet = client.open_by_key(SHEET_ID)
61
+ logger.info(f"Connected to Google Sheet: {SHEET_ID}")
62
+ return _spreadsheet
63
+ except Exception as e:
64
+ logger.error(f"Failed to connect to Google Sheets: {e}")
65
+ return None
66
+
67
+
68
+ def _get_or_create_worksheet(name: str, headers: List[str]):
69
+ """Get or create a worksheet with headers."""
70
+ spreadsheet = _get_spreadsheet()
71
+ if spreadsheet is None:
72
+ return None
73
+
74
+ try:
75
+ # Try to get existing worksheet
76
+ try:
77
+ worksheet = spreadsheet.worksheet(name)
78
+ except gspread.WorksheetNotFound:
79
+ # Create new worksheet
80
+ worksheet = spreadsheet.add_worksheet(title=name, rows=1000, cols=len(headers))
81
+ worksheet.append_row(headers)
82
+ logger.info(f"Created worksheet: {name}")
83
+
84
+ return worksheet
85
+ except Exception as e:
86
+ logger.error(f"Failed to get/create worksheet {name}: {e}")
87
+ return None
88
+
89
+
90
+ def save_rating_to_sheets(
91
+ query: str,
92
+ category: str,
93
+ entity_id: str,
94
+ entity_name: str,
95
+ rank: int,
96
+ score: float,
97
+ rating: str,
98
+ page: int,
99
+ client_ip: str = "unknown"
100
+ ) -> bool:
101
+ """
102
+ Save an entity rating to Google Sheets.
103
+
104
+ Returns True if successful, False otherwise.
105
+ """
106
+ headers = [
107
+ "timestamp", "client_ip", "query", "category",
108
+ "entity_id", "entity_name", "rank", "score", "rating", "page"
109
+ ]
110
+
111
+ worksheet = _get_or_create_worksheet(RATINGS_WORKSHEET, headers)
112
+ if worksheet is None:
113
+ return False
114
+
115
+ try:
116
+ row = [
117
+ datetime.now().isoformat(),
118
+ client_ip,
119
+ query,
120
+ category,
121
+ entity_id,
122
+ entity_name,
123
+ rank,
124
+ round(score, 4) if isinstance(score, float) else score,
125
+ rating,
126
+ page
127
+ ]
128
+ worksheet.append_row(row)
129
+ logger.info(f"Saved rating: {entity_name} - {rating}")
130
+ return True
131
+ except Exception as e:
132
+ logger.error(f"Failed to save rating: {e}")
133
+ return False
134
+
135
+
136
+ def save_notes_to_sheets(
137
+ query: str,
138
+ category: str,
139
+ notes: str,
140
+ client_ip: str = "unknown"
141
+ ) -> bool:
142
+ """
143
+ Save notes/feedback to Google Sheets.
144
+
145
+ Returns True if successful, False otherwise.
146
+ """
147
+ headers = ["timestamp", "client_ip", "query", "category", "notes"]
148
+
149
+ worksheet = _get_or_create_worksheet(NOTES_WORKSHEET, headers)
150
+ if worksheet is None:
151
+ return False
152
+
153
+ try:
154
+ row = [
155
+ datetime.now().isoformat(),
156
+ client_ip,
157
+ query,
158
+ category,
159
+ notes
160
+ ]
161
+ worksheet.append_row(row)
162
+ logger.info(f"Saved notes for query: {query[:50]}...")
163
+ return True
164
+ except Exception as e:
165
+ logger.error(f"Failed to save notes: {e}")
166
+ return False
167
+
168
+
169
+ def is_sheets_enabled() -> bool:
170
+ """Check if Google Sheets storage is configured and working."""
171
+ return os.environ.get("GOOGLE_SHEETS_CREDENTIALS") is not None
172
+
173
+
174
+ def test_connection() -> Dict[str, Any]:
175
+ """Test the Google Sheets connection."""
176
+ result = {
177
+ "configured": is_sheets_enabled(),
178
+ "connected": False,
179
+ "sheet_id": SHEET_ID,
180
+ "error": None
181
+ }
182
+
183
+ if not result["configured"]:
184
+ result["error"] = "GOOGLE_SHEETS_CREDENTIALS not set"
185
+ return result
186
+
187
+ try:
188
+ spreadsheet = _get_spreadsheet()
189
+ if spreadsheet:
190
+ result["connected"] = True
191
+ result["title"] = spreadsheet.title
192
+ result["worksheets"] = [ws.title for ws in spreadsheet.worksheets()]
193
+ except Exception as e:
194
+ result["error"] = str(e)
195
+
196
+ return result
requirements.txt CHANGED
@@ -7,3 +7,5 @@ fastapi
7
  uvicorn[standard]
8
  python-multipart
9
  httpx
 
 
 
7
  uvicorn[standard]
8
  python-multipart
9
  httpx
10
+ gspread>=5.0.0
11
+ google-auth>=2.0.0