Skip to content

Commit 642ebcc

Browse files
committed
fix (proactivity): enhanced proactivity pipeline
feat (tasks): added task description field feat (memories): added memories page
1 parent 0cf6556 commit 642ebcc

32 files changed

Lines changed: 758 additions & 135 deletions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { NextResponse } from "next/server"
2+
import { withAuth } from "@lib/api-utils"
3+
4+
const appServerUrl =
5+
process.env.NEXT_PUBLIC_ENVIRONMENT === "selfhost"
6+
? process.env.INTERNAL_APP_SERVER_URL
7+
: process.env.NEXT_PUBLIC_APP_SERVER_URL
8+
9+
export const GET = withAuth(async function GET(request, { authHeader }) {
10+
const backendUrl = new URL(`${appServerUrl}/api/memories/`)
11+
12+
try {
13+
const response = await fetch(backendUrl.toString(), {
14+
method: "GET",
15+
headers: { "Content-Type": "application/json", ...authHeader }
16+
})
17+
18+
const data = await response.json()
19+
if (!response.ok) {
20+
throw new Error(data.detail || "Failed to fetch memories")
21+
}
22+
return NextResponse.json(data)
23+
} catch (error) {
24+
console.error("API Error in /api/memories:", error)
25+
return NextResponse.json({ error: error.message }, { status: 500 })
26+
}
27+
})

src/client/app/memories/page.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
"use client"
2+
3+
import React, { useState, useEffect, useMemo } from "react"
4+
import toast from "react-hot-toast"
5+
import {
6+
IconLoader,
7+
IconBrain,
8+
IconTag,
9+
IconClock,
10+
IconFileText
11+
} from "@tabler/icons-react"
12+
import { motion, AnimatePresence } from "framer-motion"
13+
import { formatDistanceToNow, parseISO } from "date-fns"
14+
import { cn } from "@utils/cn"
15+
import { GridBackground } from "@components/ui/GridBackground"
16+
17+
const MemoryCard = ({ memory }) => {
18+
const timeAgo = formatDistanceToNow(parseISO(memory.created_at), {
19+
addSuffix: true
20+
})
21+
22+
return (
23+
<motion.div
24+
layout
25+
initial={{ opacity: 0, scale: 0.9 }}
26+
animate={{ opacity: 1, scale: 1 }}
27+
exit={{ opacity: 0, scale: 0.9 }}
28+
transition={{ duration: 0.3 }}
29+
className="bg-neutral-900/50 p-6 rounded-2xl border border-neutral-800/80 flex flex-col justify-between text-left h-full shadow-lg hover:border-sentient-blue/30 transition-colors"
30+
>
31+
<p className="text-neutral-200 text-base mb-4 font-sans leading-relaxed">
32+
{memory.content}
33+
</p>
34+
<div className="mt-auto pt-4 border-t border-neutral-800/50 text-xs text-neutral-500 space-y-2">
35+
<div className="flex items-center gap-2">
36+
<IconClock size={14} />
37+
<span>{timeAgo}</span>
38+
</div>
39+
{memory.source && (
40+
<div className="flex items-center gap-2">
41+
<IconFileText size={14} />
42+
<span>Source: {memory.source}</span>
43+
</div>
44+
)}
45+
{memory.topics && memory.topics.length > 0 && (
46+
<div className="flex items-center gap-2 flex-wrap pt-1">
47+
<IconTag size={14} />
48+
{memory.topics.map((topic) => (
49+
<span
50+
key={topic}
51+
className="bg-neutral-800 px-2 py-0.5 rounded-full text-neutral-300"
52+
>
53+
{topic}
54+
</span>
55+
))}
56+
</div>
57+
)}
58+
</div>
59+
</motion.div>
60+
)
61+
}
62+
63+
const MemoriesPage = () => {
64+
const [memories, setMemories] = useState([])
65+
const [isLoading, setIsLoading] = useState(true)
66+
const [activeTopic, setActiveTopic] = useState("All")
67+
68+
useEffect(() => {
69+
const fetchMemories = async () => {
70+
setIsLoading(true)
71+
try {
72+
const response = await fetch("/api/memories")
73+
if (!response.ok) {
74+
throw new Error("Failed to fetch memories.")
75+
}
76+
const data = await response.json()
77+
setMemories(data.memories || [])
78+
} catch (error) {
79+
toast.error(error.message)
80+
} finally {
81+
setIsLoading(false)
82+
}
83+
}
84+
fetchMemories()
85+
}, [])
86+
87+
const topics = useMemo(() => {
88+
const allTopics = new Set()
89+
memories.forEach((memory) => {
90+
memory.topics.forEach((topic) => allTopics.add(topic))
91+
})
92+
return ["All", ...Array.from(allTopics).sort()]
93+
}, [memories])
94+
95+
const filteredMemories = useMemo(() => {
96+
if (activeTopic === "All") {
97+
return memories
98+
}
99+
return memories.filter((memory) => memory.topics.includes(activeTopic))
100+
}, [memories, activeTopic])
101+
102+
return (
103+
<div className="flex-1 flex h-screen bg-brand-black text-white overflow-hidden md:pl-20">
104+
<GridBackground className="flex-1 flex flex-col overflow-hidden relative">
105+
<header className="flex items-center justify-between p-6 bg-brand-black/50 backdrop-blur-sm border-b border-neutral-800/50 shrink-0 z-10">
106+
<div>
107+
<h1 className="text-3xl lg:text-4xl font-bold text-white flex items-center gap-3">
108+
<IconBrain
109+
size={36}
110+
className="text-sentient-blue"
111+
/>
112+
Your Memories
113+
</h1>
114+
<p className="text-neutral-400 mt-1">
115+
A collection of facts and information I've learned
116+
about you.
117+
</p>
118+
</div>
119+
</header>
120+
<main className="flex-1 overflow-y-auto p-6 md:p-10 custom-scrollbar">
121+
{isLoading ? (
122+
<div className="flex justify-center items-center h-full">
123+
<IconLoader className="w-12 h-12 animate-spin text-sentient-blue" />
124+
</div>
125+
) : (
126+
<div className="w-full max-w-7xl mx-auto">
127+
<div className="flex flex-wrap gap-2 mb-8 sticky top-0 bg-brand-black/50 backdrop-blur-sm py-4 z-10 rounded-b-xl -mt-2">
128+
{topics.map((topic) => (
129+
<button
130+
key={topic}
131+
onClick={() => setActiveTopic(topic)}
132+
className={cn(
133+
"px-4 py-2 rounded-full text-sm font-medium transition-colors",
134+
activeTopic === topic
135+
? "bg-white text-black shadow-lg shadow-white/10"
136+
: "bg-neutral-800 text-neutral-300 hover:bg-neutral-700 hover:text-white"
137+
)}
138+
>
139+
{topic}
140+
</button>
141+
))}
142+
</div>
143+
{filteredMemories.length > 0 ? (
144+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
145+
<AnimatePresence>
146+
{filteredMemories.map((memory) => (
147+
<MemoryCard
148+
key={memory.id}
149+
memory={memory}
150+
/>
151+
))}
152+
</AnimatePresence>
153+
</div>
154+
) : (
155+
<div className="text-center py-20 text-neutral-500">
156+
<IconBrain
157+
size={48}
158+
className="mx-auto mb-4"
159+
/>
160+
<h3 className="text-xl font-semibold text-neutral-300">
161+
No memories found
162+
</h3>
163+
<p>
164+
{activeTopic === "All"
165+
? "I haven't learned anything about you yet. Try telling me something in the chat!"
166+
: `No memories match the topic '${activeTopic}'.`}
167+
</p>
168+
</div>
169+
)}
170+
</div>
171+
)}
172+
</main>
173+
</GridBackground>
174+
</div>
175+
)
176+
}
177+
178+
export default MemoriesPage

src/client/app/tasks/page.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,13 @@ function TasksPageContent() {
248248
if (!searchQuery.trim()) {
249249
return oneTimeTasks
250250
}
251-
return oneTimeTasks.filter((task) =>
252-
task.description.toLowerCase().includes(searchQuery.toLowerCase())
251+
return oneTimeTasks.filter(
252+
(task) =>
253+
task.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
254+
(task.description &&
255+
task.description
256+
.toLowerCase()
257+
.includes(searchQuery.toLowerCase()))
253258
)
254259
}, [oneTimeTasks, searchQuery])
255260

@@ -258,7 +263,7 @@ function TasksPageContent() {
258263
return recurringTasks
259264
}
260265
return recurringTasks.filter((task) =>
261-
task.description.toLowerCase().includes(searchQuery.toLowerCase())
266+
task.name.toLowerCase().includes(searchQuery.toLowerCase())
262267
)
263268
}, [recurringTasks, searchQuery])
264269

@@ -268,7 +273,7 @@ function TasksPageContent() {
268273
return allCalendarTasks
269274
}
270275
return allCalendarTasks.filter((task) =>
271-
task.description.toLowerCase().includes(searchQuery.toLowerCase())
276+
task.name.toLowerCase().includes(searchQuery.toLowerCase())
272277
)
273278
}, [oneTimeTasks, recurringInstances, searchQuery])
274279

0 commit comments

Comments
 (0)