diff --git a/dashboard/src/app/api/chat/route.ts b/dashboard/src/app/api/chat/route.ts index f638aa8..f6a55ab 100644 --- a/dashboard/src/app/api/chat/route.ts +++ b/dashboard/src/app/api/chat/route.ts @@ -36,6 +36,20 @@ async function persistMessage(role: "user" | "agent", content: string, sessionKe } } +export async function GET(request: NextRequest) { + const sessionKey = request.nextUrl.searchParams.get("sessionKey") || DEFAULT_SESSION_KEY; + + try { + const res = await fetch(`${BRIDGE_URL}/tiger/chat/history?sessionId=${encodeURIComponent(sessionKey)}&limit=50`, { + headers: { Authorization: `Bearer ${BRIDGE_TOKEN}` }, + }); + const data = await res.json(); + return Response.json(data); + } catch (err) { + return Response.json({ error: (err as Error).message }, { status: 500 }); + } +} + export async function POST(request: NextRequest) { const body = await request.json().catch(() => ({})); const message: string = body?.message; @@ -47,8 +61,7 @@ export async function POST(request: NextRequest) { }); } - // Persist user message NOW, before the LLM call. If the call fails, the - // history still records what the user said. + // Persist user message NOW, before the LLM call await persistMessage("user", message, sessionKey); const t0 = Date.now(); diff --git a/dashboard/src/app/api/tiger/file-tasks/route.ts b/dashboard/src/app/api/tiger/file-tasks/route.ts index b099cb4..ada992d 100644 --- a/dashboard/src/app/api/tiger/file-tasks/route.ts +++ b/dashboard/src/app/api/tiger/file-tasks/route.ts @@ -15,11 +15,17 @@ export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const section = searchParams.get("section"); // active | completed | all + const project = searchParams.get("project"); // dashboard, oil, etc let endpoint = "/tiger/file-tasks"; if (section === "active") endpoint = "/tiger/file-tasks/active"; else if (section === "completed") endpoint = "/tiger/file-tasks/completed"; + // Add project filter if provided + if (project) { + endpoint += `?project=${encodeURIComponent(project)}`; + } + const result = await bridgeGet(endpoint); return NextResponse.json(result); } catch (err: unknown) { diff --git a/dashboard/src/components/app-sidebar.tsx b/dashboard/src/components/app-sidebar.tsx index 7851dc9..fb65bc8 100644 --- a/dashboard/src/components/app-sidebar.tsx +++ b/dashboard/src/components/app-sidebar.tsx @@ -53,6 +53,7 @@ const navMain = [ { title: "Agents", url: "/agents", icon: Bot }, { title: "Knowledge", url: "/knowledge", icon: Brain }, { title: "Workspace", url: "/workspace", icon: FolderOpen }, + { title: "Activity", url: "/activity", icon: ScrollText }, { title: "Cost", url: "/cost", icon: DollarSign }, { title: "Logs", url: "/logs", icon: ScrollText }, ] diff --git a/dashboard/src/components/digest-card.tsx b/dashboard/src/components/digest-card.tsx index 579e209..874a418 100644 --- a/dashboard/src/components/digest-card.tsx +++ b/dashboard/src/components/digest-card.tsx @@ -1,5 +1,7 @@ "use client" +import { bridgeGet } from "@/lib/bridge" + /** * digest-card.tsx — Today's digest of agent activity * @@ -27,7 +29,7 @@ interface ActivityResponse { events: ActivityEvent[] } -const fetcher = (url: string) => fetch(url).then((r) => r.json()) +const fetcher = (url: string) => bridgeGet(url).then((r) => r.json()) function relTime(ts: number): string { if (!ts) return "" diff --git a/dashboard/src/components/telegram-thread-card.tsx b/dashboard/src/components/telegram-thread-card.tsx index cfd1801..775026b 100644 --- a/dashboard/src/components/telegram-thread-card.tsx +++ b/dashboard/src/components/telegram-thread-card.tsx @@ -1,71 +1,93 @@ "use client" -/** - * telegram-thread-card.tsx — Preview of recent Telegram conversation - * - * PHASE 1: Renders an empty state today. Phase 4 of the plan adds a Telegram - * listener in the bridge that writes inbound/outbound Telegram messages - * with session_id = "telegram:". When that lands, this component - * starts populating with zero additional frontend work. - */ - -import * as React from "react" -import useSWR from "swr" +import { useEffect, useState } from "react" import { Card } from "@/components/ui/card" import { Send } from "lucide-react" interface TelegramMessage { - role: "user" | "agent" | "system" + role: string content: string - ts: number -} - -interface TelegramResponse { - ok: boolean - messages: TelegramMessage[] -} - -const fetcher = (url: string) => - fetch(url).then((r) => (r.ok ? r.json() : { ok: false, messages: [] })) - -function relTime(ts: number): string { - if (!ts) return "" - const diff = Date.now() - ts - const m = Math.floor(diff / 60_000) - if (m < 1) return "just now" - if (m < 60) return `${m}m ago` - const h = Math.floor(m / 60) - if (h < 24) return `${h}h ago` - const d = Math.floor(h / 24) - return `${d}d ago` -} - -function truncate(s: string, max = 90): string { - return s.length <= max ? s : s.slice(0, max - 1) + "…" + timestamp: number + meta?: Record } export function TelegramThreadCard() { - const { data } = useSWR( - "/api/chat?source=telegram&limit=5", - fetcher, - { refreshInterval: 60_000, shouldRetryOnError: false } - ) + const [messages, setMessages] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + fetch("/api/chat/history?limit=5") + .then(r => r.json()) + .then(data => { + if (data?.messages) { + setMessages(data.messages.slice(-5).reverse()) + } + setLoading(false) + }) + .catch(e => { + console.error("Failed to load:", e) + setError(e.message) + setLoading(false) + }) + }, []) - const messages = data?.messages ?? [] const hasData = messages.length > 0 + // Simple timestamp formatter + const formatTime = (ts: number) => { + if (!ts) return "" + const diff = Date.now() - ts + const mins = Math.floor(diff / 60000) + if (mins < 1) return "just now" + if (mins < 60) return `${mins}m ago` + const hours = Math.floor(mins / 60) + if (hours < 24) return `${hours}h ago` + return new Date(ts).toLocaleDateString() + } + + // Simple truncate + const truncate = (text: string, max = 40) => { + if (!text) return "" + return text.length > max ? text.slice(0, max) + "..." : text + } + + if (loading) { + return ( + +
+ + Telegram thread +
+
+ Loading... +
+
+ ) + } + + if (error) { + return ( + +
+ + Telegram thread +
+
+ Error: {error} +
+
+ ) + } + return (
- - Telegram thread - + Chat history
- - Open chat → - + Open chat →
{hasData ? ( @@ -77,27 +99,24 @@ export function TelegramThreadCard() { {msg.role === "user" ? "You" : "Tiger"} - {relTime(msg.ts)} + {formatTime(msg.timestamp)} -

+

{truncate(msg.content)} -

+
))} ) : (
-

- No Telegram messages mirrored yet. -

+

No messages yet.

- Phase 4 will sync your @Tiger_4321_bot conversation here so web - and Telegram share one history. + Start a conversation to see messages here.

)}
) -} +} \ No newline at end of file diff --git a/dashboard/src/contexts/chat-context.tsx b/dashboard/src/contexts/chat-context.tsx index 6bd07a7..6edd4f0 100644 --- a/dashboard/src/contexts/chat-context.tsx +++ b/dashboard/src/contexts/chat-context.tsx @@ -131,11 +131,16 @@ export function ChatProvider({ children }: { children: React.ReactNode }) { // Initial mount: pick stored sessionKey, load its history, fetch sessions list. React.useEffect(() => { let cancelled = false + + // Check URL for session param + const urlParams = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '') + const sessionParam = urlParams.get('session') + let initialKey = sessionParam ? `agent:main:${sessionParam}` : readPersistedKey() + async function init() { - const persisted = readPersistedKey() if (cancelled) return - setCurrentSessionKey(persisted) - const [hist] = await Promise.all([loadHistoryFor(persisted), refreshSessions()]) + setCurrentSessionKey(initialKey) + const [hist] = await Promise.all([loadHistoryFor(initialKey), refreshSessions()]) if (!cancelled) { setMessages(hist) setLoading(false)