bibibi12345 commited on
Commit
24cb9ba
·
1 Parent(s): 848d580

added extra field to make message look more like website trace

Browse files
Files changed (2) hide show
  1. main.py +113 -22
  2. models.py +20 -2
main.py CHANGED
@@ -2,16 +2,19 @@ import os
2
  import uuid
3
  import json
4
  import time
 
5
  import httpx
6
  from fastapi import FastAPI, Request, HTTPException, Depends, status
7
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
8
  from fastapi.responses import StreamingResponse
9
  from dotenv import load_dotenv
10
  import secrets # Added for secure comparison
 
 
11
  from models import (
12
  ChatMessage, ChatCompletionRequest, NotionTranscriptConfigValue,
13
- NotionTranscriptItem, NotionDebugOverrides, NotionRequestBody,
14
- ChoiceDelta, Choice, ChatCompletionChunk, Model, ModelList
15
  )
16
 
17
  # Load environment variables from .env file
@@ -53,43 +56,131 @@ app = FastAPI()
53
  # --- Helper Functions ---
54
 
55
  def build_notion_request(request_data: ChatCompletionRequest) -> NotionRequestBody:
56
- """Transforms OpenAI-style messages to Notion transcript format."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  transcript = [
58
  NotionTranscriptItem(
59
  type="config",
60
  value=NotionTranscriptConfigValue(model=request_data.notion_model)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  )
62
  ]
 
63
  for message in request_data.messages:
64
- # Map 'assistant' role to 'markdown-chat', all others to 'user'
65
  if message.role == "assistant":
66
- # Notion uses "markdown-chat" for assistant replies in the transcript history
67
- transcript.append(NotionTranscriptItem(type="markdown-chat", value=message.content))
68
- else: # Handles 'user', 'system', etc.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  content = message.content
70
  if isinstance(content, str):
71
- # Handle string content: Append one item, using default [[""]] for empty strings
72
  notion_value = [[content]] if content else [[""]]
73
- transcript.append(NotionTranscriptItem(type="user", value=notion_value))
 
 
 
 
 
74
  elif isinstance(content, list):
75
- # Handle list content: Append a SEPARATE item for each valid text part
76
  found_text_part = False
77
  for part in content:
78
- # Check if part is a dict with type="text" and non-empty text
79
- if isinstance(part, dict) and part.get("type") == "text":
80
  text_content = part.get("text")
81
  if isinstance(text_content, str) and text_content:
82
- # Create and append a SEPARATE item for this text part
83
- transcript.append(NotionTranscriptItem(type="user", value=[[text_content]]))
 
 
 
 
 
84
  found_text_part = True
85
- # If the list was empty or had no valid text parts, append a default empty item to maintain behavior
86
  if not found_text_part:
87
- print(f'Error: no valid input found: {message}')
88
- transcript.append(NotionTranscriptItem(type="user", value=[[""]]))
 
 
 
 
 
 
89
  else:
90
- # Handle unexpected content types (e.g., None, int) by appending a default empty item
91
- transcript.append(NotionTranscriptItem(type="user", value=[[""]]))
92
- print(f'Error: no valid input found: {message}')
 
 
 
 
 
 
 
93
 
94
  # Use globally configured spaceId, set createThread=True
95
  return NotionRequestBody(
@@ -112,10 +203,10 @@ async def stream_notion_response(notion_request_body: NotionRequestBody):
112
  """Streams the request to Notion and yields OpenAI-compatible SSE chunks."""
113
  headers = {
114
  'accept': 'application/x-ndjson',
115
- 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6,ja;q=0.5',
116
  'content-type': 'application/json',
117
  'notion-audit-log-platform': 'web',
118
- 'notion-client-version': '23.13.0.3604', # Consider making this configurable
119
  'origin': 'https://www.notion.so',
120
  'priority': 'u=1, i',
121
  # Referer might be optional or need adjustment. Removing threadId part.
 
2
  import uuid
3
  import json
4
  import time
5
+ import random
6
  import httpx
7
  from fastapi import FastAPI, Request, HTTPException, Depends, status
8
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
9
  from fastapi.responses import StreamingResponse
10
  from dotenv import load_dotenv
11
  import secrets # Added for secure comparison
12
+ from datetime import datetime, timedelta, timezone # Explicit datetime imports
13
+ from zoneinfo import ZoneInfo # For timezone handling
14
  from models import (
15
  ChatMessage, ChatCompletionRequest, NotionTranscriptConfigValue,
16
+ NotionTranscriptContextValue, NotionTranscriptItem, NotionDebugOverrides,
17
+ NotionRequestBody, ChoiceDelta, Choice, ChatCompletionChunk, Model, ModelList
18
  )
19
 
20
  # Load environment variables from .env file
 
56
  # --- Helper Functions ---
57
 
58
  def build_notion_request(request_data: ChatCompletionRequest) -> NotionRequestBody:
59
+ """Transforms OpenAI-style messages to Notion transcript format, adding userId and createdAt."""
60
+
61
+ # --- Timestamp and User ID Logic ---
62
+ user_id = os.getenv("NOTION_ACTIVE_USER_HEADER")
63
+ user_messages = [msg for msg in request_data.messages if msg.role == "user"]
64
+ num_user_messages = len(user_messages)
65
+ user_message_timestamps = {} # Store timestamps keyed by message id
66
+
67
+ if num_user_messages > 0:
68
+ # Get current time specifically in Pacific Time (America/Los_Angeles)
69
+ pacific_tz = ZoneInfo("America/Los_Angeles")
70
+ now_pacific = datetime.now(timezone.utc).astimezone(pacific_tz)
71
+
72
+ # Assign timestamp to the last user message
73
+ last_user_msg_id = user_messages[-1].id
74
+ user_message_timestamps[last_user_msg_id] = now_pacific
75
+
76
+ # Calculate timestamps for previous user messages (10 mins earlier each)
77
+ current_timestamp = now_pacific
78
+ for i in range(num_user_messages - 2, -1, -1): # Iterate backwards from second-to-last
79
+ current_timestamp -= timedelta(minutes=random.randint(3, 20)) # Use random interval (3-20 mins)
80
+ user_message_timestamps[user_messages[i].id] = current_timestamp
81
+
82
+ # --- Build Transcript ---
83
+ # Get current time in Pacific timezone for context
84
+ pacific_tz = ZoneInfo("America/Los_Angeles")
85
+ now_pacific = datetime.now(timezone.utc).astimezone(pacific_tz)
86
+ # Format timestamp exactly as YYYY-MM-DDTHH:MM:SS.fff-HH:MM
87
+ dt_str = now_pacific.strftime("%Y-%m-%dT%H:%M:%S")
88
+ ms = f"{now_pacific.microsecond // 1000:03d}" # Ensure 3 digits for milliseconds
89
+ tz_str = now_pacific.strftime("%z") # Gets +HHMM or -HHMM
90
+ formatted_tz = f"{tz_str[:-2]}:{tz_str[-2:]}" # Insert colon
91
+ current_datetime_iso = f"{dt_str}.{ms}{formatted_tz}"
92
+
93
+ # Generate random text for userName and spaceName
94
+ random_words = ["Project", "Workspace", "Team", "Studio", "Lab", "Hub", "Zone", "Space"]
95
+ user_name = f"User{random.randint(100, 999)}"
96
+ space_name = f"{random.choice(random_words)} {random.randint(1, 99)}"
97
+
98
  transcript = [
99
  NotionTranscriptItem(
100
  type="config",
101
  value=NotionTranscriptConfigValue(model=request_data.notion_model)
102
+ ),
103
+ NotionTranscriptItem(
104
+ type="context",
105
+ value=NotionTranscriptContextValue(
106
+ userId=user_id or "", # Use the user_id from env or empty string
107
+ spaceId=NOTION_SPACE_ID,
108
+ surface="home_module",
109
+ timezone="America/Los_Angeles",
110
+ userName=user_name,
111
+ spaceName=space_name,
112
+ spaceViewId=str(uuid.uuid4()), # Random UUID for spaceViewId
113
+ currentDatetime=current_datetime_iso
114
+ )
115
+ ),
116
+ NotionTranscriptItem(
117
+ type="agent-integration"
118
+ # No value field needed for agent-integration
119
  )
120
  ]
121
+
122
  for message in request_data.messages:
 
123
  if message.role == "assistant":
124
+ # Assistant messages get a traceId, but not userId or createdAt
125
+ transcript.append(NotionTranscriptItem(
126
+ type="markdown-chat",
127
+ value=message.content,
128
+ traceId=str(uuid.uuid4()) # Generate unique traceId for assistant message
129
+ ))
130
+ elif message.role == "user":
131
+ created_at_dt = user_message_timestamps.get(message.id)
132
+ created_at_iso = None
133
+ if created_at_dt:
134
+ # Format timestamp exactly as YYYY-MM-DDTHH:MM:SS.fff-HH:MM
135
+ dt_str = created_at_dt.strftime("%Y-%m-%dT%H:%M:%S")
136
+ ms = f"{created_at_dt.microsecond // 1000:03d}" # Ensure 3 digits for milliseconds
137
+ tz_str = created_at_dt.strftime("%z") # Gets +HHMM or -HHMM
138
+ formatted_tz = f"{tz_str[:-2]}:{tz_str[-2:]}" # Insert colon
139
+ created_at_iso = f"{dt_str}.{ms}{formatted_tz}"
140
+
141
  content = message.content
142
  if isinstance(content, str):
 
143
  notion_value = [[content]] if content else [[""]]
144
+ transcript.append(NotionTranscriptItem(
145
+ type="user",
146
+ value=notion_value,
147
+ userId=user_id,
148
+ createdAt=created_at_iso
149
+ ))
150
  elif isinstance(content, list):
 
151
  found_text_part = False
152
  for part in content:
153
+ if isinstance(part, dict) and part.get("type") == "text":
 
154
  text_content = part.get("text")
155
  if isinstance(text_content, str) and text_content:
156
+ # Append separate item for each text part, with same userId/timestamp
157
+ transcript.append(NotionTranscriptItem(
158
+ type="user",
159
+ value=[[text_content]],
160
+ userId=user_id,
161
+ createdAt=created_at_iso
162
+ ))
163
  found_text_part = True
 
164
  if not found_text_part:
165
+ # Append default empty item if no valid text parts found
166
+ transcript.append(NotionTranscriptItem(
167
+ type="user",
168
+ value=[[""]],
169
+ userId=user_id,
170
+ createdAt=created_at_iso # Still add userId/timestamp
171
+ ))
172
+ print(f'Warning: No valid text parts found in user message list content: {message}')
173
  else:
174
+ # Handle unexpected content types with default empty item
175
+ transcript.append(NotionTranscriptItem(
176
+ type="user",
177
+ value=[[""]],
178
+ userId=user_id,
179
+ createdAt=created_at_iso # Still add userId/timestamp
180
+ ))
181
+ print(f'Warning: Unexpected content type in user message: {message}')
182
+ # System messages are currently ignored in the Notion transcript based on original logic
183
+ # else: # Handle 'system' role if needed in the future
184
 
185
  # Use globally configured spaceId, set createThread=True
186
  return NotionRequestBody(
 
203
  """Streams the request to Notion and yields OpenAI-compatible SSE chunks."""
204
  headers = {
205
  'accept': 'application/x-ndjson',
206
+ 'accept-language': 'en-US,en;q=0.9',
207
  'content-type': 'application/json',
208
  'notion-audit-log-platform': 'web',
209
+ 'notion-client-version': '23.13.0.3668', # Consider making this configurable
210
  'origin': 'https://www.notion.so',
211
  'priority': 'u=1, i',
212
  # Referer might be optional or need adjustment. Removing threadId part.
models.py CHANGED
@@ -7,8 +7,12 @@ from typing import List, Optional, Dict, Any, Literal, Union
7
 
8
  # Input Models (OpenAI-like)
9
  class ChatMessage(BaseModel):
 
10
  role: Literal["system", "user", "assistant"]
11
  content: Union[str, List[Dict[str, Any]]]
 
 
 
12
 
13
  class ChatCompletionRequest(BaseModel):
14
  messages: List[ChatMessage]
@@ -26,9 +30,23 @@ class NotionTranscriptConfigValue(BaseModel):
26
  type: str = "markdown-chat"
27
  model: str # e.g., "anthropic-opus-4"
28
 
 
 
 
 
 
 
 
 
 
 
29
  class NotionTranscriptItem(BaseModel):
30
- type: Literal["config", "user", "markdown-chat"]
31
- value: Union[List[List[str]], str, NotionTranscriptConfigValue]
 
 
 
 
32
 
33
  class NotionDebugOverrides(BaseModel):
34
  cachedInferences: Dict = Field(default_factory=dict)
 
7
 
8
  # Input Models (OpenAI-like)
9
  class ChatMessage(BaseModel):
10
+ id: uuid.UUID = Field(default_factory=uuid.uuid4)
11
  role: Literal["system", "user", "assistant"]
12
  content: Union[str, List[Dict[str, Any]]]
13
+ userId: Optional[str] = None # Added for user messages
14
+ createdAt: Optional[str] = None # Added for timestamping
15
+ traceId: Optional[str] = None # Added for assistant messages
16
 
17
  class ChatCompletionRequest(BaseModel):
18
  messages: List[ChatMessage]
 
30
  type: str = "markdown-chat"
31
  model: str # e.g., "anthropic-opus-4"
32
 
33
+ class NotionTranscriptContextValue(BaseModel):
34
+ userId: str
35
+ spaceId: str
36
+ surface: str = "home_module"
37
+ timezone: str = "America/Los_Angeles"
38
+ userName: str
39
+ spaceName: str
40
+ spaceViewId: str
41
+ currentDatetime: str
42
+
43
  class NotionTranscriptItem(BaseModel):
44
+ id: uuid.UUID = Field(default_factory=uuid.uuid4)
45
+ type: Literal["config", "user", "markdown-chat", "agent-integration", "context"]
46
+ value: Optional[Union[List[List[str]], str, NotionTranscriptConfigValue, NotionTranscriptContextValue]] = None
47
+ userId: Optional[str] = None # Added for user messages in Notion transcript
48
+ createdAt: Optional[str] = None # Added for timestamping in Notion transcript
49
+ traceId: Optional[str] = None # Added for assistant messages in Notion transcript
50
 
51
  class NotionDebugOverrides(BaseModel):
52
  cachedInferences: Dict = Field(default_factory=dict)