<!DOCTYPE html>
<html lang="en">
{{template "views/partials/head" .}}

<body class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">
<div class="app-layout">
  {{template "views/partials/navbar" .}}

  <main class="main-content">
    <div class="main-content-inner" x-data="tracesApp()" x-init="init()">

    <!-- Notifications -->
    <div class="fixed top-20 right-4 z-50 space-y-2" style="max-width: 400px;">
        <template x-for="notification in notifications" :key="notification.id">
            <div x-show="true"
                 x-transition:enter="transition ease-out duration-200"
                 x-transition:enter-start="opacity-0"
                 x-transition:enter-end="opacity-100"
                 x-transition:leave="transition ease-in duration-150"
                 x-transition:leave-start="opacity-100"
                 x-transition:leave-end="opacity-0"
                 :class="notification.type === 'error' ? 'bg-red-500' : 'bg-green-500'"
                 class="rounded-lg p-4 text-white flex items-start space-x-3">
                <div class="flex-shrink-0">
                    <i :class="notification.type === 'error' ? 'fas fa-exclamation-circle' : 'fas fa-check-circle'" class="text-xl"></i>
                </div>
                <div class="flex-1 min-w-0">
                    <p class="text-sm font-medium break-words" x-text="notification.message"></p>
                </div>
                <button @click="dismissNotification(notification.id)" class="flex-shrink-0 text-white hover:opacity-80 transition-opacity">
                    <i class="fas fa-times"></i>
                </button>
            </div>
        </template>
    </div>

    <div class="container mx-auto px-4 py-8 flex-grow">

        <!-- Hero Header -->
        <div class="hero-section">
            <div class="hero-content">
                <h1 class="hero-title">
                    Traces
                </h1>
                <p class="hero-subtitle">View logged API requests, responses, and backend operations</p>
                <div class="flex flex-wrap justify-center gap-2" x-show="activeTab === 'api'">
                    <button type="button" @click="clearTraces()" class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors">
                        <i class="fas fa-trash text-[10px]"></i>
                        <span>Clear Traces</span>
                    </button>
                    <a href="/api/traces" download="traces.json" class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors">
                        <i class="fas fa-download text-[10px]"></i>
                        <span>Export Traces</span>
                    </a>
                </div>
                <div class="flex flex-wrap justify-center gap-2" x-show="activeTab === 'backend'">
                    <button type="button" @click="clearBackendTraces()" class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors">
                        <i class="fas fa-trash text-[10px]"></i>
                        <span>Clear Backend Traces</span>
                    </button>
                    <a href="/api/backend-traces" download="backend-traces.json" class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors">
                        <i class="fas fa-download text-[10px]"></i>
                        <span>Export Backend Traces</span>
                    </a>
                </div>
            </div>
        </div>

        <!-- Tab Bar -->
        <div class="flex border-b border-[var(--color-border-subtle)] mb-6">
            <button @click="switchTab('api')"
                    :class="activeTab === 'api' ? 'border-[var(--color-primary)] text-[var(--color-primary)]' : 'border-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'"
                    class="px-4 py-2 text-sm font-medium border-b-2 transition-colors">
                <i class="fas fa-exchange-alt mr-1.5 text-xs"></i>API Traces
                <span class="ml-1 text-xs opacity-70" x-text="'(' + traces.length + ')'"></span>
            </button>
            <button @click="switchTab('backend')"
                    :class="activeTab === 'backend' ? 'border-[var(--color-primary)] text-[var(--color-primary)]' : 'border-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'"
                    class="px-4 py-2 text-sm font-medium border-b-2 transition-colors">
                <i class="fas fa-cogs mr-1.5 text-xs"></i>Backend Traces
                <span class="ml-1 text-xs opacity-70" x-text="'(' + backendTraces.length + ')'"></span>
            </button>
        </div>

        <!-- Tracing Settings -->
        <div class="bg-[var(--color-bg-secondary)] border border-[var(--color-primary)]/20 rounded-lg p-6 mb-8">
            <h2 class="text-xl font-semibold text-[var(--color-text-primary)] mb-4 flex items-center">
                <i class="fas fa-bug mr-2 text-[var(--color-primary)] text-sm"></i>
                Tracing Settings
            </h2>
            <p class="text-xs text-[var(--color-text-secondary)] mb-4">Configure API and backend tracing</p>

            <div class="space-y-4">
                <!-- Enable Tracing -->
                <div class="flex items-center justify-between">
                    <div>
                        <label class="text-sm font-medium text-[var(--color-text-primary)]">Enable Tracing</label>
                        <p class="text-xs text-[var(--color-text-secondary)] mt-1">Enable tracing of requests and responses</p>
                    </div>
                    <label class="relative inline-flex items-center cursor-pointer">
                        <input type="checkbox" x-model="settings.enable_tracing"
                               @change="updateTracingEnabled()"
                               class="sr-only peer">
                        <div class="w-11 h-6 bg-[var(--color-bg-primary)] peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[var(--color-primary-light)] rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-[var(--color-border-subtle)] after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[var(--color-primary)]"></div>
                    </label>
                </div>

                <!-- Tracing Max Items -->
                <div>
                    <label class="block text-sm font-medium text-[var(--color-text-primary)] mb-2">Tracing Max Items</label>
                    <p class="text-xs text-[var(--color-text-secondary)] mb-2">Maximum number of tracing items to keep (0 = unlimited)</p>
                    <input type="number" x-model="settings.tracing_max_items"
                           min="0"
                           placeholder="1000"
                           :disabled="!settings.enable_tracing"
                           class="w-full px-3 py-2 bg-[var(--color-bg-primary)] border border-[var(--color-primary)]/20 rounded text-sm text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/50"
                           :class="!settings.enable_tracing ? 'opacity-50 cursor-not-allowed' : ''">
                </div>

                <!-- Save Button -->
                <div class="flex justify-end pt-2">
                    <button type="button" @click="saveTracingSettings()"
                            :disabled="saving"
                            class="inline-flex items-center gap-1.5 text-xs text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] bg-transparent hover:bg-[var(--color-primary)]/10 border border-[var(--color-border-subtle)] hover:border-[var(--color-primary)]/30 rounded-md py-1.5 px-2.5 transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border-subtle)]">
                        <i class="fas fa-save text-[10px]" :class="saving ? 'fa-spin fa-spinner' : ''"></i>
                        <span x-text="saving ? 'Saving...' : 'Save Settings'"></span>
                    </button>
                </div>
            </div>
        </div>

        <!-- API Traces Table -->
        <div class="mt-8" x-show="activeTab === 'api'">
            <div class="overflow-x-auto">
                <table class="w-full border-collapse">
                    <thead>
                        <tr class="border-b border-[var(--color-bg-secondary)]">
                            <th class="w-8 p-2"></th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Method</th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Path</th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Status</th>
                        </tr>
                    </thead>
                    <template x-for="(trace, index) in traces" :key="index">
                        <tbody>
                            <tr @click="toggleTrace(index)"
                                class="cursor-pointer hover:bg-[var(--color-bg-secondary)]/50 border-b border-[var(--color-bg-secondary)] transition-colors">
                                <td class="p-2 w-8 text-center">
                                    <i class="fas fa-chevron-right text-xs text-[var(--color-text-secondary)] transition-transform duration-200"
                                       :class="expandedTraces[index] ? 'rotate-90' : ''"></i>
                                </td>
                                <td class="p-2" x-text="trace.request.method"></td>
                                <td class="p-2" x-text="trace.request.path"></td>
                                <td class="p-2" x-text="trace.response.status"></td>
                            </tr>
                            <tr x-show="expandedTraces[index]">
                                <td colspan="4" class="p-0">
                                    <div class="p-4 bg-[var(--color-bg-secondary)]/30 border-b border-[var(--color-bg-secondary)]">
                                        <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
                                            <div>
                                                <h4 class="text-sm font-semibold text-[var(--color-text-primary)] mb-2">Request Body</h4>
                                                <pre class="overflow-auto max-h-[70vh] p-3 rounded-lg bg-[var(--color-bg-primary)] border border-[var(--color-border-subtle)] text-xs font-mono text-[var(--color-text-secondary)] whitespace-pre-wrap break-words"
                                                     x-text="formatTraceBody(trace.request.body)"></pre>
                                            </div>
                                            <div>
                                                <h4 class="text-sm font-semibold text-[var(--color-text-primary)] mb-2">Response Body</h4>
                                                <pre class="overflow-auto max-h-[70vh] p-3 rounded-lg bg-[var(--color-bg-primary)] border border-[var(--color-border-subtle)] text-xs font-mono text-[var(--color-text-secondary)] whitespace-pre-wrap break-words"
                                                     x-text="formatTraceBody(trace.response.body)"></pre>
                                            </div>
                                        </div>
                                    </div>
                                </td>
                            </tr>
                        </tbody>
                    </template>
                </table>
                <div x-show="traces.length === 0" class="text-center py-8 text-[var(--color-text-secondary)] text-sm">
                    No API traces recorded yet.
                </div>
            </div>
        </div>

        <!-- Backend Traces Table -->
        <div class="mt-8" x-show="activeTab === 'backend'">
            <div class="overflow-x-auto">
                <table class="w-full border-collapse">
                    <thead>
                        <tr class="border-b border-[var(--color-bg-secondary)]">
                            <th class="w-8 p-2"></th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Type</th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Timestamp</th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Model</th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Summary</th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Duration</th>
                            <th class="text-left p-2 text-xs font-semibold text-[var(--color-text-secondary)]">Status</th>
                        </tr>
                    </thead>
                    <template x-for="(trace, index) in backendTraces" :key="index">
                        <tbody>
                            <tr @click="toggleBackendTrace(index)"
                                class="cursor-pointer hover:bg-[var(--color-bg-secondary)]/50 border-b border-[var(--color-bg-secondary)] transition-colors">
                                <td class="p-2 w-8 text-center">
                                    <i class="fas fa-chevron-right text-xs text-[var(--color-text-secondary)] transition-transform duration-200"
                                       :class="expandedBackendTraces[index] ? 'rotate-90' : ''"></i>
                                </td>
                                <td class="p-2">
                                    <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
                                          :class="getTypeClass(trace.type)"
                                          x-text="trace.type"></span>
                                </td>
                                <td class="p-2 text-xs text-[var(--color-text-secondary)]" x-text="formatTimestamp(trace.timestamp)"></td>
                                <td class="p-2 text-sm" x-text="trace.model_name || '-'"></td>
                                <td class="p-2 text-sm max-w-xs truncate" x-text="trace.summary || '-'"></td>
                                <td class="p-2 text-xs text-[var(--color-text-secondary)]" x-text="formatDuration(trace.duration)"></td>
                                <td class="p-2">
                                    <template x-if="!trace.error">
                                        <i class="fas fa-check-circle text-green-500 text-xs"></i>
                                    </template>
                                    <template x-if="trace.error">
                                        <i class="fas fa-times-circle text-red-500 text-xs" :title="trace.error"></i>
                                    </template>
                                </td>
                            </tr>
                            <tr x-show="expandedBackendTraces[index]">
                                <td colspan="7" class="p-0">
                                    <div class="p-4 bg-[var(--color-bg-secondary)]/30 border-b border-[var(--color-bg-secondary)]">
                                        <!-- Header info -->
                                        <div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
                                            <div class="bg-[var(--color-bg-primary)] rounded-lg p-3 border border-[var(--color-border-subtle)]">
                                                <div class="text-xs text-[var(--color-text-secondary)] mb-1">Type</div>
                                                <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
                                                      :class="getTypeClass(trace.type)"
                                                      x-text="trace.type"></span>
                                            </div>
                                            <div class="bg-[var(--color-bg-primary)] rounded-lg p-3 border border-[var(--color-border-subtle)]">
                                                <div class="text-xs text-[var(--color-text-secondary)] mb-1">Model</div>
                                                <div class="text-sm font-medium" x-text="trace.model_name || '-'"></div>
                                            </div>
                                            <div class="bg-[var(--color-bg-primary)] rounded-lg p-3 border border-[var(--color-border-subtle)]">
                                                <div class="text-xs text-[var(--color-text-secondary)] mb-1">Backend</div>
                                                <div class="text-sm font-medium" x-text="trace.backend || '-'"></div>
                                            </div>
                                            <div class="bg-[var(--color-bg-primary)] rounded-lg p-3 border border-[var(--color-border-subtle)]">
                                                <div class="text-xs text-[var(--color-text-secondary)] mb-1">Duration</div>
                                                <div class="text-sm font-medium" x-text="formatDuration(trace.duration)"></div>
                                            </div>
                                        </div>

                                        <!-- Error banner -->
                                        <div x-show="trace.error" class="bg-red-500/10 border border-red-500/30 rounded-lg p-3 mb-4">
                                            <div class="flex items-center gap-2">
                                                <i class="fas fa-exclamation-triangle text-red-500 text-sm"></i>
                                                <span class="text-sm text-red-400" x-text="trace.error"></span>
                                            </div>
                                        </div>

                                        <!-- Data fields as nested accordions -->
                                        <template x-if="trace.data && Object.keys(trace.data).length > 0">
                                            <div>
                                                <h4 class="text-sm font-semibold text-[var(--color-text-primary)] mb-2">Data Fields</h4>
                                                <div class="border border-[var(--color-border-subtle)] rounded-lg overflow-hidden">
                                                    <template x-for="[key, value] in Object.entries(trace.data)" :key="key">
                                                        <div class="border-b border-[var(--color-border-subtle)] last:border-b-0">
                                                            <!-- Field header row -->
                                                            <div @click="isLargeValue(value) && toggleBackendField(index, key)"
                                                                 class="flex items-center gap-2 px-3 py-2 hover:bg-[var(--color-bg-primary)]/50 transition-colors"
                                                                 :class="isLargeValue(value) ? 'cursor-pointer' : ''">
                                                                <template x-if="isLargeValue(value)">
                                                                    <i class="fas fa-chevron-right text-[10px] text-[var(--color-text-secondary)] transition-transform duration-200 w-3 flex-shrink-0"
                                                                       :class="isBackendFieldExpanded(index, key) ? 'rotate-90' : ''"></i>
                                                                </template>
                                                                <template x-if="!isLargeValue(value)">
                                                                    <span class="w-3 flex-shrink-0"></span>
                                                                </template>
                                                                <span class="text-sm font-mono text-[var(--color-primary)] flex-shrink-0" x-text="key"></span>
                                                                <template x-if="!isLargeValue(value)">
                                                                    <span class="font-mono text-xs text-[var(--color-text-secondary)]" x-text="formatValue(value)"></span>
                                                                </template>
                                                                <template x-if="isLargeValue(value) && !isBackendFieldExpanded(index, key)">
                                                                    <span class="text-xs text-[var(--color-text-secondary)] truncate" x-text="truncateValue(value, 120)"></span>
                                                                </template>
                                                            </div>
                                                            <!-- Expanded field value -->
                                                            <div x-show="isLargeValue(value) && isBackendFieldExpanded(index, key)"
                                                                 class="px-3 pb-3">
                                                                <pre class="overflow-auto max-h-[70vh] p-3 rounded-lg bg-[var(--color-bg-primary)] border border-[var(--color-border-subtle)] text-xs font-mono text-[var(--color-text-secondary)] whitespace-pre-wrap break-words"
                                                                     x-text="formatLargeValue(value)"></pre>
                                                            </div>
                                                        </div>
                                                    </template>
                                                </div>
                                            </div>
                                        </template>
                                    </div>
                                </td>
                            </tr>
                        </tbody>
                    </template>
                </table>
                <div x-show="backendTraces.length === 0" class="text-center py-8 text-[var(--color-text-secondary)] text-sm">
                    No backend traces recorded yet.
                </div>
            </div>
        </div>

    </div>


</div>

<script>
function tracesApp() {
    return {
        activeTab: 'api',
        traces: [],
        backendTraces: [],
        expandedTraces: {},
        expandedBackendTraces: {},
        expandedBackendFields: {},
        notifications: [],
        settings: {
            enable_tracing: false,
            tracing_max_items: 0
        },
        saving: false,
        refreshInterval: null,

        init() {
            this.loadTracingSettings();
            this.fetchTraces();
            this.fetchBackendTraces();
            this.startAutoRefresh();
        },

        switchTab(tab) {
            this.activeTab = tab;
        },

        startAutoRefresh() {
            if (this.refreshInterval) clearInterval(this.refreshInterval);
            this.refreshInterval = setInterval(() => {
                if (this.activeTab === 'api') {
                    this.fetchTraces();
                } else {
                    this.fetchBackendTraces();
                }
            }, 5000);
        },

        async loadTracingSettings() {
            try {
                const response = await fetch('/api/settings');
                const data = await response.json();

                if (response.ok) {
                    this.settings.enable_tracing = data.enable_tracing || false;
                    this.settings.tracing_max_items = data.tracing_max_items || 0;
                } else {
                    this.addNotification('Failed to load tracing settings: ' + (data.error || 'Unknown error'), 'error');
                }
            } catch (error) {
                console.error('Error loading tracing settings:', error);
                this.addNotification('Failed to load tracing settings: ' + error.message, 'error');
            }
        },

        updateTracingEnabled() {
            if (!this.settings.enable_tracing) {
                this.settings.tracing_max_items = 0;
            }
        },

        async saveTracingSettings() {
            if (this.saving) return;

            this.saving = true;

            try {
                const payload = {
                    enable_tracing: this.settings.enable_tracing,
                    tracing_max_items: parseInt(this.settings.tracing_max_items) || 0
                };

                const response = await fetch('/api/settings', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(payload)
                });

                const data = await response.json();

                if (response.ok && data.success) {
                    this.addNotification('Tracing settings saved successfully!', 'success');
                } else {
                    this.addNotification('Failed to save tracing settings: ' + (data.error || 'Unknown error'), 'error');
                }
            } catch (error) {
                console.error('Error saving tracing settings:', error);
                this.addNotification('Failed to save tracing settings: ' + error.message, 'error');
            } finally {
                this.saving = false;
            }
        },

        addNotification(message, type = 'success') {
            const id = Date.now();
            this.notifications.push({ id, message, type });
            setTimeout(() => this.dismissNotification(id), 5000);
        },

        dismissNotification(id) {
            this.notifications = this.notifications.filter(n => n.id !== id);
        },

        async fetchTraces() {
            try {
                const response = await fetch('/api/traces');
                this.traces = await response.json();
            } catch (e) {
                console.error('Error fetching API traces:', e);
            }
        },

        async fetchBackendTraces() {
            try {
                const response = await fetch('/api/backend-traces');
                this.backendTraces = await response.json();
            } catch (e) {
                console.error('Error fetching backend traces:', e);
            }
        },

        async clearTraces() {
            if (confirm('Clear all API traces?')) {
                await fetch('/api/traces/clear', { method: 'POST' });
                this.traces = [];
                this.expandedTraces = {};
            }
        },

        async clearBackendTraces() {
            if (confirm('Clear all backend traces?')) {
                await fetch('/api/backend-traces/clear', { method: 'POST' });
                this.backendTraces = [];
                this.expandedBackendTraces = {};
                this.expandedBackendFields = {};
            }
        },

        toggleTrace(index) {
            this.expandedTraces = {
                ...this.expandedTraces,
                [index]: !this.expandedTraces[index]
            };
        },

        toggleBackendTrace(index) {
            this.expandedBackendTraces = {
                ...this.expandedBackendTraces,
                [index]: !this.expandedBackendTraces[index]
            };
        },

        toggleBackendField(index, key) {
            const fieldKey = index + '-' + key;
            this.expandedBackendFields = {
                ...this.expandedBackendFields,
                [fieldKey]: !this.expandedBackendFields[fieldKey]
            };
        },

        isBackendFieldExpanded(index, key) {
            return !!this.expandedBackendFields[index + '-' + key];
        },

        formatTraceBody(body) {
            try {
                const binaryString = atob(body);
                const bytes = new Uint8Array(binaryString.length);
                for (let i = 0; i < binaryString.length; i++) {
                    bytes[i] = binaryString.charCodeAt(i);
                }
                const text = new TextDecoder().decode(bytes);
                try {
                    return JSON.stringify(JSON.parse(text), null, 2);
                } catch {
                    return text;
                }
            } catch {
                return body || '';
            }
        },

        formatLargeValue(value) {
            if (typeof value === 'string') {
                try {
                    return JSON.stringify(JSON.parse(value), null, 2);
                } catch {
                    return value;
                }
            }
            if (typeof value === 'object') {
                return JSON.stringify(value, null, 2);
            }
            return String(value);
        },

        formatTimestamp(ts) {
            if (!ts) return '-';
            const d = new Date(ts);
            return d.toLocaleTimeString() + '.' + String(d.getMilliseconds()).padStart(3, '0');
        },

        formatDuration(ns) {
            if (!ns) return '-';
            const ms = ns / 1000000;
            if (ms < 1000) return ms.toFixed(1) + 'ms';
            return (ms / 1000).toFixed(2) + 's';
        },

        getTypeClass(type) {
            const classes = {
                'llm': 'bg-blue-500/20 text-blue-400',
                'embedding': 'bg-purple-500/20 text-purple-400',
                'transcription': 'bg-yellow-500/20 text-yellow-400',
                'image_generation': 'bg-green-500/20 text-green-400',
                'video_generation': 'bg-pink-500/20 text-pink-400',
                'tts': 'bg-orange-500/20 text-orange-400',
                'sound_generation': 'bg-teal-500/20 text-teal-400',
                'rerank': 'bg-indigo-500/20 text-indigo-400',
                'tokenize': 'bg-gray-500/20 text-gray-400',
            };
            return classes[type] || 'bg-gray-500/20 text-gray-400';
        },

        isLargeValue(value) {
            if (typeof value === 'string') return value.length > 120;
            if (typeof value === 'object') return JSON.stringify(value).length > 120;
            return false;
        },

        truncateValue(value, maxLen) {
            let str = typeof value === 'object' ? JSON.stringify(value) : String(value);
            if (str.length <= maxLen) return str;
            return str.substring(0, maxLen) + '...';
        },

        formatValue(value) {
            if (value === null || value === undefined) return 'null';
            if (typeof value === 'boolean') return value ? 'true' : 'false';
            if (typeof value === 'object') return JSON.stringify(value);
            return String(value);
        }
    }
}
</script>

    {{template "views/partials/footer" .}}
    </div>
  </main>
</div>

</body>
</html>
