diff --git a/src/components/monitor/SettingsPanel.tsx b/src/components/monitor/SettingsPanel.tsx
index c4630a9..1ca30f1 100644
--- a/src/components/monitor/SettingsPanel.tsx
+++ b/src/components/monitor/SettingsPanel.tsx
@@ -1,13 +1,18 @@
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
-import { Settings, X, History, Wifi, WifiOff, RefreshCw, Terminal } from 'lucide-react'
+import { Settings, X, History, Wifi, WifiOff, RefreshCw, Terminal, Download, Trash2, Database } from 'lucide-react'
interface SettingsPanelProps {
connected: boolean
historicalMode: boolean
debugMode: boolean
+ logCollection: boolean
+ logCount: number
onHistoricalModeChange: (enabled: boolean) => void
onDebugModeChange: (enabled: boolean) => void
+ onLogCollectionChange: (enabled: boolean) => void
+ onDownloadLogs: () => void
+ onClearLogs: () => void
onConnect: () => void
onDisconnect: () => void
onRefresh: () => void
@@ -17,8 +22,13 @@ export function SettingsPanel({
connected,
historicalMode,
debugMode,
+ logCollection,
+ logCount,
onHistoricalModeChange,
onDebugModeChange,
+ onLogCollectionChange,
+ onDownloadLogs,
+ onClearLogs,
onConnect,
onDisconnect,
onRefresh,
@@ -161,6 +171,56 @@ export function SettingsPanel({
+ {/* Log collection */}
+
+
+
+
+ Log Collection
+
+
+
+
+ > collect raw events for export
+
+
+ {logCount > 0 && (
+
+ > {logCount} events collected
+
+ )}
+
+
+
+
+
+
+
+
+
{/* Info panel */}
diff --git a/src/integrations/trpc/router.ts b/src/integrations/trpc/router.ts
index f08d9b4..7b6378a 100644
--- a/src/integrations/trpc/router.ts
+++ b/src/integrations/trpc/router.ts
@@ -13,6 +13,10 @@ import {
// Server-side debug mode state
let debugMode = false
+// Server-side log collection
+let collectLogs = false
+const collectedEvents: Array<{ timestamp: number; event: unknown }> = []
+
const t = initTRPC.create({
transformer: superjson,
})
@@ -66,6 +70,38 @@ const clawdbotRouter = router({
return { debugMode }
}),
+ // Log collection
+ setLogCollection: publicProcedure
+ .input(z.object({ enabled: z.boolean() }))
+ .mutation(({ input }) => {
+ collectLogs = input.enabled
+ if (input.enabled) {
+ console.log(`[clawdbot] log collection started`)
+ } else {
+ console.log(`[clawdbot] log collection stopped, ${collectedEvents.length} events collected`)
+ }
+ return { collectLogs, eventCount: collectedEvents.length }
+ }),
+
+ getLogCollection: publicProcedure.query(() => {
+ return { collectLogs, eventCount: collectedEvents.length }
+ }),
+
+ downloadLogs: publicProcedure.query(() => {
+ return {
+ events: collectedEvents,
+ count: collectedEvents.length,
+ collectedAt: new Date().toISOString(),
+ }
+ }),
+
+ clearLogs: publicProcedure.mutation(() => {
+ const count = collectedEvents.length
+ collectedEvents.length = 0
+ console.log(`[clawdbot] cleared ${count} collected events`)
+ return { cleared: count }
+ }),
+
sessions: publicProcedure
.input(
z
@@ -103,6 +139,14 @@ const clawdbotRouter = router({
const client = getClawdbotClient()
const unsubscribe = client.onEvent((event) => {
+ // Collect raw event when log collection is enabled
+ if (collectLogs) {
+ collectedEvents.push({
+ timestamp: Date.now(),
+ event,
+ })
+ }
+
// Log raw event when debug mode is enabled
if (debugMode) {
console.log('\n[DEBUG] Raw event:', JSON.stringify(event, null, 2))
diff --git a/src/routes/monitor/index.tsx b/src/routes/monitor/index.tsx
index 7f0d2ad..c4411ba 100644
--- a/src/routes/monitor/index.tsx
+++ b/src/routes/monitor/index.tsx
@@ -63,6 +63,8 @@ function MonitorPage() {
const [error, setError] = useState(null)
const [historicalMode, setHistoricalMode] = useState(false)
const [debugMode, setDebugMode] = useState(false)
+ const [logCollection, setLogCollection] = useState(false)
+ const [logCount, setLogCount] = useState(0)
const [selectedSession, setSelectedSession] = useState(null)
// Sidebar collapse state - default to collapsed on mobile
@@ -168,6 +170,56 @@ function MonitorPage() {
}
}
+ const handleLogCollectionChange = async (enabled: boolean) => {
+ setLogCollection(enabled)
+ try {
+ const result = await trpc.clawdbot.setLogCollection.mutate({ enabled })
+ setLogCount(result.eventCount)
+ } catch (e) {
+ console.error('Failed to set log collection:', e)
+ }
+ }
+
+ const handleDownloadLogs = async () => {
+ try {
+ const result = await trpc.clawdbot.downloadLogs.query()
+ const blob = new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = `clawdbot-events-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`
+ document.body.appendChild(a)
+ a.click()
+ document.body.removeChild(a)
+ URL.revokeObjectURL(url)
+ } catch (e) {
+ console.error('Failed to download logs:', e)
+ }
+ }
+
+ const handleClearLogs = async () => {
+ try {
+ await trpc.clawdbot.clearLogs.mutate()
+ setLogCount(0)
+ } catch (e) {
+ console.error('Failed to clear logs:', e)
+ }
+ }
+
+ // Poll log count while collecting
+ useEffect(() => {
+ if (!logCollection) return
+ const interval = setInterval(async () => {
+ try {
+ const result = await trpc.clawdbot.getLogCollection.query()
+ setLogCount(result.eventCount)
+ } catch {
+ // ignore
+ }
+ }, 2000)
+ return () => clearInterval(interval)
+ }, [logCollection])
+
const handleToggleSidebar = useCallback(() => {
setSidebarCollapsed((prev) => !prev)
}, [])
@@ -275,8 +327,13 @@ function MonitorPage() {
connected={connected}
historicalMode={historicalMode}
debugMode={debugMode}
+ logCollection={logCollection}
+ logCount={logCount}
onHistoricalModeChange={handleHistoricalModeChange}
onDebugModeChange={handleDebugModeChange}
+ onLogCollectionChange={handleLogCollectionChange}
+ onDownloadLogs={handleDownloadLogs}
+ onClearLogs={handleClearLogs}
onConnect={handleConnect}
onDisconnect={handleDisconnect}
onRefresh={handleRefresh}