Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ const primarySettingsSections: NavSection[] = [
icon: Palette,
feature: Feature.CUSTOMIZATION_SUPPORT,
},
{
name: 'Reset Data',
to: '/settings/reset-data',
icon: RotateCcw,
},
{
name: 'Tool Approvals',
to: '/settings/approvals',
Expand Down
2 changes: 2 additions & 0 deletions packages/browseros-agent/apps/agent/entrypoints/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { MagicLinkCallback } from './login/MagicLinkCallback'
import { MCPSettingsPage } from './mcp-settings/MCPSettingsPage'
import { MemoryPage } from './memory/MemoryPage'
import { ProfilePage } from './profile/ProfilePage'
import { ResetDataPage } from './reset-data/ResetDataPage'
import { ScheduledTasksPage } from './scheduled-tasks/ScheduledTasksPage'
import { SearchProviderPage } from './search-provider/SearchProviderPage'
import { SkillsPage } from './skills/SkillsPage'
Expand Down Expand Up @@ -143,6 +144,7 @@ export const App: FC = () => {
<Route path="chat" element={<LlmHubPage />} />
<Route path="mcp" element={<MCPSettingsPage />} />
<Route path="customization" element={<CustomizationPage />} />
<Route path="reset-data" element={<ResetDataPage />} />
<Route path="search" element={<SearchProviderPage />} />
<Route path="survey" element={<SurveyPage {...surveyParams} />} />
<Route path="usage" element={<UsagePage />} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useAgentServerUrl } from '@/lib/browseros/useBrowserOSProviders'

export const MEMORY_QUERY_KEY = 'memory'

async function fetchMemory(baseUrl: string): Promise<string> {
const response = await fetch(`${baseUrl}/memory`)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
Expand Down Expand Up @@ -30,15 +32,15 @@ export function useMemoryContent() {
const queryClient = useQueryClient()

const { data, isLoading, error, refetch } = useQuery<string, Error>({
queryKey: ['memory', baseUrl],
queryKey: [MEMORY_QUERY_KEY, baseUrl],
queryFn: () => fetchMemory(baseUrl as string),
enabled: !!baseUrl && !urlLoading,
})

const saveMutation = useMutation({
mutationFn: (content: string) => saveMemory(baseUrl as string, content),
onSuccess: (_data, content) => {
queryClient.setQueryData(['memory', baseUrl], content)
queryClient.setQueryData([MEMORY_QUERY_KEY, baseUrl], content)
},
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { Brain, FileText, Loader2, RotateCcw } from 'lucide-react'
import { type FC, type ReactNode, useState } from 'react'
import { toast } from 'sonner'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import { useAgentServerUrl } from '@/lib/browseros/useBrowserOSProviders'
import { MEMORY_QUERY_KEY } from '../memory/useMemoryContent'
import { SOUL_QUERY_KEY } from '../soul/useSoulContent'

type ResetTarget = 'memory' | 'soul'

type ResetAction = {
target: ResetTarget
title: string
description: string
buttonLabel: string
icon: ReactNode
}

async function deleteServerResource(
baseUrl: string,
resource: ResetTarget,
): Promise<void> {
const response = await fetch(`${baseUrl}/${resource}`, { method: 'DELETE' })
if (!response.ok) throw new Error(`HTTP ${response.status}`)
}

export const ResetDataPage: FC = () => {
const {
baseUrl,
isLoading: isUrlLoading,
error: urlError,
} = useAgentServerUrl()
const queryClient = useQueryClient()
const [pendingAction, setPendingAction] = useState<ResetAction | null>(null)

const resetMutation = useMutation({
mutationFn: async (target: ResetTarget) => {
if (!baseUrl) throw new Error('BrowserOS server URL is unavailable')
await deleteServerResource(baseUrl, target)
return target
},
onSuccess: async (target) => {
if (target === 'memory') {
queryClient.setQueryData([MEMORY_QUERY_KEY, baseUrl], '')
}
await queryClient.invalidateQueries({
queryKey: target === 'memory' ? [MEMORY_QUERY_KEY] : [SOUL_QUERY_KEY],
})
toast.success(target === 'memory' ? 'Memory reset' : 'SOUL.md reset')
},
onError: (_error, target) => {
toast.error(
target === 'memory'
? 'Failed to reset memory'
: 'Failed to reset SOUL.md',
)
},
})

const actions: ResetAction[] = [
{
target: 'memory',
title: 'Reset memory?',
description:
'This deletes CORE.md and daily memory files. This cannot be undone.',
buttonLabel: 'Reset memory',
icon: <Brain className="h-4 w-4 text-muted-foreground" />,
},
{
target: 'soul',
title: 'Reset SOUL.md?',
description:
'This replaces SOUL.md with the default template. This cannot be undone.',
buttonLabel: 'Reset SOUL.md',
icon: <FileText className="h-4 w-4 text-muted-foreground" />,
},
]

const isBusy = isUrlLoading || resetMutation.isPending
const disabled = isBusy || Boolean(urlError) || !baseUrl

const handleConfirm = () => {
if (!pendingAction) return
resetMutation.mutate(pendingAction.target)
setPendingAction(null)
}

return (
<div className="mx-auto w-full max-w-3xl space-y-6 p-6">
<div>
<div className="mb-2 flex items-center gap-2 text-muted-foreground">
<RotateCcw className="h-4 w-4" />
<span className="font-medium text-xs uppercase tracking-wider">
Reset
</span>
</div>
<h1 className="font-semibold text-2xl">Reset Data</h1>
</div>

{urlError ? (
<div className="rounded-lg border border-destructive/50 bg-destructive/5 p-4">
<p className="text-destructive text-sm">
BrowserOS server is unavailable.
</p>
</div>
) : null}

<div className="space-y-3">
{actions.map((action) => (
<div
key={action.target}
className="flex flex-col gap-3 rounded-lg border bg-card p-4 shadow-sm sm:flex-row sm:items-center sm:justify-between"
>
<div className="flex min-w-0 items-center gap-3">
{action.icon}
<div className="min-w-0">
<h2 className="font-medium text-sm">{action.buttonLabel}</h2>
<p className="mt-1 text-muted-foreground text-xs">
{action.description}
</p>
</div>
</div>
<Button
type="button"
variant="destructive"
size="sm"
className="shrink-0"
disabled={disabled}
onClick={() => setPendingAction(action)}
>
{resetMutation.isPending &&
resetMutation.variables === action.target ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<RotateCcw className="h-3.5 w-3.5" />
)}
{action.buttonLabel}
</Button>
</div>
))}
</div>

<AlertDialog
open={Boolean(pendingAction)}
onOpenChange={(open) => {
if (!open) setPendingAction(null)
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{pendingAction?.title}</AlertDialogTitle>
<AlertDialogDescription>
{pendingAction?.description}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirm}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{pendingAction?.buttonLabel}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { keepPreviousData, useQueryClient } from '@tanstack/react-query'
import type { UIMessage } from 'ai'
import { Loader2 } from 'lucide-react'
import type { FC } from 'react'
import { useMemo } from 'react'
import { useMemo, useState } from 'react'
import { toast } from 'sonner'
import { useSessionInfo } from '@/lib/auth/sessionStorage'
import { useConversations } from '@/lib/conversations/conversationStorage'
import { GetProfileIdByUserIdDocument } from '@/lib/conversations/graphql/uploadConversationDocument'
Expand All @@ -21,8 +22,11 @@ import {
import { LocalChatHistory } from './local/LocalChatHistory'

const RemoteChatHistory: FC<{ userId: string }> = ({ userId }) => {
const { conversationId: activeConversationId } = useChatSessionContext()
const { conversationId: activeConversationId, resetConversation } =
useChatSessionContext()
const { clearConversations } = useConversations()
const queryClient = useQueryClient()
const [isClearingAll, setIsClearingAll] = useState(false)

const { data: profileData } = useGraphqlQuery(GetProfileIdByUserIdDocument, {
userId,
Expand Down Expand Up @@ -68,6 +72,50 @@ const RemoteChatHistory: FC<{ userId: string }> = ({ userId }) => {
deleteConversationMutation.mutate({ rowId: id })
}

const getAllRemoteConversationIds = async () => {
let pages = graphqlData?.pages ?? []
let hasMore = Boolean(
pages.at(-1)?.conversations?.pageInfo.hasNextPage ?? hasNextPage,
)

while (hasMore) {
const result = await fetchNextPage()
pages = result.data?.pages ?? pages
hasMore = Boolean(pages.at(-1)?.conversations?.pageInfo.hasNextPage)
}

return pages.flatMap((page) =>
(page.conversations?.nodes ?? [])
.filter((node): node is NonNullable<typeof node> => node !== null)
.map((node) => node.rowId),
)
}

const handleClearAll = async () => {
setIsClearingAll(true)
try {
const ids = [...new Set(await getAllRemoteConversationIds())]
for (let i = 0; i < ids.length; i += 10) {
const batch = ids.slice(i, i + 10)
await Promise.all(
batch.map((rowId) =>
deleteConversationMutation.mutateAsync({ rowId }),
),
)
}
await clearConversations()
resetConversation()
await queryClient.invalidateQueries({
queryKey: [getQueryKeyFromDocument(GetConversationsForHistoryDocument)],
})
toast.success('Chat sessions cleared')
} catch {
toast.error('Failed to clear chat sessions')
} finally {
setIsClearingAll(false)
}
}
Comment thread
shadowfax92 marked this conversation as resolved.

const conversations = useMemo<HistoryConversation[]>(() => {
if (!graphqlData?.pages) return []

Expand Down Expand Up @@ -110,6 +158,8 @@ const RemoteChatHistory: FC<{ userId: string }> = ({ userId }) => {
groupedConversations={groupedConversations}
activeConversationId={activeConversationId}
onDelete={handleDelete}
onClearAll={handleClearAll}
isClearingAll={isClearingAll || deleteConversationMutation.isPending}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
onLoadMore={fetchNextPage}
Expand All @@ -121,8 +171,6 @@ const RemoteChatHistory: FC<{ userId: string }> = ({ userId }) => {
export const ChatHistory: FC = () => {
const { sessionInfo } = useSessionInfo()
const userId = sessionInfo.user?.id
// needed to initiate remote-sync
useConversations()

if (userId) {
return <RemoteChatHistory userId={userId} />
Expand Down
Loading
Loading