Skip to content

Commit 3e3736c

Browse files
committed
fix (onboarding): onboarding bugfixes, memory bugfixes (ongoing)
1 parent 3177bc4 commit 3e3736c

File tree

10 files changed

+236
-44
lines changed

10 files changed

+236
-44
lines changed

src/client/app/layout.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default function RootLayout({ children }) {
6464
/>
6565
<meta name="theme-color" content="#F1A21D" />
6666
</head>
67-
<body className="font-sans">
67+
<body className="font-sans" suppressHydrationWarning>
6868
<Auth0Provider>
6969
<PostHogProvider>
7070
<Toaster position="bottom-right" />

src/client/app/onboarding/page.js

Lines changed: 128 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
IconSparkles,
1212
IconHeart
1313
} from "@tabler/icons-react"
14-
import AnimatedBackground from "@components/onboarding/AnimatedBackground"
14+
import InteractiveNetworkBackground from "@components/ui/InteractiveNetworkBackground"
1515
import ProgressBar from "@components/onboarding/ProgressBar"
1616
import SparkleEffect from "@components/ui/SparkleEffect"
1717
import { GridBackground } from "@components/ui/GridBackground"
@@ -86,22 +86,68 @@ const questions = [
8686
required: true,
8787
options: [
8888
{ value: "", label: "Select your timezone..." },
89-
{ value: "America/New_York", label: "Eastern Time (US & Canada)" },
90-
{ value: "America/Chicago", label: "Central Time (US & Canada)" },
91-
{ value: "America/Denver", label: "Mountain Time (US & Canada)" },
89+
{ value: "UTC", label: "(GMT+00:00) Coordinated Universal Time" },
90+
{
91+
value: "America/New_York",
92+
label: "(GMT-04:00) Eastern Time (US & Canada)"
93+
},
94+
{
95+
value: "America/Chicago",
96+
label: "(GMT-05:00) Central Time (US & Canada)"
97+
},
98+
{
99+
value: "America/Denver",
100+
label: "(GMT-06:00) Mountain Time (US & Canada)"
101+
},
92102
{
93103
value: "America/Los_Angeles",
94-
label: "Pacific Time (US & Canada)"
104+
label: "(GMT-07:00) Pacific Time (US & Canada)"
105+
},
106+
{ value: "America/Anchorage", label: "(GMT-08:00) Alaska" },
107+
{ value: "America/Phoenix", label: "(GMT-07:00) Arizona" },
108+
{ value: "Pacific/Honolulu", label: "(GMT-10:00) Hawaii" },
109+
{ value: "America/Sao_Paulo", label: "(GMT-03:00) Brasilia" },
110+
{
111+
value: "America/Buenos_Aires",
112+
label: "(GMT-03:00) Buenos Aires"
95113
},
96114
{
97-
value: "America/St_Johns",
98-
label: "Newfoundland (NDT)"
115+
value: "Europe/London",
116+
label: "(GMT+01:00) London, Dublin, Lisbon"
99117
},
100-
{ value: "Europe/London", label: "London, Dublin (GMT/BST)" },
101-
{ value: "Europe/Berlin", label: "Berlin, Paris (CET)" },
102-
{ value: "Asia/Kolkata", label: "India (IST)" },
103-
{ value: "Asia/Singapore", label: "Singapore (SGT)" },
104-
{ value: "UTC", label: "Coordinated Universal Time (UTC)" }
118+
{
119+
value: "Europe/Berlin",
120+
label: "(GMT+02:00) Amsterdam, Berlin, Paris, Rome"
121+
},
122+
{
123+
value: "Europe/Helsinki",
124+
label: "(GMT+03:00) Helsinki, Kyiv, Riga, Sofia"
125+
},
126+
{
127+
value: "Europe/Moscow",
128+
label: "(GMT+03:00) Moscow, St. Petersburg"
129+
},
130+
{ value: "Africa/Cairo", label: "(GMT+02:00) Cairo" },
131+
{ value: "Africa/Johannesburg", label: "(GMT+02:00) Johannesburg" },
132+
{ value: "Asia/Dubai", label: "(GMT+04:00) Abu Dhabi, Muscat" },
133+
{ value: "Asia/Kolkata", label: "(GMT+05:30) India Standard Time" },
134+
{
135+
value: "Asia/Shanghai",
136+
label: "(GMT+08:00) Beijing, Hong Kong, Shanghai"
137+
},
138+
{ value: "Asia/Singapore", label: "(GMT+08:00) Singapore" },
139+
{ value: "Asia/Tokyo", label: "(GMT+09:00) Tokyo, Seoul" },
140+
{
141+
value: "Australia/Sydney",
142+
label: "(GMT+10:00) Sydney, Melbourne"
143+
},
144+
{ value: "Australia/Brisbane", label: "(GMT+10:00) Brisbane" },
145+
{ value: "Australia/Adelaide", label: "(GMT+09:30) Adelaide" },
146+
{ value: "Australia/Perth", label: "(GMT+08:00) Perth" },
147+
{
148+
value: "Pacific/Auckland",
149+
label: "(GMT+12:00) Auckland, Wellington"
150+
}
105151
]
106152
},
107153
{
@@ -211,23 +257,73 @@ const OnboardingPage = () => {
211257
if (navigator.geolocation) {
212258
setLocationState({ loading: true, data: null, error: null })
213259
navigator.geolocation.getCurrentPosition(
214-
(position) => {
260+
async (position) => {
215261
const { latitude, longitude } = position.coords
216-
const locationData = { latitude, longitude }
217-
setLocationState({
218-
loading: false,
219-
data: locationData,
220-
error: null
221-
})
222-
handleAnswer("location", locationData)
262+
try {
263+
const response = await fetch(
264+
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}`
265+
)
266+
if (!response.ok) {
267+
throw new Error("Failed to fetch location details.")
268+
}
269+
const data = await response.json()
270+
const address = data.address
271+
// Construct a readable location string
272+
const locationString = [
273+
address.city || address.town || address.village,
274+
address.state,
275+
address.country
276+
]
277+
.filter(Boolean) // Remove any null/undefined parts
278+
.join(", ")
279+
280+
if (!locationString) {
281+
throw new Error(
282+
"Could not determine location name from coordinates."
283+
)
284+
}
285+
286+
// Update state with the text location
287+
setLocationState({
288+
loading: false,
289+
data: locationString, // Store the string
290+
error: null
291+
})
292+
handleAnswer("location", locationString) // Save the string
293+
} catch (error) {
294+
setLocationState({
295+
loading: false,
296+
data: null,
297+
error: error.message
298+
})
299+
toast.error(
300+
`Could not convert coordinates to location: ${error.message}`
301+
)
302+
}
223303
},
224304
(error) => {
305+
let userMessage =
306+
"An unknown error occurred while detecting your location."
307+
switch (error.code) {
308+
case error.PERMISSION_DENIED:
309+
userMessage =
310+
"Location permission denied. Please enable location access for this site in your browser settings and try again."
311+
break
312+
case error.POSITION_UNAVAILABLE:
313+
userMessage =
314+
"Location information is unavailable. This can happen if location services are turned off in your operating system (e.g., Windows or macOS). Please check your system settings and network connection."
315+
break
316+
case error.TIMEOUT:
317+
userMessage =
318+
"The request to get your location timed out. Please try again."
319+
break
320+
}
225321
setLocationState({
226322
loading: false,
227323
data: null,
228-
error: error.message
324+
error: userMessage
229325
})
230-
toast.error(`Could not get location: ${error.message}`)
326+
toast.error(userMessage)
231327
}
232328
)
233329
}
@@ -281,9 +377,7 @@ const OnboardingPage = () => {
281377

282378
// Format answer for display
283379
let displayAnswer = answer
284-
if (currentQuestion.type === "location") {
285-
displayAnswer = locationState.data ? "Shared my location" : answer
286-
} else if (Array.isArray(answer)) {
380+
if (Array.isArray(answer)) {
287381
displayAnswer = answer.join(", ")
288382
}
289383

@@ -347,7 +441,7 @@ const OnboardingPage = () => {
347441
if (!response.ok) throw new Error("Could not fetch user data.")
348442
const result = await response.json()
349443
if (result?.data?.onboardingComplete) {
350-
router.push("/chat")
444+
router.push("/chat?show_demo=true")
351445
} else {
352446
const firstQuestion = questions[0]?.question || ""
353447
setConversation([
@@ -462,7 +556,7 @@ const OnboardingPage = () => {
462556
variants={itemVariants}
463557
className="text-lg md:text-xl text-neutral-300 max-w-xl mx-auto"
464558
>
465-
Your proactive AI, ready to get to know you.
559+
I'm excited to get to know you.
466560
</motion.p>
467561
<motion.div
468562
variants={itemVariants}
@@ -472,7 +566,7 @@ const OnboardingPage = () => {
472566
onClick={() => {
473567
setStage("questions")
474568
}}
475-
className="rounded-xl bg-brand-orange/80 px-8 py-3 text-lg font-semibold text-brand-white transition-colors hover:bg-opacity-80"
569+
className="rounded-lg bg-brand-orange px-8 py-3 text-lg font-semibold text-brand-black transition-colors hover:bg-brand-orange/90"
476570
whileHover={{ scale: 1.05 }}
477571
whileTap={{ scale: 0.95 }}
478572
>
@@ -663,7 +757,7 @@ const OnboardingPage = () => {
663757
<div className="flex flex-col sm:flex-row gap-4 items-start">
664758
<input
665759
type="text"
666-
placeholder="Or enter Locality, City, State..."
760+
placeholder="Enter Locality, City, State..."
667761
value={
668762
typeof answers[currentQuestion.id] === "string"
669763
? answers[currentQuestion.id]
@@ -697,10 +791,13 @@ const OnboardingPage = () => {
697791
}
698792

699793
return (
700-
<div className="grid md:grid-cols-20 min-h-screen w-full bg-brand-black text-brand-white overflow-hidden">
794+
<div className="grid md:grid-cols-20 min-h-screen w-full text-brand-white overflow-hidden">
701795
{/* Left Column: Onboarding Flow */}
702796
<div className="relative flex flex-col items-center justify-center w-full p-4 sm:p-8 overflow-hidden md:col-span-13">
703-
<AnimatedBackground />
797+
<div className="absolute inset-0 z-[-1]">
798+
<InteractiveNetworkBackground />
799+
</div>
800+
<div className="absolute -top-[250px] left-1/2 -translate-x-1/2 w-[800px] h-[500px] bg-brand-orange/10 rounded-full blur-3xl -z-10" />
704801
<div className="relative z-10 w-full h-full flex flex-col items-center justify-center">
705802
<SparkleEffect trigger={sparkleTrigger} />
706803
<AnimatePresence mode="wait">

src/client/components/onboarding/UseCaseCarousel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function UseCaseCarousel() {
4747
<CarouselContent>
4848
{useCases.map((useCase, index) => (
4949
<CarouselItem key={index} className="p-4">
50-
<div className="flex flex-col items-center justify-center text-center gap-4 p-6 h-80 rounded-2xl bg-brand-gray/50 border border-brand-gray backdrop-blur-sm">
50+
<div className="flex flex-col items-center justify-center text-center gap-4 p-6 h-80 rounded-2xl bg-neutral-800/60 border border-neutral-700/50 backdrop-blur-sm">
5151
{useCase.icon}
5252
<h3 className="text-2xl font-bold text-brand-white">
5353
{useCase.title}

src/server/delete_pg_memories.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import os
2+
import asyncio
3+
import asyncpg
4+
from dotenv import load_dotenv
5+
import logging
6+
7+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
8+
9+
# --- Configuration ---
10+
# This script is designed to be run from the `src/server` directory.
11+
# It will load your existing .env file to get the PostgreSQL connection details.
12+
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
13+
if os.path.exists(dotenv_path):
14+
load_dotenv(dotenv_path=dotenv_path)
15+
logging.info(f"Loaded environment variables from: {dotenv_path}")
16+
else:
17+
logging.warning(f".env file not found at {dotenv_path}. Relying on shell environment variables.")
18+
19+
POSTGRES_USER = os.getenv("POSTGRES_USER")
20+
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD")
21+
POSTGRES_HOST = os.getenv("POSTGRES_HOST")
22+
POSTGRES_PORT = os.getenv("POSTGRES_PORT")
23+
POSTGRES_DB = os.getenv("POSTGRES_DB")
24+
25+
async def clear_memory_database():
26+
"""Connects to PostgreSQL and truncates memory-related tables after user confirmation."""
27+
if not all([POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_DB]):
28+
logging.error("PostgreSQL connection details are not fully configured in your .env file.")
29+
return
30+
31+
print("\n" + "="*80)
32+
print("⚠️ DANGER: DESTRUCTIVE ACTION AHEAD ⚠️")
33+
print("="*80)
34+
print("This script will permanently delete ALL data from the following PostgreSQL tables:")
35+
print(" - facts")
36+
print(" - fact_topics")
37+
print("\nThis action will erase all user memories and CANNOT be undone.")
38+
print("This is useful for starting with a completely fresh memory store.")
39+
print("="*80 + "\n")
40+
41+
try:
42+
confirm = input(f"To confirm deletion, please type 'DELETE ALL MEMORIES' and press Enter: ")
43+
if confirm.strip() != 'DELETE ALL MEMORIES':
44+
print("\n❌ Deletion cancelled. No changes were made.")
45+
return
46+
except KeyboardInterrupt:
47+
print("\n\n❌ Deletion cancelled by user. No changes were made.")
48+
return
49+
50+
conn = None
51+
try:
52+
logging.info(f"Connecting to PostgreSQL database '{POSTGRES_DB}' on {POSTGRES_HOST}...")
53+
conn = await asyncpg.connect(
54+
user=POSTGRES_USER,
55+
password=POSTGRES_PASSWORD,
56+
database=POSTGRES_DB,
57+
host=POSTGRES_HOST,
58+
port=POSTGRES_PORT
59+
)
60+
logging.info("Successfully connected to PostgreSQL.")
61+
62+
logging.info("Truncating tables: facts, fact_topics...")
63+
# TRUNCATE is faster than DELETE and also resets identity columns. CASCADE handles foreign keys.
64+
await conn.execute("TRUNCATE TABLE facts, fact_topics RESTART IDENTITY CASCADE;")
65+
logging.info("✅ Tables have been successfully cleared.")
66+
67+
except Exception as e:
68+
logging.error(f"An error occurred: {e}", exc_info=True)
69+
finally:
70+
if conn:
71+
await conn.close()
72+
logging.info("PostgreSQL connection closed.")
73+
74+
if __name__ == "__main__":
75+
asyncio.run(clear_memory_database())

src/server/main/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from main.settings.routes import router as settings_router
4949
from main.testing.routes import router as testing_router
5050
from main.search.routes import router as search_router
51+
from main.memories.db import close_db_pool as close_memories_pg_pool
5152
from main.memories.routes import router as memories_router
5253
from main.files.routes import router as files_router
5354
# FIX: Import both router and stream from voice.routes
@@ -138,6 +139,7 @@ async def lifespan(app_instance: FastAPI):
138139
print(f"[{datetime.datetime.now(timezone.utc).isoformat()}] [LIFESPAN] App shutdown sequence initiated...")
139140
if mongo_manager and mongo_manager.client:
140141
mongo_manager.client.close()
142+
await close_memories_pg_pool()
141143
print(f"[{datetime.datetime.now(timezone.utc).isoformat()}] [LIFESPAN] App shutdown complete.")
142144

143145
app = FastAPI(title="Sentient Main Server", version="2.2.0", docs_url="/docs", redoc_url="/redoc", lifespan=lifespan)

src/server/main/memories/prompts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
Key Instructions:
9898
1. Deconstruct Compound Sentences into ATOMIC FACTS: Vigorously split sentences containing conjunctions like 'and', 'but', or 'while' into separate, self-contained facts. Each fact must stand on its own.
9999
2. Isolate Each Idea: Ensure every item in the output list represents one distinct, meaningful idea. EACH ATOMIC FACT MUST BE A COMPLETE THOUGHT.
100-
3. Handle Pronouns and Possessives: If the input contains "I", "me", or "my", correctly convert them to refer to the provided USERNAME. For example, "My sister" becomes "{{USERNAME}}'s sister".
100+
3. Personalize Facts: If the input contains pronouns like "I", "me", or "my", or generic references like "the user", you MUST replace them with the provided USERNAME to create a personalized fact. For example, if USERNAME is 'Alex', "My sister" becomes "Alex's sister", and "The user's favorite color is blue" becomes "Alex's favorite color is blue".
101101
4. Strict JSON Output: Your entire response MUST be a single, valid JSON ARRAY of strings that strictly adheres to the given schema. Do not add any commentary before or after the JSON.
102102
103103
YOU MUST COMPUSLORILY IGNORE THE FOLLOWING:

src/server/mcp_hub/memory/db.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ async def get_db_pool() -> asyncpg.Pool:
5757
logger.debug(f"Returning existing PostgreSQL connection pool for event loop {id(loop)}.")
5858
return pool
5959

60+
async def close_db_pool_for_loop(loop: asyncio.AbstractEventLoop):
61+
"""Closes the connection pool associated with a specific event loop and removes it from the global dict."""
62+
global _pools
63+
pool = _pools.pop(loop, None)
64+
if pool:
65+
if not pool.is_closing():
66+
logger.info(f"Closing PostgreSQL pool for event loop {id(loop)}.")
67+
await pool.close()
68+
else:
69+
logger.debug(f"Pool for event loop {id(loop)} was already closing.")
70+
6071
async def close_db_pool():
6172
"""Closes all PostgreSQL connection pools managed by this module."""
6273
global _pools

src/server/mcp_hub/memory/prompts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
Key Instructions:
9494
1. Deconstruct Compound Sentences into ATOMIC FACTS: Vigorously split sentences containing conjunctions like 'and', 'but', or 'while' into separate, self-contained facts. Each fact must stand on its own.
9595
2. Isolate Each Idea: Ensure every item in the output list represents one distinct, meaningful idea. EACH ATOMIC FACT MUST BE A COMPLETE THOUGHT.
96-
3. Handle Pronouns and Possessives: If the input contains "I", "me", or "my", correctly convert them to refer to the provided USERNAME. For example, "My sister" becomes "{{USERNAME}}'s sister".
96+
3. Personalize Facts: If the input contains pronouns like "I", "me", or "my", or generic references like "the user", you MUST replace them with the provided USERNAME to create a personalized fact. For example, if USERNAME is 'Alex', "My sister" becomes "Alex's sister", and "The user's favorite color is blue" becomes "Alex's favorite color is blue".
9797
4. Strict JSON Output: Your entire response MUST be a single, valid JSON ARRAY of strings that strictly adheres to the given schema. Do not add any commentary before or after the JSON.
9898
9999
YOU MUST COMPUSLORILY IGNORE THE FOLLOWING:

0 commit comments

Comments
 (0)