/** * TalkingQ Platform - Session Detail Page */ const SESSION_TOPIC_DRILLDOWN_KEY = 'talkingq_session_topic_drilldown'; const SESSION_VIEW_STATE_KEY = 'talkingq_session_view_state'; const SESSION_DEFAULT_DATE = '2026-05-25'; const SESSION_TOPIC_OPTIONS = ['讲故事', '唱儿歌', '问答学习', '天气查询', '闹钟设置', '游戏互动', '英语学习', '百科问答', '情感陪伴', '其他']; function getMockSessionData() { const rows = [ ['sess-a251', 'company_a', '智慧星科技', 'DEV-A001', '小Q故事机器人', '2026-05-25 14:22', '讲故事', 12, '8m 32s', 'good'], ['sess-a252', 'company_a', '智慧星科技', 'DEV-A001', '小Q故事机器人', '2026-05-25 10:12', '百科问答', 8, '5m 28s', 'good'], ['sess-a253', 'company_a', '智慧星科技', 'DEV-A005', '英语学习助手', '2026-05-25 13:48', '英语学习', 7, '5m 10s', 'good'], ['sess-a254', 'company_a', '智慧星科技', 'DEV-A008', '小Q音乐伙伴', '2026-05-25 13:08', '唱儿歌', 10, '6m 48s', 'good'], ['sess-a255', 'company_a', '智慧星科技', 'DEV-A011', '生活助手', '2026-05-25 12:36', '天气查询', 6, '4m 12s', 'neutral'], ['sess-a256', 'company_a', '智慧星科技', 'DEV-A018', '设备小助手', '2026-05-25 11:52', '闹钟设置', 4, '2m 20s', 'good'], ['sess-a257', 'company_a', '智慧星科技', 'DEV-A021', '小Q游戏伙伴', '2026-05-25 11:18', '游戏互动', 15, '11m 05s', 'good'], ['sess-a258', 'company_a', '智慧星科技', 'DEV-A026', '陪伴机器人', '2026-05-25 10:40', '情感陪伴', 8, '5m 44s', 'good'], ['sess-a241', 'company_a', '智慧星科技', 'DEV-A001', '小Q故事机器人', '2026-05-24 16:06', '讲故事', 9, '6m 16s', 'good'], ['sess-a242', 'company_a', '智慧星科技', 'DEV-A005', '英语学习助手', '2026-05-24 14:20', '问答学习', 6, '4m 38s', 'neutral'], ['sess-a231', 'company_a', '智慧星科技', 'DEV-A021', '小Q游戏伙伴', '2026-05-23 19:42', '游戏互动', 13, '9m 52s', 'good'], ['sess-b251', 'company_b', '未来玩具厂', 'DEV-B002', '学习机助手', '2026-05-25 14:05', '问答学习', 3, '1m 45s', 'bad'], ['sess-b252', 'company_b', '未来玩具厂', 'DEV-B004', '科学百科探索者', '2026-05-25 13:42', '百科问答', 9, '7m 03s', 'neutral'], ['sess-b253', 'company_b', '未来玩具厂', 'DEV-B008', '小Q故事机器人', '2026-05-25 13:18', '讲故事', 11, '8m 15s', 'good'], ['sess-b254', 'company_b', '未来玩具厂', 'DEV-B015', '小Q音乐伙伴', '2026-05-25 12:46', '唱儿歌', 8, '4m 50s', 'good'], ['sess-b255', 'company_b', '未来玩具厂', 'DEV-B019', '小Q游戏伙伴', '2026-05-25 11:56', '游戏互动', 14, '10m 22s', 'good'], ['sess-b256', 'company_b', '未来玩具厂', 'DEV-B024', '生活助手', '2026-05-25 11:20', '天气查询', 5, '3m 06s', 'neutral'], ['sess-b241', 'company_b', '未来玩具厂', 'DEV-B008', '小Q故事机器人', '2026-05-24 15:32', '讲故事', 10, '7m 30s', 'good'], ['sess-b242', 'company_b', '未来玩具厂', 'DEV-B004', '科学百科探索者', '2026-05-24 12:14', '百科问答', 7, '5m 48s', 'good'], ['sess-c251', 'company_c', '童趣电子', 'DEV-C001', '小Q故事机器人', '2026-05-25 13:55', '讲故事', 18, '15m 20s', 'good'], ['sess-c252', 'company_c', '童趣电子', 'DEV-C006', '小Q音乐伙伴', '2026-05-25 13:08', '唱儿歌', 9, '5m 35s', 'good'], ['sess-c253', 'company_c', '童趣电子', 'DEV-C012', '学习机助手', '2026-05-25 12:32', '问答学习', 10, '6m 18s', 'good'], ['sess-c254', 'company_c', '童趣电子', 'DEV-C018', '英语学习助手', '2026-05-25 11:54', '英语学习', 6, '4m 27s', 'neutral'], ['sess-c255', 'company_c', '童趣电子', 'DEV-C022', '科学百科探索者', '2026-05-25 11:16', '百科问答', 12, '9m 11s', 'good'], ['sess-c256', 'company_c', '童趣电子', 'DEV-C027', '陪伴机器人', '2026-05-25 10:45', '情感陪伴', 7, '4m 58s', 'good'], ['sess-c257', 'company_c', '童趣电子', 'DEV-C031', '小Q助手', '2026-05-25 10:20', '其他', 2, '1m 02s', 'neutral'], ['sess-c241', 'company_c', '童趣电子', 'DEV-C001', '小Q故事机器人', '2026-05-24 18:18', '讲故事', 16, '13m 44s', 'good'], ['sess-c242', 'company_c', '童趣电子', 'DEV-C012', '学习机助手', '2026-05-24 09:48', '问答学习', 9, '6m 01s', 'good'], ]; return rows.map(([id, companyId, companyName, device, agent, start, intent, turns, duration, quality]) => ({ id, companyId, companyName, device, agent, start, date: start.slice(0, 10), intent, turns, duration, quality, })); } function readSessionTopicDrilldown() { try { const raw = sessionStorage.getItem(SESSION_TOPIC_DRILLDOWN_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (!parsed?.topic) return null; if (parsed.createdAt && Date.now() - parsed.createdAt > 30 * 60 * 1000) { sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY); return null; } return parsed; } catch (error) { sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY); return null; } } function readSessionViewState() { try { return JSON.parse(sessionStorage.getItem(SESSION_VIEW_STATE_KEY) || '{}') || {}; } catch (error) { return {}; } } function updateSessionViewState(patch) { const next = { ...readSessionViewState(), ...patch }; sessionStorage.setItem(SESSION_VIEW_STATE_KEY, JSON.stringify(next)); } function setSessionTopicDrilldown(topic, context = {}) { if (!topic) { sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY); return; } sessionStorage.setItem(SESSION_TOPIC_DRILLDOWN_KEY, JSON.stringify({ topic, companyId: context.companyId || 'all', companyName: context.companyName || '全部公司', source: context.source || 'session-filter', createdAt: Date.now(), })); } function getSessionCurrentCompanyId() { const currentRole = window.__getUserRole ? window.__getUserRole() : 'admin'; const currentCompanyId = window.__getCurrentCompanyId ? window.__getCurrentCompanyId() : 'all'; if (currentRole !== 'client') return currentCompanyId; if (currentCompanyId !== 'all') return currentCompanyId; try { const user = JSON.parse(sessionStorage.getItem('talkingq_user') || 'null'); return user?.companyId || user?.allowedCompanies?.[0] || currentCompanyId; } catch (error) { return currentCompanyId; } } function getSessionCompanyName(companyId, fallback = '当前公司') { if (companyId === 'all') return '全部公司'; return window.__getCompanyNameById ? window.__getCompanyNameById(companyId) : fallback; } function sessionMatchesTopic(session, topic) { if (!topic) return true; const intent = String(session.intent || ''); if (intent === topic) return true; if (intent.includes(topic) || topic.includes(intent)) return true; const aliases = { '讲故事': ['故事', '睡前故事', '绘本'], '唱儿歌': ['儿歌', '音乐', '唱歌'], '问答学习': ['问答', '学习', '作业'], '天气查询': ['天气'], '闹钟设置': ['闹钟', '提醒'], '游戏互动': ['游戏', '互动'], '英语学习': ['英语', '英文'], '百科问答': ['百科', '科普'], '情感陪伴': ['陪伴', '情感', '聊天'], '其他': ['其他'], }; return (aliases[topic] || []).some(keyword => intent.includes(keyword)); } function sessionEscapeHtml(value) { return String(value ?? '').replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', }[char])); } function sessionEscapeAttr(value) { return sessionEscapeHtml(value).replace(/"/g, '"'); } function formatSessionDateLabel(date) { const [year, month, day] = String(date || '').split('-'); return year && month && day ? `${year}.${month}.${day}` : date; } function getSessionQualityText(quality) { if (quality === 'good') return '优质'; if (quality === 'bad') return '待优化'; return '正常'; } function getSessionQualityBadge(quality) { if (quality === 'good') return 'badge-success'; if (quality === 'bad') return 'badge-danger'; return 'badge-default'; } function getTopicOptionsForCompany(sessions) { const seen = new Set(sessions.map(item => item.intent)); return SESSION_TOPIC_OPTIONS.filter(topic => seen.has(topic)); } function getCompanyDevices(companySessions, daySessions) { const byDevice = new Map(); companySessions.forEach(session => { if (!byDevice.has(session.device)) { byDevice.set(session.device, { device: session.device, companyId: session.companyId, companyName: session.companyName, agent: session.agent, sessions: [], }); } }); daySessions.forEach(session => { if (!byDevice.has(session.device)) { byDevice.set(session.device, { device: session.device, companyId: session.companyId, companyName: session.companyName, agent: session.agent, sessions: [], }); } byDevice.get(session.device).sessions.push(session); }); return Array.from(byDevice.values()).map(item => { const topics = Array.from(new Set(item.sessions.map(session => session.intent))); const turns = item.sessions.reduce((sum, session) => sum + session.turns, 0); const lastSession = item.sessions.slice().sort((a, b) => b.start.localeCompare(a.start))[0]; return { ...item, topics, turns, count: item.sessions.length, lastSession, }; }).sort((a, b) => b.count - a.count || a.device.localeCompare(b.device)); } function getConversationLines(topic) { const samples = { '讲故事': ['小Q,给我讲一个关于恐龙的故事吧!', '好的!从前,在一片茂密的丛林里,住着一只叫做小雷的霸王龙宝宝……', '霸王龙是最厉害的恐龙吗?', '霸王龙很厉害,但恐龙世界里还有很多不同种类的大家伙。我们可以一起认识它们。'], '唱儿歌': ['小Q,唱一首上学路上的儿歌。', '当然可以,我会用轻快一点的节奏来唱。', '再唱一首动物相关的。', '好的,这次我们唱一首关于小兔子的儿歌。'], '问答学习': ['小Q,为什么月亮会变圆又变弯?', '这是因为月亮围着地球转,我们看到被太阳照亮的部分会变化。', '那满月是什么时候?', '当我们看到月亮被照亮的一面几乎完整朝向地球时,就是满月。'], '天气查询': ['小Q,今天要带雨伞吗?', '今天下午可能有小雨,建议带一把轻便雨伞。', '那适合出去玩吗?', '可以短时间外出,尽量避开下午降雨时段。'], '闹钟设置': ['小Q,明天早上七点叫我起床。', '好的,已为你设置明天早上七点的起床提醒。', '再加一个七点十分。', '已添加七点十分的第二个提醒。'], '游戏互动': ['小Q,我们玩猜动物吧。', '好,我来描述一个动物,你来猜。它有长长的鼻子和大大的耳朵。', '是大象!', '答对了!下一题要不要难一点?'], '英语学习': ['How do I say 小熊 in English?', '小熊 can be said as little bear or teddy bear, depending on context.', 'Can you make a sentence?', 'Sure. The little bear is sleeping under the tree.'], '百科问答': ['小Q,火山为什么会喷发?', '地下的岩浆和气体压力变大时,会沿着裂缝冲出来,就形成了火山喷发。', '岩浆冷了会变成什么?', '岩浆冷却后会变成岩石,有些还会形成新的地貌。'], '情感陪伴': ['小Q,我今天有点不开心。', '我在呢。你愿意告诉我发生了什么吗?', '我和朋友吵架了。', '听起来你很难过。我们可以先深呼吸,再想想怎么和朋友好好说。'], '其他': ['小Q,你现在能做什么?', '我可以聊天、讲故事、唱儿歌、回答问题,也可以帮你做一些简单提醒。', '那我们随便聊聊吧。', '好呀,你今天最开心的一件事是什么?'], }; return samples[topic] || samples['其他']; } function renderSessionConversation(session, options = {}) { if (!session) { return '
暂无可查看的对话内容
${formatSessionDateLabel(selectedDate)} · 从左侧选择设备查看对话内容
请选择设备查看当天对话历史
${formatSessionDateLabel(selectedDate)} 当天暂无对话
该设备在所选日期没有产生对话
${formatSessionDateLabel(selectedDate)} · ${device.sessions.length} 条对话 · ${device.turns} 轮互动
所选日期暂无「${sessionEscapeHtml(selectedTopic)}」相关对话
| 设备 | ${showCompanyColumn ? '所属公司 | ' : ''}当日会话 | 轮次 | 涉及话题 | 最后对话 | 操作 |
|---|