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
29 changes: 11 additions & 18 deletions app/api/plus/analytics/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { supabaseServer } from '@/lib/supabase-server'
import { supabaseServer } from '@/lib/supabase-server'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
Expand All @@ -9,7 +9,6 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'User ID required' }, { status: 400 })
}

// Verify user is Plus plan
const { data: user, error: userError } = await supabaseServer
.from('users')
.select('subscription_plan')
Expand All @@ -20,23 +19,19 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized - Plus plan required' }, { status: 403 })
}

// Get chat sessions for analytics
const { data: chats } = await supabaseServer
.from('chat_sessions')
.select('tool, created_at')
.select('tool, created_at, attachments')
.eq('user_id', userId)

// Get file uploads
const { data: uploads } = await supabaseServer
.from('file_uploads')
.select('created_at')
.eq('user_id', userId)

// Calculate stats
const totalChats = chats?.length || 0
const totalUploads = uploads?.length || 0
const totalUploads = chats?.reduce((sum: number, chat: any) => {
if (Array.isArray(chat.attachments)) {
return sum + chat.attachments.length
}
return sum
}, 0) || 0

// Group chats by tool
const chatsByTool: { [key: string]: number } = {}
chats?.forEach((chat: any) => {
const tool = chat.tool || 'Universal'
Expand All @@ -45,24 +40,22 @@ export async function GET(request: NextRequest) {

const mostUsedTool = Object.entries(chatsByTool).sort((a, b) => b[1] - a[1])[0]?.[0] || 'Universal'

// Calculate daily activity (last 7 days)
const dailyActivity: { date: string; chats: number }[] = []
for (let i = 6; i >= 0; i--) {
const date = new Date()
date.setDate(date.getDate() - i)
const dateStr = date.toISOString().split('T')[0]

const count = chats?.filter((c: any) =>
const count = chats?.filter((c: any) =>
c.created_at?.startsWith(dateStr)
).length || 0

dailyActivity.push({
date: dateStr,
chats: count
chats: count,
})
}

// Average response time (mock for now)
const avgResponseTime = 2.5

return NextResponse.json({
Expand All @@ -71,7 +64,7 @@ export async function GET(request: NextRequest) {
mostUsedTool,
avgResponseTime,
chatsByTool,
dailyActivity
dailyActivity,
})
} catch (error) {
console.error('Analytics error:', error)
Expand Down
136 changes: 126 additions & 10 deletions app/history/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,52 @@ interface ChatSession {
tool?: string
}

function escapeHtml(value: string) {
return value
.replaceAll('&', '&')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;')
}

function buildExportRows(conversations: ChatSession[]) {
return conversations.map((conversation) => ({
sessionId: conversation.session_id,
title: conversation.title || 'Untitled chat',
message: conversation.last_message || '',
tool: conversation.tool || 'Chat',
date: new Date(conversation.created_at).toLocaleString(),
}))
}

function buildExportTable(rows: ReturnType<typeof buildExportRows>) {
return `
<table>
<thead>
<tr>
<th>Session</th>
<th>Title</th>
<th>Tool</th>
<th>Message</th>
<th>Date</th>
</tr>
</thead>
<tbody>
${rows.map((row) => `
<tr>
<td>${escapeHtml(row.sessionId)}</td>
<td>${escapeHtml(row.title)}</td>
<td>${escapeHtml(row.tool)}</td>
<td>${escapeHtml(row.message)}</td>
<td>${escapeHtml(row.date)}</td>
</tr>
`).join('')}
</tbody>
</table>
`
}

export default function HistoryPage() {
const { user } = useAuth()
const [conversations, setConversations] = useState<ChatSession[]>([])
Expand Down Expand Up @@ -48,14 +94,8 @@ export default function HistoryPage() {
fetchHistory()
}, [fetchHistory])

const handleExport = () => {
const data = conversations.map((conversation) => ({
sessionId: conversation.session_id,
title: conversation.title,
message: conversation.last_message,
tool: conversation.tool,
date: conversation.created_at,
}))
const handleExportJson = () => {
const data = buildExportRows(conversations)

const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
Expand All @@ -68,14 +108,84 @@ export default function HistoryPage() {
URL.revokeObjectURL(url)
}

const handleExportWord = () => {
const rows = buildExportRows(conversations)
const html = `
<html>
<head>
<meta charset="utf-8" />
<title>Tera history export</title>
<style>
body { font-family: Arial, sans-serif; padding: 24px; color: #111; }
h1 { font-size: 24px; margin: 0 0 16px; }
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th, td { border: 1px solid #ccc; padding: 8px; vertical-align: top; text-align: left; }
th { background: #f5f5f5; }
</style>
</head>
<body>
<h1>Tera chat history</h1>
${buildExportTable(rows)}
</body>
</html>
`

const blob = new Blob([html], { type: 'application/msword' })
const url = URL.createObjectURL(blob)
const anchor = document.createElement('a')
anchor.href = url
anchor.download = `tera-history-${new Date().toISOString().split('T')[0]}.doc`
document.body.appendChild(anchor)
anchor.click()
document.body.removeChild(anchor)
URL.revokeObjectURL(url)
}

const handleExportPdf = () => {
const rows = buildExportRows(conversations)
const printWindow = window.open('', '_blank', 'noopener,noreferrer,width=1100,height=900')

if (!printWindow) {
alert('Popup blocked. Allow popups to export PDF.')
return
}

const html = `
<html>
<head>
<meta charset="utf-8" />
<title>Tera history export</title>
<style>
@page { margin: 18mm; }
body { font-family: Arial, sans-serif; padding: 0; color: #111; }
h1 { font-size: 24px; margin: 0 0 16px; }
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th, td { border: 1px solid #ccc; padding: 8px; vertical-align: top; text-align: left; }
th { background: #f5f5f5; }
</style>
</head>
<body>
<h1>Tera chat history</h1>
${buildExportTable(rows)}
</body>
</html>
`

printWindow.document.open()
printWindow.document.write(html)
printWindow.document.close()
printWindow.focus()
setTimeout(() => printWindow.print(), 300)
}

return (
<div className="tera-page">
<div className="tera-page-shell pt-24 md:pt-10">
<div className="tera-page-header">
<div>
<p className="tera-eyebrow">Workspace</p>
<h1 className="tera-title mt-3">Chat history</h1>
<p className="tera-subtitle mt-4">Search recent sessions, reopen previous conversations, or export a clean JSON archive.</p>
<p className="tera-subtitle mt-4">Search recent sessions, reopen previous conversations, or export JSON, Word, or PDF archives.</p>
</div>
<div className="flex flex-wrap items-center gap-3">
<input
Expand All @@ -88,9 +198,15 @@ export default function HistoryPage() {
}}
className="tera-input min-w-[16rem]"
/>
<button type="button" onClick={handleExport} className="tera-button-secondary">
<button type="button" onClick={handleExportJson} className="tera-button-secondary">
Export JSON
</button>
<button type="button" onClick={handleExportWord} className="tera-button-secondary">
Export Word
</button>
<button type="button" onClick={handleExportPdf} className="tera-button-secondary">
Export PDF
</button>
</div>
</div>

Expand Down
Loading