UNPKG

signalk-parquet

Version:

SignalK plugin to save marine data directly to Parquet files with regimen-based control

964 lines (849 loc) 217 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SignalK Parquet Data Interface</title> <link rel="icon" type="image/png" href="parquet.png"> <script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background-color: #f5f5f5; color: #333; line-height: 1.6; } .container { max-width: 1400px; margin: 0 auto; padding: 20px; } .header { background: linear-gradient(135deg, #2BB5D6 0%, #013542 100%); color: white; padding: 20px 0; margin-bottom: 20px; border-radius: 10px; text-align: center; } .header h1 { font-size: 2.5rem; margin-bottom: 10px; } .header p { font-size: 1.1rem; opacity: 0.9; } .section { background: white; padding: 20px; margin-bottom: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .section h2 { color: #667eea; margin-bottom: 15px; font-size: 1.5rem; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: 500; color: #555; } input, select, textarea { width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px; font-size: 14px; transition: border-color 0.3s; } input:focus, select:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; } button { background: #667eea; color: white; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; transition: background-color 0.3s; margin-right: 10px; margin-bottom: 10px; } button:hover { background: #5a6fd8; } button:disabled { background: #ccc; cursor: not-allowed; } .btn-secondary { background: #6c757d; } .btn-secondary:hover { background: #5a6268; } .results { margin-top: 20px; } .table-container { overflow-x: auto; margin-top: 15px; } table { width: 100%; border-collapse: collapse; min-width: 600px; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; word-wrap: break-word; } th:nth-child(1), td:nth-child(1) { min-width: 180px; } /* Path */ th:nth-child(2), td:nth-child(2) { width: 60px; } /* Enabled */ th:nth-child(3), td:nth-child(3) { min-width: 100px; } /* Regimen */ th:nth-child(4), td:nth-child(4) { min-width: 120px; } /* Source */ th:nth-child(5), td:nth-child(5) { width: 90px; } /* Context */ th:nth-child(6), td:nth-child(6) { min-width: 120px; } /* Exclude MMSI */ th:nth-child(7), td:nth-child(7) { width: 140px; } /* Actions */ th { background-color: #f8f9fa; font-weight: 600; color: #495057; } tr:hover { background-color: #f8f9fa; } .loading { text-align: center; padding: 20px; color: #666; } .error { background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; padding: 10px; border-radius: 5px; margin-top: 10px; } .success { background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 10px; border-radius: 5px; margin-top: 10px; } .path-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; margin-top: 15px; } .path-item { background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #667eea; } .path-item h4 { margin-bottom: 5px; color: #495057; } .path-item p { font-size: 0.9rem; color: #666; } .path-item button { margin-top: 10px; padding: 5px 10px; font-size: 12px; } .query-examples { background: #f8f9fa; padding: 15px; border-radius: 5px; margin-top: 15px; } .query-examples h4 { margin-bottom: 10px; color: #495057; } .query-examples ul { list-style: none; padding: 0; } .query-examples li { margin-bottom: 10px; padding: 10px; background: white; border-radius: 3px; cursor: pointer; transition: background-color 0.3s; } .query-examples li:hover { background: #e9ecef; } .query-examples code { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.9rem; } .stats { display: flex; justify-content: space-between; margin-bottom: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px; } .stat-item { text-align: center; } .stat-value { font-size: 1.5rem; font-weight: bold; color: #667eea; } .stat-label { font-size: 0.9rem; color: #666; } .collapsible-header { cursor: pointer; display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; margin: -20px -20px 15px -20px; background: #f8f9fa; border-radius: 10px 10px 0 0; transition: background-color 0.3s; } .collapsible-header:hover { background: #e9ecef; } .collapsible-header h2 { margin: 0; } .collapse-icon { font-size: 1.2rem; transition: transform 0.3s; } .collapse-icon.collapsed { transform: rotate(-90deg); } .collapsible-content { overflow: hidden; transition: max-height 0.3s ease-out; } .collapsible-content.collapsed { max-height: 0; } .collapsible-content.expanded { max-height: 2000px; } /* Tab Navigation Styles */ .tab-navigation { display: flex; background: white; border-radius: 10px 10px 0 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 0; overflow-x: auto; } .tab-button { background: #f8f9fa; border: none; padding: 15px 20px; cursor: pointer; font-size: 14px; font-weight: 500; color: #666; border-radius: 0; transition: all 0.3s; white-space: nowrap; border-bottom: 3px solid transparent; margin: 0; } .tab-button:first-child { border-radius: 10px 0 0 0; } .tab-button:last-child { border-radius: 0 10px 0 0; } .tab-button:hover { background: #e9ecef; color: #333; } .tab-button.active { background: white; color: #667eea; border-bottom-color: #667eea; } .tab-content { background: white; border-radius: 0 0 10px 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 20px; } .tab-panel { display: none; } .tab-panel.active { display: block; } .tab-panel .section { margin-bottom: 0; box-shadow: none; border-radius: 0 0 10px 10px; } @media (max-width: 768px) { .container { padding: 10px; } .header h1 { font-size: 2rem; } .stats { flex-direction: column; gap: 10px; } .tab-navigation { flex-wrap: wrap; } .tab-button { flex: 1; min-width: 120px; padding: 12px 15px; font-size: 13px; } .tab-button:first-child, .tab-button:last-child { border-radius: 0; } } </style> </head> <body> <div class="container"> <div class="header"> <h1> <img src="parquet.png" alt="Parquet" style="height: 80px; vertical-align: middle; margin-right: 10px; border-radius: 5px;"> SignalK Parquet Data Store</h1> <p>Export, query, and explore your SignalK data archived as Parquet files</p> </div> <!-- Tab Navigation --> <div class="tab-navigation"> <button class="tab-button active" onclick="showTab('pathConfig')">⚙️ Path Configuration</button> <button class="tab-button" onclick="showTab('commandManager')">🎮 Command Manager</button> <button class="tab-button" onclick="showTab('liveConnections')" style="display: none;">🔴 Live Connections</button> <button class="tab-button" onclick="showTab('dataPaths')">📁 Available Data Paths</button> <button class="tab-button" onclick="showTab('customQuery')">🔍 Custom Query</button> <button class="tab-button" onclick="showTab('aiAnalysis')">🧠 AI Analysis<br><small>EXPERIMENTAL</small></button> <button class="tab-button" onclick="showTab('s3Config')">☁️ Cloud Status</button> </div> <!-- Tab Content Panels --> <div class="tab-content"> <!-- Path Configuration Tab --> <div id="pathConfig" class="tab-panel active"> <div class="section"> <h2>⚙️ Path Configuration</h2> <p>Manage which SignalK paths to collect data from:</p> <button onclick="loadPathConfigurations()">🔄 Refresh Paths</button> <button onclick="showAddPathForm()">➕ Add New Path</button> <button id="toggleCommandsBtn" onclick="toggleCommandPaths()">👁️ Show Commands</button> <!-- Add Path Form (hidden by default) --> <div id="addPathForm" style="display: none; margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 5px; min-height: 400px; overflow: visible;"> <h4>Add New Path Configuration</h4> <div class="form-group"> <label for="pathSignalK">SignalK Path:</label> <input type="text" id="pathSignalK" placeholder="e.g., navigation.position"> </div> <div class="form-group"> <label for="pathEnabled" style="display: inline !important; margin-bottom: 0 !important;">Always Enabled:</label> <input type="checkbox" id="pathEnabled" style="display: inline !important; width: auto !important; margin-left: 10px;"> </div> <div class="form-group"> <label for="pathRegimen">Regimen Control:</label> <input type="text" id="pathRegimen" placeholder="e.g., captureWeather, capturePassage"> </div> <div class="form-group"> <label for="pathContext">Context:</label> <input type="text" id="pathContext" placeholder="e.g., vessels.self or vessels.*" value="vessels.self"> </div> <div class="form-group"> <label for="pathSource">Source Filter (optional):</label> <input type="text" id="pathSource" placeholder="e.g., mqtt-weatherflow-udp"> <small style="color: #666; display: block; margin-top: 5px;"> Filter by specific data source. Leave empty to accept all sources. </small> </div> <div class="form-group"> <label for="pathExcludeMMSI">Exclude MMSI (for vessels.* context):</label> <input type="text" id="pathExcludeMMSI" placeholder="e.g., 123456789, 987654321 (comma-separated)"> <small style="color: #666; display: block; margin-top: 5px;"> Only applicable when context is "vessels.*". Leave empty to include all vessels. </small> </div> <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd; min-height: 60px; display: flex; gap: 10px; align-items: center;"> <button onclick="addPathConfiguration()">✅ Add Path</button> <button class="btn-secondary" onclick="hideAddPathForm()">❌ Cancel</button> </div> </div> <div id="pathConfigContainer"> <div class="loading">Loading path configurations...</div> </div> </div> </div> <!-- Command Manager Tab --> <div id="commandManager" class="tab-panel"> <div class="section"> <h2>🎮 Command Manager</h2> <p>Register and manage SignalK commands for remote control:</p> <button onclick="loadCommands()">🔄 Refresh Commands</button> <button onclick="showAddCommandForm()">➕ Add New Command</button> <!-- Add Command Form (hidden by default) --> <div id="addCommandForm" style="display: none; margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 5px;"> <h4>Register New Command</h4> <div class="form-group"> <label for="commandName">Command Name:</label> <input type="text" id="commandName" placeholder="e.g., captureWeather, startLogging"> </div> <div class="form-group"> <label for="commandDescription">Description (optional):</label> <input type="text" id="commandDescription" placeholder="e.g., Start weather data capture"> </div> <div class="form-group"> <label for="commandKeywords">Keywords (for Claude context matching):</label> <input type="text" id="commandKeywords" placeholder="e.g., anchor, anchoring, drag, swing"> <small style="color: #666; font-size: 11px;">Separate multiple keywords with commas</small> </div> <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;"> <button onclick="registerCommand()">✅ Register Command</button> <button class="btn-secondary" onclick="hideAddCommandForm()">❌ Cancel</button> </div> </div> <!-- Edit Command Form (hidden by default) --> <div id="editCommandForm" style="display: none; margin: 20px 0; padding: 20px; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 5px;"> <h4>Edit Command</h4> <div class="form-group"> <label for="editCommandName">Command Name:</label> <input type="text" id="editCommandName" readonly style="background: #f8f9fa;"> </div> <div class="form-group"> <label for="editCommandDescription">Description:</label> <input type="text" id="editCommandDescription" placeholder="e.g., Start weather data capture"> </div> <div class="form-group"> <label for="editCommandKeywords">Keywords (for Claude context matching):</label> <input type="text" id="editCommandKeywords" placeholder="e.g., anchor, anchoring, drag, swing"> <small style="color: #666; font-size: 11px;">Separate multiple keywords with commas</small> </div> <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;"> <button onclick="updateCommand()">✅ Update Command</button> <button class="btn-secondary" onclick="hideEditCommandForm()">❌ Cancel</button> </div> </div> <div id="commandContainer"> <div class="loading">Loading commands...</div> </div> <!-- Command History --> <div style="margin-top: 30px;"> <h3>📋 Command History</h3> <button onclick="loadCommandHistory()">🔄 Refresh History</button> <div id="commandHistoryContainer" style="margin-top: 15px;"> <div class="loading">Loading command history...</div> </div> </div> </div> </div> <!-- Live Connections Tab --> <div id="liveConnections" class="tab-panel"> <div class="section" style="display: none;"> <h2>🔴 Live Connections - DISABLED</h2> <p>Streaming functionality has been disabled.</p> <div style="margin-bottom: 20px;"> <button onclick="loadStreams()">🔄 Refresh Streams</button> <button onclick="showAddStreamForm()">➕ Create New Stream</button> </div> <!-- Add Stream Form (hidden by default) --> <div id="addStreamForm" style="display: none; margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 5px;"> <h4>Create New Historical Stream</h4> <div class="form-group"> <label for="streamName">Stream Name:</label> <input type="text" id="streamName" placeholder="e.g., Navigation History"> </div> <div class="form-group"> <label for="streamPath">SignalK Path:</label> <div style="display: flex; gap: 10px; align-items: center;"> <select id="streamPath" style="flex: 1;"> <option value="">Loading available paths...</option> </select> <button type="button" onclick="refreshSignalKPaths()" style="padding: 8px; background: #007bff; color: white; border: none; border-radius: 3px;" title="Refresh path list">🔄</button> </div> <small style="color: #666; display: block; margin-top: 5px;"> Available paths from historical data. Click refresh if you don't see your path. </small> </div> <div class="form-group"> <label for="streamTimeRange">Time Range:</label> <select id="streamTimeRange"> <option value="1h">Last 1 Hour</option> <option value="6h">Last 6 Hours</option> <option value="24h">Last 24 Hours</option> <option value="7d">Last 7 Days</option> <option value="30d">Last 30 Days</option> <option value="custom">Custom Range</option> </select> </div> <div class="form-group" id="customTimeRange" style="display: none;"> <label>Custom Time Range:</label> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <input type="datetime-local" id="streamStartTime" placeholder="Start time"> <input type="datetime-local" id="streamEndTime" placeholder="End time"> </div> </div> <div class="form-group"> <label for="streamResolution">Data Resolution:</label> <select id="streamResolution"> <option value="1000">1 second</option> <option value="5000">5 seconds</option> <option value="15000">15 seconds</option> <option value="30000" selected>30 seconds</option> <option value="60000">1 minute</option> <option value="300000">5 minutes</option> </select> </div> <div class="form-group"> <label for="streamRate">Streaming Rate:</label> <select id="streamRate"> <option value="100">Fast (100ms intervals)</option> <option value="500" selected>Normal (500ms intervals)</option> <option value="1000">Slow (1 second intervals)</option> <option value="5000">Very Slow (5 second intervals)</option> </select> </div> <div class="form-group"> <label for="streamAggregateMethod">Statistical Function:</label> <select id="streamAggregateMethod"> <option value="average" selected>Average - arithmetic mean of values</option> <option value="min">Minimum - lowest value in time window</option> <option value="max">Maximum - highest value in time window</option> <option value="first">First - earliest value in time window</option> <option value="last">Last - most recent value in time window</option> <option value="mid">Median - middle value when sorted</option> <option value="middle_index">Middle Index - middle value by position</option> </select> <small style="color: #666; display: block; margin-top: 5px;"> Statistical function applied to data within each time window </small> </div> <div class="form-group"> <label for="streamWindowSize">Rolling Window Size:</label> <select id="streamWindowSize"> <option value="5">5 data points</option> <option value="10" selected>10 data points</option> <option value="20">20 data points</option> <option value="50">50 data points</option> <option value="100">100 data points</option> </select> <small style="color: #666; display: block; margin-top: 5px;"> Number of recent values used for real-time statistical calculations </small> </div> <div class="form-group"> <label for="streamAutoStart">Auto-start stream:</label> <input type="checkbox" id="streamAutoStart" checked> <small style="color: #666; display: block; margin-top: 5px;"> Start streaming immediately after creation </small> </div> <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid #ddd;"> <button onclick="createStream()">✅ Create Stream</button> <button class="btn-secondary" onclick="hideAddStreamForm()">❌ Cancel</button> </div> </div> <!-- Active Streams Display --> <div id="streamsContainer"> <div class="loading">Loading streams...</div> </div> <!-- Stream Statistics --> <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;"> <h3>📊 Stream Statistics</h3> <div id="streamStats" style="margin-top: 15px;"> <div class="loading">Loading statistics...</div> </div> </div> <!-- Live Data Display --> <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;"> <h3>📡 Live Data Stream</h3> <p style="margin-bottom: 15px;">Real-time time-bucketed statistical streaming data (newest first, max 100 entries):</p> <!-- Data Interpretation Guide --> <div style="background: #f0f8ff; padding: 15px; border-radius: 5px; margin-bottom: 15px; font-size: 13px;"> <strong>📊 How to Read This Data:</strong><br/> • <strong>📊 INITIAL</strong>: Full time window data (first load with many buckets)<br/> • <strong>📈 INCREMENTAL</strong>: New data points since last update (sliding window - fewer buckets)<br/> • <strong>Statistical Method</strong>: MAX, AVG, MIN, FIRST, LAST, MID applied to each time bucket<br/> • <strong>Buckets</strong>: Number of time intervals processed (e.g., 30-second time buckets)<br/> • <strong>Value</strong>: Result of statistical calculation for that time bucket<br/> • <strong>EMA/SMA</strong>: Moving averages (10-period) calculated for numeric values - EMA responds faster to changes<br/> • <strong>Time</strong>: Timestamp of the time bucket (not delivery time) </div> <div style="max-height: 400px; overflow-y: auto; border: 1px solid #ddd; border-radius: 5px;"> <table id="liveDataTable" style="width: 100%; border-collapse: collapse; font-size: 12px;"> <thead style="position: sticky; top: 0; background: #f8f9fa; z-index: 1;"> <tr> <th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Bucket Time</th> <th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Stream Type & Statistical Method</th> <th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Path</th> <th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">Statistical Value</th> <th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">EMA (10)</th> <th style="padding: 8px; border-bottom: 1px solid #ddd; text-align: left;">SMA (10)</th> </tr> </thead> <tbody id="liveDataBody"> <tr> <td colspan="6" style="padding: 20px; text-align: center; color: #666;"> No data streaming yet. Start a stream to see live data. </td> </tr> </tbody> </table> </div> <div style="margin-top: 10px; font-size: 11px; color: #666;"> <span id="liveDataCount">0</span> time-bucketed entries • <button onclick="clearLiveData()" style="font-size: 11px; padding: 2px 8px;">🗑️ Clear</button> <button onclick="toggleLiveDataPause()" id="pauseDataBtn" style="font-size: 11px; padding: 2px 8px; margin-left: 5px;">⏸️ Pause</button> <button onclick="showDataSummary()" style="font-size: 11px; padding: 2px 8px; margin-left: 5px;">📊 Summary</button> </div> <!-- Data Summary Panel --> <div id="dataSummaryPanel" style="display: none; margin-top: 15px; padding: 15px; background: #f8f9fa; border-radius: 5px;"> <h4>📊 Data Summary & Trends</h4> <div id="dataSummaryContent"></div> </div> </div> </div> </div> <!-- AI Analysis Tab --> <div id="aiAnalysis" class="tab-panel"> <div class="section"> <h2>🧠 AI Analysis with Claude</h2><small>EXPERIMENTAL</small> <p>Analyze your maritime data using Claude AI for insights, patterns, and recommendations.</p> <!-- Connection Test --> <div style="margin-bottom: 20px;"> <button onclick="testClaudeConnection()" id="testConnectionBtn">🔗 Test Claude Connection</button> <div id="claudeConnectionResult" style="margin-top: 10px;"></div> </div> <!-- Vessel Context Section --> <div style="margin-bottom: 30px; border: 2px solid #e3f2fd; border-radius: 8px; background: #fafbfc;"> <div onclick="toggleVesselContext()" style="padding: 15px 20px; cursor: pointer; display: flex; align-items: center; justify-content: space-between; user-select: none;"> <div> <h3 style="margin: 0;">⚓ Vessel Context Document</h3> <p style="margin: 5px 0 0 0; color: #555; font-size: 0.9em;">Click to expand - context automatically included with every AI analysis</p> </div> <span id="vesselContextToggleIcon" style="font-size: 1.2em; color: #667eea;">▶</span> </div> <div id="vesselContextContent" style="display: none; padding: 0 20px 20px 20px; border-top: 1px solid #e3f2fd;"> <!-- Auto-extracted vessel info --> <div style="margin-bottom: 20px;"> <h4 style="margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between;"> <span>📊 Auto-Extracted Vessel Information</span> <button onclick="refreshVesselInfo()" style="font-size: 0.9em; padding: 6px 12px;">🔄 Refresh from SignalK</button> </h4> <div id="autoVesselInfo" style="background: white; padding: 15px; border-radius: 5px; border: 1px solid #ddd;"> <div style="color: #666; font-style: italic;">Loading vessel information...</div> </div> </div> <!-- Custom context --> <div style="margin-bottom: 20px;"> <h4 style="margin-bottom: 10px;">✏️ Custom Context & Notes</h4> <p style="font-size: 0.9em; color: #666; margin-bottom: 10px;">Add any additional context about your vessel, operations, or specific information you want Claude to consider during analysis:</p> <textarea id="customVesselContext" placeholder="e.g., 'This is a racing sailboat primarily used for coastal sailing. The vessel has recently had engine maintenance. We're particularly interested in wind performance analysis...'" style="width: 100%; min-height: 120px; padding: 12px; border: 1px solid #ddd; border-radius: 5px; resize: vertical; font-family: inherit;"></textarea> </div> <!-- Context preview and actions --> <div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;"> <button onclick="saveVesselContext()" style="background: #4CAF50; padding: 8px 16px;">💾 Save Context</button> <button onclick="previewClaudeContext()" style="background: #2196F3; padding: 8px 16px;">👁️ Preview Claude Context</button> <div id="vesselContextStatus" style="margin-left: 10px; font-size: 0.9em;"></div> </div> <!-- Context preview modal --> <div id="contextPreviewModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; overflow-y: auto;"> <div style="max-width: 800px; margin: 50px auto; background: white; border-radius: 10px; padding: 20px; max-height: 80vh; overflow-y: auto;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <h3>👁️ Claude Context Preview</h3> <button onclick="closeContextPreview()" style="background: #f44336; color: white; padding: 5px 10px; border-radius: 3px;">&times;</button> </div> <p style="color: #666; margin-bottom: 15px;">This is the context that will be sent to Claude with every analysis:</p> <pre id="contextPreviewContent" style="background: #f8f9fa; padding: 15px; border-radius: 5px; border: 1px solid #ddd; white-space: pre-wrap; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.9em; line-height: 1.4; max-height: 400px; overflow-y: auto;"></pre> <div style="margin-top: 15px; padding: 10px; background: #e8f5e8; border-radius: 5px; border-left: 4px solid #4CAF50;"> <small><strong>💡 Tip:</strong> A good vessel context helps Claude provide more accurate and relevant insights for your specific vessel and operations.</small> </div> </div> </div> </div> </div> <!-- Custom Analysis --> <div style="margin-bottom: 30px;"> <h3>🎯 Custom Analysis</h3> <div style="margin-top: 15px;"> <!-- Analysis Mode Toggle with Model Selector --> <div style="margin-bottom: 20px; padding: 15px; border: 2px solid #e3f2fd; border-radius: 8px; background: #f8f9ff;"> <div style="margin-bottom: 10px;"> <label style="display: flex; align-items: center; justify-content: space-between; cursor: pointer; font-weight: bold;"> <span>🚀 Direct Database Access</span> <input type="checkbox" id="enableDatabaseAccess" checked onchange="toggleAnalysisMode()" style="transform: scale(1.2);"> </label> </div> <div style="font-size: 0.9em; color: #555;"> <div id="legacyModeDesc" style="color: #666; display: none;"> <strong>Legacy:</strong> Pre-load data samples for analysis (faster, limited scope) </div> <div id="databaseModeDesc" style="color: #1976d2;"> <strong>Enhanced:</strong> Claude explores your database interactively (more powerful, complete historical access) </div> </div> <!-- Claude Model Selection - moved here --> <div style="margin-top: 15px;"> <label for="claudeModelMain">Claude Model:</label> <select id="claudeModelMain" style="width: 100%; padding: 8px; margin-top: 5px;"> <option value="claude-sonnet-4-20250514" selected>Claude Sonnet 4 - Latest Sonnet model (recommended)</option> <option value="claude-opus-4-1-20250805">Claude Opus 4.1 - Most powerful model (premium)</option> <option value="claude-opus-4-20250514">Claude Opus 4 - High performance model (premium)</option> </select> <p style="font-size: 0.9em; color: #666; margin: 5px 0 0 0;">Choose the Claude model for analysis. Sonnet provides the most detailed insights.</p> </div> </div> <!-- Collapsible Selection Options --> <div id="selectionOptionsSection" style="margin-bottom: 20px;"> <div onclick="toggleSelectionOptions()" style="cursor: pointer; padding: 10px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 5px; user-select: none;"> <span id="selectionToggleIcon" style="margin-right: 8px;">▶</span> <strong>Selection Options</strong> <span id="selectionToggleSubtext" style="font-size: 0.9em; color: #666; margin-left: 10px;">(Advanced options for legacy mode)</span> </div> <div id="selectionOptionsContainer" style="display: none; border: 1px solid #dee2e6; border-top: none; border-radius: 0 0 5px 5px; padding: 15px; background: #f9f9f9;"> <!-- Data Path Selection --> <div style="margin-bottom: 15px;"> <label>Data Paths (select one or more):</label> <div id="analysisDataPathContainer" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; margin-top: 5px; background: white;"> <div style="color: #666; font-style: italic;">Loading available paths...</div> </div> <style> .path-checkbox-row { display: flex; align-items: center; padding: 8px 8px 8px 5px; margin: 2px 0; border-radius: 4px; } .path-checkbox-row:nth-child(odd) { background-color: #f8f9fa; } .path-checkbox-row:nth-child(even) { background-color: white; } .path-checkbox-row:hover { background-color: #e3f2fd; } .path-info { display: flex; align-items: center; flex-grow: 1; } .path-icon { margin-right: 8px; font-size: 16px; } .path-checkbox { margin-left: auto; width: 16px; height: 16px; flex-shrink: 0; } </style> <div style="margin-top: 5px; font-size: 0.9em; color: #666; display: flex; justify-content: space-between; align-items: center;"> <span><span id="selectedPathCount">0</span> path(s) selected</span> <div> <button onclick="selectAllPaths()" style="font-size: 0.8em; padding: 2px 6px; margin-right: 5px;">Select All</button> <button onclick="clearAllPaths()" style="font-size: 0.8em; padding: 2px 6px;">Clear All</button> </div> </div> </div> <!-- Date Range Selection --> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;"> <div> <label for="analysisStartDate">Start Date:</label> <input type="datetime-local" id="analysisStartDate" style="width: 100%; padding: 8px; margin-top: 5px;"> </div> <div> <label for="analysisEndDate">End Date:</label> <input type="datetime-local" id="analysisEndDate" style="width: 100%; padding: 8px; margin-top: 5px;"> </div> </div> <!-- Aggregation Method --> <div style="margin-bottom: 15px;"> <label for="aggregationMethod">Aggregation Method:</label> <select id="aggregationMethod" style="width: 100%; padding: 8px; margin-top: 5px;"> <option value="average" selected>Average - Mean values over time buckets</option> <option value="min">Min - Minimum values over time buckets</option> <option value="max">Max - Maximum values over time buckets</option> <option value="first">First - First value in each time bucket</option> <option value="last">Last - Last value in each time bucket</option> <option value="mid">Median - Middle values over time buckets</option> <option value="multiple">Multiple - Get min, max, and average</option> </select> </div> <!-- Time Resolution --> <div style="margin-bottom: 15px;"> <label for="resolution">Time Resolution:</label> <select id="resolution" style="width: 100%; padding: 8px; margin-top: 5px;"> <option value="" selected>Auto - Automatically determine best resolution</option> <option value="1m">1 minute - Fine-grained data</option> <option value="5m">5 minutes - Detailed data</option> <option value="15m">15 minutes - Regular sampling</option> <option value="1h">1 hour - Hourly summaries</option> <option value="1d">1 day - Daily summaries</option> </select> </div> </div> </div> <div style="margin-bottom: 15px;"> <label for="customPrompt">Custom Analysis Request (optional):</label> <textarea id="customPrompt" placeholder="What specific insights are you looking for? Leave blank for general analysis..." style="width: 100%; padding: 8px; margin-top: 5px; min-height: 60px; resize: vertical;"></textarea> </div> <button onclick="runCustomAnalysis()" style="padding: 12px 25px; margin-top: 15px;">🚀 Run Custom Analysis</button> </div> </div> <!-- Analysis Results --> <div id="analysisResults" style="display: none;"> <h3>📊 Analysis Results</h3> <div id="analysisContent" style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-top: 15px;"> <!-- Results will appear here --> </div> <!-- Follow-up Questions --> <div id="followUpSection" style="display: none; margin-top: 20px; padding: 15px; border: 2px solid #e8f4f8; border-radius: 8px; background: #f8fdff;"> <h4 style="margin: 0 0 10px 0; color: #1976d2;">💬 Ask Follow-up Questions</h4> <p style="font-size: 0.9em; color: #666; margin: 0 0 15px 0;">Continue the conversation to explore your data deeper