diff --git a/memory/.dreams/events.jsonl b/memory/.dreams/events.jsonl index 8547efc..48f85a3 100644 --- a/memory/.dreams/events.jsonl +++ b/memory/.dreams/events.jsonl @@ -13,3 +13,4 @@ {"type":"memory.recall.recorded","timestamp":"2026-04-26T10:54:35.677Z","query":"mva154 хост сервер характеристики доступ SSH","resultCount":1,"results":[{"path":"memory/2026-04-11.md","startLine":62,"endLine":71,"score":0.3638544976711273}]} {"type":"memory.recall.recorded","timestamp":"2026-05-04T06:57:05.721Z","query":"enduro trails backlog задачи","resultCount":3,"results":[{"path":"memory/2026-05-02.md","startLine":107,"endLine":142,"score":0.375397714972496},{"path":"memory/2026-05-02.md","startLine":86,"endLine":117,"score":0.3636188447475433},{"path":"memory/2026-05-03.md","startLine":1,"endLine":36,"score":0.3552299261093139}]} {"type":"memory.recall.recorded","timestamp":"2026-05-05T05:03:32.115Z","query":"DEV_TASK_PHASE5 dev agent enduro trails фаза 5","resultCount":1,"results":[{"path":"memory/2026-05-04.md","startLine":1,"endLine":30,"score":0.40014922022819516}]} +{"type":"memory.recall.recorded","timestamp":"2026-05-05T20:11:55.846Z","query":"TTS синтез речи модель голос ElevenLabs Yandex SpeechKit","resultCount":2,"results":[{"path":"memory/2026-03-23.md","startLine":64,"endLine":80,"score":0.40174805521965024},{"path":"memory/2026-03-23.md","startLine":48,"endLine":66,"score":0.3850157171487808}]} diff --git a/memory/.dreams/short-term-recall.json b/memory/.dreams/short-term-recall.json index 7509054..0934434 100644 --- a/memory/.dreams/short-term-recall.json +++ b/memory/.dreams/short-term-recall.json @@ -1,6 +1,6 @@ { "version": 1, - "updatedAt": "2026-05-05T05:03:32.115Z", + "updatedAt": "2026-05-05T20:11:55.846Z", "entries": { "memory:memory/2026-04-05.md:29:55": { "key": "memory:memory/2026-04-05.md:29:55", @@ -748,6 +748,68 @@ "ui-тестирования", "playwright/puppeteer" ] + }, + "memory:memory/2026-03-23.md:64:80": { + "key": "memory:memory/2026-03-23.md:64:80", + "path": "memory/2026-03-23.md", + "startLine": 64, + "endLine": 80, + "source": "memory", + "snippet": "- **Claude Sonnet:** создаёт текст с оптимальной пунктуацией, паузами, плавными формулировками, что лучше для TTS - **DeepSeek v3.2:** генерирует более компактный текст, менее выраженная структура предложений, что может приводить к искажениям при озвучивании - **Ключевой вывод:** Качество голосовых сообщений зависит не от самого синтеза речи, а от текста, который подаётся в TTS-движок - **Рекомендация:** Для задач, где важна качественная озвучка, использовать модели с более структурированным выводом (Claude Sonnet) ### Создание специализированного агента для оптимизации текста под TTS - 23 марта 17:09 - Слава предложил создать первого специализированного агента для формулирования текста, ид", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 0.40174805521965024, + "maxScore": 0.40174805521965024, + "firstRecalledAt": "2026-05-05T20:11:55.846Z", + "lastRecalledAt": "2026-05-05T20:11:55.846Z", + "queryHashes": [ + "4c737bff00f4" + ], + "recallDays": [ + "2026-05-05" + ], + "conceptTags": [ + "v3.2", + "tts-движок", + "claude", + "sonnet", + "создаёт", + "текст", + "оптимальной", + "пунктуацией" + ] + }, + "memory:memory/2026-03-23.md:48:66": { + "key": "memory:memory/2026-03-23.md:48:66", + "path": "memory/2026-03-23.md", + "startLine": 48, + "endLine": 66, + "source": "memory", + "snippet": "### Расчёт экономии от использования специализированных агентов - 23 марта 16:59 - Слава попросил рассчитать экономию от использования группы специализированных агентов вместо одного универсального - Проведён расчёт на основе цен OpenRouter API: - Claude Sonnet 4.6: $3/$15 за 1M токенов (input/output) - Claude Haiku: $1/$5 за 1M токенов - Llama 4 Maverick: $0.15/$0.60 за 1M токенов - Gemini 2.0 Flash: $0.10/$0.40 за 1M токенов - Оценка ежедневного потребления: 105K input, 67K output токенов - Стоимость одной модели Sonnet: ~$1.32 в день - Стоимость команды специалистов: ~$0.69 в день - Экономия: 48% ($0.63 в день, $19 в месяц, $230 в год) - Дополнительные преимущества: повышение каче", + "recallCount": 1, + "dailyCount": 0, + "groundedCount": 0, + "totalScore": 0.3850157171487808, + "maxScore": 0.3850157171487808, + "firstRecalledAt": "2026-05-05T20:11:55.846Z", + "lastRecalledAt": "2026-05-05T20:11:55.846Z", + "queryHashes": [ + "4c737bff00f4" + ], + "recallDays": [ + "2026-05-05" + ], + "conceptTags": [ + "router", + "4.6", + "input/output", + "0.15", + "0.60", + "2.0", + "0.10", + "0.40" + ] } } } diff --git a/tasks/enduro-trails/prototype/static/app.js b/tasks/enduro-trails/prototype/static/app.js index 0395439..f0b6094 100644 --- a/tasks/enduro-trails/prototype/static/app.js +++ b/tasks/enduro-trails/prototype/static/app.js @@ -1,5 +1,5 @@ // ═══════════════════════════════════════════════════════════════════ -// Enduro Trails — Phase 5 Redesign +// Enduro Trails - Phase 5 Redesign // Theme system (auto/light/dark + SunCalc), skeleton, swipe, animations // ═══════════════════════════════════════════════════════════════════ @@ -54,7 +54,7 @@ function toggleTheme() { if (themeMode === 'auto') themeMode = 'light'; else if (themeMode === 'light') themeMode = 'dark'; else themeMode = 'auto'; - + localStorage.setItem('enduro-theme-mode', themeMode); applyTheme(); } @@ -64,9 +64,9 @@ function updateThemeButtonIcon() { const moonIcon = document.getElementById('theme-icon-moon'); const label = document.getElementById('theme-label'); if (!sunIcon || !moonIcon) return; - + const dark = isDarkTheme(); - + if (themeMode === 'auto') { // Dynamic icon based on actual theme sunIcon.style.display = dark ? 'none' : 'block'; @@ -89,8 +89,8 @@ function switchMapStyle() { const dark = isDarkTheme(); const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || ''; const styleUrl = dark ? basePath + '/style.json' : basePath + '/style-light.json'; - - // Check if style-light.json exists — if not, keep current + + // Check if style-light.json exists - if not, keep current fetch(styleUrl, { method: 'HEAD' }).then(r => { if (r.ok) { map.setStyle(styleUrl); @@ -170,7 +170,7 @@ function formatDuration(seconds) { } function formatDist(m) { - if (!m) return '—'; + if (!m) return '-'; if (m >= 1000) return (m / 1000).toFixed(1) + ' км'; return Math.round(m) + ' м'; } @@ -223,7 +223,7 @@ function initSheetSwipe() { document.querySelectorAll('.bottom-sheet').forEach(sheet => { let startY = 0; let isDragging = false; - + sheet.addEventListener('touchstart', (e) => { const rect = sheet.getBoundingClientRect(); const touchY = e.touches[0].clientY; @@ -234,7 +234,7 @@ function initSheetSwipe() { sheet.classList.add('swiping'); } }, { passive: true }); - + sheet.addEventListener('touchmove', (e) => { if (!isDragging) return; const dy = e.touches[0].clientY - startY; @@ -242,7 +242,7 @@ function initSheetSwipe() { sheet.style.transform = `translateY(${dy}px)`; } }, { passive: true }); - + sheet.addEventListener('touchend', (e) => { if (!isDragging) return; isDragging = false; @@ -289,7 +289,7 @@ function showSkeleton(containerId, count) { function deactivateAllModes() { // Deactivate all input modes but preserve route/scenic/link data on map - if (routeMode) { routeMode = false; document.getElementById('tb-route').classList.remove('active'); closeSheet('sheet-route'); /* NOT clearRoute — keep line on map */ } + if (routeMode) { routeMode = false; document.getElementById('tb-route').classList.remove('active'); closeSheet('sheet-route'); /* NOT clearRoute - keep line on map */ } if (rulerMode) toggleRuler(); if (markerMode) toggleMarkerMode(); if (typeof reconMode !== 'undefined' && reconMode) toggleReconMode(); @@ -380,7 +380,7 @@ function toggleLayer(group) { }); } -// ─── Роутинг — состояние ─────────────────────────────────────────── +// ─── Роутинг - состояние ─────────────────────────────────────────── const ROUTE_COLORS = ['#0066ff', '#00aa44', '#9933cc', '#ff8800', '#888888']; let routeMode = false; let routeWaypoints = []; @@ -399,25 +399,25 @@ function toggleRouteMode() { const btn = document.getElementById('tb-route'); if (routeMode) { - // If sheet is open — close sheet but stay in mode + // If sheet is open - close sheet but stay in mode const sheet = document.getElementById('sheet-route'); if (sheet && sheet.classList.contains('open')) { closeSheet('sheet-route'); return; } - // Sheet is closed — exit mode and clear route + // Sheet is closed - exit mode and clear route routeMode = false; btn.classList.remove('active'); clearRoute(); window._map.getCanvas().style.cursor = ''; } else { - // Enter route mode — do NOT open sheet + // Enter route mode - do NOT open sheet deactivateAllModes(); routeMode = true; btn.classList.add('active'); clearRoute(); window._map.getCanvas().style.cursor = 'crosshair'; - // sheet is NOT opened — user taps mini-bar to open it + // sheet is NOT opened - user taps mini-bar to open it } updateMapModeClass(); } @@ -543,8 +543,55 @@ function haversineM(a, b) { } function formatSegmentDist(m) { - if (m < 1000) return Math.round(m) + ' м'; - return (m / 1000).toFixed(1).replace('.', ',') + ' км'; + if (m < 1000) return Math.round(m) + ' м'; + return (m / 1000).toFixed(1).replace('.', ',') + ' км'; +} + +// Returns array of route-distance segments (meters) for each waypoint. +// segDists[0] = 0, segDists[i] = distance along route geometry from wp[i-1] to wp[i]. +// Falls back to haversine if route geometry is unavailable or snap fails. +function getRouteSegmentDistances() { + const route = routeResults[activeRouteIdx]; + if (!route || !route.geometry || !route.geometry.coordinates) return null; + + const coords = route.geometry.coordinates; // [[lon, lat], ...] + const n = coords.length; + if (n < 2 || routeWaypoints.length < 2) return null; + + // Convert geometry coords to {lat, lon} for haversineM + const geoPts = coords.map(([lon, lat]) => ({ lat, lon })); + + // Snap each waypoint to the nearest geometry point index + const snapIdx = routeWaypoints.map(wp => { + let bestIdx = 0; + let bestDist = Infinity; + for (let j = 0; j < n; j++) { + const d = haversineM(wp, geoPts[j]); + if (d < bestDist) { bestDist = d; bestIdx = j; } + } + return bestIdx; + }); + + // For each segment i→i+1, sum haversine along geometry from snapIdx[i] to snapIdx[i+1] + const segDists = [0]; + for (let i = 1; i < routeWaypoints.length; i++) { + const from = snapIdx[i - 1]; + const to = snapIdx[i]; + if (from === to) { + // Same snap point — fallback to straight-line + segDists.push(haversineM(routeWaypoints[i - 1], routeWaypoints[i])); + continue; + } + // Walk geometry in the correct direction + const step = to > from ? 1 : -1; + let dist = 0; + for (let j = from; j !== to; j += step) { + dist += haversineM(geoPts[j], geoPts[j + step]); + } + segDists.push(dist); + } + + return segDists; } async function renderWaypointsList() { @@ -553,13 +600,18 @@ async function renderWaypointsList() { const gripSvg = ``; + const segDists = (routeResults.length > 0 && activeRouteIdx >= 0) + ? getRouteSegmentDistances() + : null; + let html = routeWaypoints.map((wp, i) => { const isStart = i === 0; const isEnd = i === routeWaypoints.length - 1; const label = isStart ? 'S' : isEnd ? 'F' : String(i); const color = isStart ? '#2EA043' : isEnd ? '#FF3B1F' : '#0066ff'; const coordText = `${wp.lat.toFixed(3)}, ${wp.lon.toFixed(3)}`; - const distStr = i > 0 ? formatSegmentDist(haversineM(routeWaypoints[i-1], wp)) : ''; + const distStr = i > 0 && segDists ? formatSegmentDist(segDists[i]) : + i > 0 ? formatSegmentDist(haversineM(routeWaypoints[i-1], wp)) : ''; return `