feat(dashboard): chat-context streaming, telegram thread card, digest/sidebar tweaks, file-tasks route
This commit is contained in:
parent
84aa98dd14
commit
0970160f29
6 changed files with 110 additions and 64 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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:<chat_id>". 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<string, unknown>
|
||||
}
|
||||
|
||||
export function TelegramThreadCard() {
|
||||
const { data } = useSWR<TelegramResponse>(
|
||||
"/api/chat?source=telegram&limit=5",
|
||||
fetcher,
|
||||
{ refreshInterval: 60_000, shouldRetryOnError: false }
|
||||
)
|
||||
const [messages, setMessages] = useState<TelegramMessage[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(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 (
|
||||
<Card className="bg-card/40 p-4 flex flex-col">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Send className="h-4 w-4 text-primary" />
|
||||
<span className="text-[11px] uppercase tracking-wider text-muted-foreground/80">Telegram thread</span>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<span className="text-sm text-muted-foreground">Loading...</span>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Card className="bg-card/40 p-4 flex flex-col">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Send className="h-4 w-4 text-primary" />
|
||||
<span className="text-[11px] uppercase tracking-wider text-muted-foreground/80">Telegram thread</span>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<span className="text-sm text-red-500">Error: {error}</span>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-card/40 p-4 flex flex-col">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Send className="h-4 w-4 text-primary" />
|
||||
<span className="text-[11px] uppercase tracking-wider text-muted-foreground/80">
|
||||
Telegram thread
|
||||
</span>
|
||||
<span className="text-[11px] uppercase tracking-wider text-muted-foreground/80">Chat history</span>
|
||||
</div>
|
||||
<a href="/chat" className="text-xs text-primary hover:underline">
|
||||
Open chat →
|
||||
</a>
|
||||
<a href="/chat?session=telegram" className="text-xs text-primary hover:underline">Open chat →</a>
|
||||
</div>
|
||||
|
||||
{hasData ? (
|
||||
|
|
@ -77,27 +99,24 @@ export function TelegramThreadCard() {
|
|||
{msg.role === "user" ? "You" : "Tiger"}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{relTime(msg.ts)}
|
||||
{formatTime(msg.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-foreground/85 text-xs leading-relaxed mt-0.5">
|
||||
<div className="text-xs text-muted-foreground/80 truncate">
|
||||
{truncate(msg.content)}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-center py-6 px-2">
|
||||
<Send className="h-8 w-8 text-muted-foreground/30 mb-2" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No Telegram messages mirrored yet.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">No messages yet.</p>
|
||||
<p className="text-[11px] text-muted-foreground/60 mt-1 max-w-[260px]">
|
||||
Phase 4 will sync your @Tiger_4321_bot conversation here so web
|
||||
and Telegram share one history.
|
||||
Start a conversation to see messages here.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue