| <template> |
| <div class="interaction-panel"> |
| |
| <div class="main-split-layout"> |
| |
| <div class="left-panel report-style" ref="leftPanel"> |
| <div v-if="reportOutline" class="report-content-wrapper"> |
| |
| <div class="report-header-block"> |
| <div class="report-meta"> |
| <span class="report-tag">Prediction Report</span> |
| <span class="report-id">ID: {{ reportId || 'REF-2024-X92' }}</span> |
| </div> |
| <h1 class="main-title">{{ reportOutline.title }}</h1> |
| <p class="sub-title">{{ reportOutline.summary }}</p> |
| <div class="header-divider"></div> |
| </div> |
| |
| |
| <div class="sections-list"> |
| <div |
| v-for="(section, idx) in reportOutline.sections" |
| :key="idx" |
| class="report-section-item" |
| :class="{ |
| 'is-active': currentSectionIndex === idx + 1, |
| 'is-completed': isSectionCompleted(idx + 1), |
| 'is-pending': !isSectionCompleted(idx + 1) && currentSectionIndex !== idx + 1 |
| }" |
| > |
| <div class="section-header-row" @click="toggleSectionCollapse(idx)" :class="{ 'clickable': isSectionCompleted(idx + 1) }"> |
| <span class="section-number">{{ String(idx + 1).padStart(2, '0') }}</span> |
| <h3 class="section-title">{{ section.title }}</h3> |
| <svg |
| v-if="isSectionCompleted(idx + 1)" |
| class="collapse-icon" |
| :class="{ 'is-collapsed': collapsedSections.has(idx) }" |
| viewBox="0 0 24 24" |
| width="20" |
| height="20" |
| fill="none" |
| stroke="currentColor" |
| stroke-width="2" |
| > |
| <polyline points="6 9 12 15 18 9"></polyline> |
| </svg> |
| </div> |
| |
| <div class="section-body" v-show="!collapsedSections.has(idx)"> |
| |
| <div v-if="generatedSections[idx + 1]" class="generated-content" v-html="renderMarkdown(generatedSections[idx + 1])"></div> |
| |
| |
| <div v-else-if="currentSectionIndex === idx + 1" class="loading-state"> |
| <div class="loading-icon"> |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"> |
| <circle cx="12" cy="12" r="10" stroke-width="4" stroke="#E5E7EB"></circle> |
| <path d="M12 2a10 10 0 0 1 10 10" stroke-width="4" stroke="#4B5563" stroke-linecap="round"></path> |
| </svg> |
| </div> |
| <span class="loading-text">正在生成{{ section.title }}...</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div v-if="!reportOutline" class="waiting-placeholder"> |
| <div class="waiting-animation"> |
| <div class="waiting-ring"></div> |
| <div class="waiting-ring"></div> |
| <div class="waiting-ring"></div> |
| </div> |
| <span class="waiting-text">Waiting for Report Agent...</span> |
| </div> |
| </div> |
| |
| |
| <div class="right-panel" ref="rightPanel"> |
| |
| <div class="action-bar"> |
| <div class="action-bar-header"> |
| <svg class="action-bar-icon" viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.5"> |
| <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> |
| </svg> |
| <div class="action-bar-text"> |
| <span class="action-bar-title">Interactive Tools</span> |
| <span class="action-bar-subtitle mono">{{ profiles.length }} agents available</span> |
| </div> |
| </div> |
| <div class="action-bar-tabs"> |
| <button |
| class="tab-pill" |
| :class="{ active: activeTab === 'chat' && chatTarget === 'report_agent' }" |
| @click="selectReportAgentChat" |
| > |
| <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path> |
| </svg> |
| <span>与Report Agent对话</span> |
| </button> |
| <div class="agent-dropdown" v-if="profiles.length > 0"> |
| <button |
| class="tab-pill agent-pill" |
| :class="{ active: activeTab === 'chat' && chatTarget === 'agent' }" |
| @click="toggleAgentDropdown" |
| > |
| <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path> |
| <circle cx="12" cy="7" r="4"></circle> |
| </svg> |
| <span>{{ selectedAgent ? selectedAgent.username : '与世界中任意个体对话' }}</span> |
| <svg class="dropdown-arrow" :class="{ open: showAgentDropdown }" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="6 9 12 15 18 9"></polyline> |
| </svg> |
| </button> |
| <div v-if="showAgentDropdown" class="dropdown-menu"> |
| <div class="dropdown-header">选择对话对象</div> |
| <div |
| v-for="(agent, idx) in profiles" |
| :key="idx" |
| class="dropdown-item" |
| @click="selectAgent(agent, idx)" |
| > |
| <div class="agent-avatar">{{ (agent.username || 'A')[0] }}</div> |
| <div class="agent-info"> |
| <span class="agent-name">{{ agent.username }}</span> |
| <span class="agent-role">{{ agent.profession || '未知职业' }}</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="tab-divider"></div> |
| <button |
| class="tab-pill survey-pill" |
| :class="{ active: activeTab === 'survey' }" |
| @click="selectSurveyTab" |
| > |
| <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M9 11l3 3L22 4"></path> |
| <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path> |
| </svg> |
| <span>发送问卷调查到世界中</span> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div v-if="activeTab === 'chat'" class="chat-container"> |
| |
| |
| <div v-if="chatTarget === 'report_agent'" class="report-agent-tools-card"> |
| <div class="tools-card-header"> |
| <div class="tools-card-avatar">R</div> |
| <div class="tools-card-info"> |
| <div class="tools-card-name">Report Agent - Chat</div> |
| <div class="tools-card-subtitle">报告生成智能体的快速对话版本,可调用 4 种专业工具,拥有MiroFish的完整记忆</div> |
| </div> |
| <button class="tools-card-toggle" @click="showToolsDetail = !showToolsDetail"> |
| <svg :class="{ 'is-expanded': showToolsDetail }" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="6 9 12 15 18 9"></polyline> |
| </svg> |
| </button> |
| </div> |
| <div v-if="showToolsDetail" class="tools-card-body"> |
| <div class="tools-grid"> |
| <div class="tool-item tool-purple"> |
| <div class="tool-icon-wrapper"> |
| <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.5V17a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-2.5A7 7 0 0 0 12 2z"></path> |
| </svg> |
| </div> |
| <div class="tool-content"> |
| <div class="tool-name">InsightForge 深度归因</div> |
| <div class="tool-desc">对齐现实世界种子数据与模拟环境状态,结合Global/Local Memory机制,提供跨时空的深度归因分析</div> |
| </div> |
| </div> |
| <div class="tool-item tool-blue"> |
| <div class="tool-icon-wrapper"> |
| <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="12" cy="12" r="10"></circle> |
| <path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path> |
| </svg> |
| </div> |
| <div class="tool-content"> |
| <div class="tool-name">PanoramaSearch 全景追踪</div> |
| <div class="tool-desc">基于图结构的广度遍历算法,重构事件传播路径,捕获全量信息流动的拓扑结构</div> |
| </div> |
| </div> |
| <div class="tool-item tool-orange"> |
| <div class="tool-icon-wrapper"> |
| <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"> |
| <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon> |
| </svg> |
| </div> |
| <div class="tool-content"> |
| <div class="tool-name">QuickSearch 快速检索</div> |
| <div class="tool-desc">基于 GraphRAG 的即时查询接口,优化索引效率,用于快速提取具体的节点属性与离散事实</div> |
| </div> |
| </div> |
| <div class="tool-item tool-green"> |
| <div class="tool-icon-wrapper"> |
| <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path> |
| <circle cx="9" cy="7" r="4"></circle> |
| <path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"></path> |
| </svg> |
| </div> |
| <div class="tool-content"> |
| <div class="tool-name">InterviewSubAgent 虚拟访谈</div> |
| <div class="tool-desc">自主式访谈,能够并行与模拟世界中个体进行多轮对话,采集非结构化的观点数据与心理状态</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div v-if="chatTarget === 'agent' && selectedAgent" class="agent-profile-card"> |
| <div class="profile-card-header"> |
| <div class="profile-card-avatar">{{ (selectedAgent.username || 'A')[0] }}</div> |
| <div class="profile-card-info"> |
| <div class="profile-card-name">{{ selectedAgent.username }}</div> |
| <div class="profile-card-meta"> |
| <span v-if="selectedAgent.name" class="profile-card-handle">@{{ selectedAgent.name }}</span> |
| <span class="profile-card-profession">{{ selectedAgent.profession || '未知职业' }}</span> |
| </div> |
| </div> |
| <button class="profile-card-toggle" @click="showFullProfile = !showFullProfile"> |
| <svg :class="{ 'is-expanded': showFullProfile }" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="6 9 12 15 18 9"></polyline> |
| </svg> |
| </button> |
| </div> |
| <div v-if="showFullProfile && selectedAgent.bio" class="profile-card-body"> |
| <div class="profile-card-bio"> |
| <div class="profile-card-label">简介</div> |
| <p>{{ selectedAgent.bio }}</p> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="chat-messages" ref="chatMessages"> |
| <div v-if="chatHistory.length === 0" class="chat-empty"> |
| <div class="empty-icon"> |
| <svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5"> |
| <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> |
| </svg> |
| </div> |
| <p class="empty-text"> |
| {{ chatTarget === 'report_agent' ? '与 Report Agent 对话,深入了解报告内容' : '与模拟个体对话,了解他们的观点' }} |
| </p> |
| </div> |
| <div |
| v-for="(msg, idx) in chatHistory" |
| :key="idx" |
| class="chat-message" |
| :class="msg.role" |
| > |
| <div class="message-avatar"> |
| <span v-if="msg.role === 'user'">U</span> |
| <span v-else>{{ msg.role === 'assistant' && chatTarget === 'report_agent' ? 'R' : (selectedAgent?.username?.[0] || 'A') }}</span> |
| </div> |
| <div class="message-content"> |
| <div class="message-header"> |
| <span class="sender-name"> |
| {{ msg.role === 'user' ? 'You' : (chatTarget === 'report_agent' ? 'Report Agent' : (selectedAgent?.username || 'Agent')) }} |
| </span> |
| <span class="message-time">{{ formatTime(msg.timestamp) }}</span> |
| </div> |
| <div class="message-text" v-html="renderMarkdown(msg.content)"></div> |
| </div> |
| </div> |
| <div v-if="isSending" class="chat-message assistant"> |
| <div class="message-avatar"> |
| <span>{{ chatTarget === 'report_agent' ? 'R' : (selectedAgent?.username?.[0] || 'A') }}</span> |
| </div> |
| <div class="message-content"> |
| <div class="typing-indicator"> |
| <span></span> |
| <span></span> |
| <span></span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="chat-input-area"> |
| <textarea |
| v-model="chatInput" |
| class="chat-input" |
| placeholder="输入您的问题..." |
| @keydown.enter.exact.prevent="sendMessage" |
| :disabled="isSending || (!selectedAgent && chatTarget === 'agent')" |
| rows="1" |
| ref="chatInputRef" |
| ></textarea> |
| <button |
| class="send-btn" |
| @click="sendMessage" |
| :disabled="!chatInput.trim() || isSending || (!selectedAgent && chatTarget === 'agent')" |
| > |
| <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2"> |
| <line x1="22" y1="2" x2="11" y2="13"></line> |
| <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> |
| </svg> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div v-if="activeTab === 'survey'" class="survey-container"> |
| |
| <div class="survey-setup"> |
| <div class="setup-section"> |
| <div class="section-header"> |
| <span class="section-title">选择调查对象</span> |
| <span class="selection-count">已选 {{ selectedAgents.size }} / {{ profiles.length }}</span> |
| </div> |
| <div class="agents-grid"> |
| <label |
| v-for="(agent, idx) in profiles" |
| :key="idx" |
| class="agent-checkbox" |
| :class="{ checked: selectedAgents.has(idx) }" |
| > |
| <input |
| type="checkbox" |
| :checked="selectedAgents.has(idx)" |
| @change="toggleAgentSelection(idx)" |
| > |
| <div class="checkbox-avatar">{{ (agent.username || 'A')[0] }}</div> |
| <div class="checkbox-info"> |
| <span class="checkbox-name">{{ agent.username }}</span> |
| <span class="checkbox-role">{{ agent.profession || '未知职业' }}</span> |
| </div> |
| <div class="checkbox-indicator"> |
| <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="3"> |
| <polyline points="20 6 9 17 4 12"></polyline> |
| </svg> |
| </div> |
| </label> |
| </div> |
| <div class="selection-actions"> |
| <button class="action-link" @click="selectAllAgents">全选</button> |
| <span class="action-divider">|</span> |
| <button class="action-link" @click="clearAgentSelection">清空</button> |
| </div> |
| </div> |
| |
| <div class="setup-section"> |
| <div class="section-header"> |
| <span class="section-title">问卷问题</span> |
| </div> |
| <textarea |
| v-model="surveyQuestion" |
| class="survey-input" |
| placeholder="输入您想问所有被选中对象的问题..." |
| rows="3" |
| ></textarea> |
| </div> |
| |
| <button |
| class="survey-submit-btn" |
| :disabled="selectedAgents.size === 0 || !surveyQuestion.trim() || isSurveying" |
| @click="submitSurvey" |
| > |
| <span v-if="isSurveying" class="loading-spinner"></span> |
| <span v-else>发送问卷</span> |
| </button> |
| </div> |
| |
| |
| <div v-if="surveyResults.length > 0" class="survey-results"> |
| <div class="results-header"> |
| <span class="results-title">调查结果</span> |
| <span class="results-count">{{ surveyResults.length }} 条回复</span> |
| </div> |
| <div class="results-list"> |
| <div |
| v-for="(result, idx) in surveyResults" |
| :key="idx" |
| class="result-card" |
| > |
| <div class="result-header"> |
| <div class="result-avatar">{{ (result.agent_name || 'A')[0] }}</div> |
| <div class="result-info"> |
| <span class="result-name">{{ result.agent_name }}</span> |
| <span class="result-role">{{ result.profession || '未知职业' }}</span> |
| </div> |
| </div> |
| <div class="result-question"> |
| <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="12" cy="12" r="10"></circle> |
| <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path> |
| <line x1="12" y1="17" x2="12.01" y2="17"></line> |
| </svg> |
| <span>{{ result.question }}</span> |
| </div> |
| <div class="result-answer" v-html="renderMarkdown(result.answer)"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </template> |
| |
| <script setup> |
| import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue' |
| import { chatWithReport, getReport, getAgentLog } from '../api/report' |
| import { interviewAgents, getSimulationProfilesRealtime } from '../api/simulation' |
| |
| const props = defineProps({ |
| reportId: String, |
| simulationId: String |
| }) |
| |
| const emit = defineEmits(['add-log', 'update-status']) |
| |
| |
| const activeTab = ref('chat') |
| const chatTarget = ref('report_agent') |
| const showAgentDropdown = ref(false) |
| const selectedAgent = ref(null) |
| const selectedAgentIndex = ref(null) |
| const showFullProfile = ref(true) |
| const showToolsDetail = ref(true) |
| |
| |
| const chatInput = ref('') |
| const chatHistory = ref([]) |
| const chatHistoryCache = ref({}) |
| const isSending = ref(false) |
| const chatMessages = ref(null) |
| const chatInputRef = ref(null) |
| |
| |
| const selectedAgents = ref(new Set()) |
| const surveyQuestion = ref('') |
| const surveyResults = ref([]) |
| const isSurveying = ref(false) |
| |
| |
| const reportOutline = ref(null) |
| const generatedSections = ref({}) |
| const collapsedSections = ref(new Set()) |
| const currentSectionIndex = ref(null) |
| const profiles = ref([]) |
| |
| |
| const isSectionCompleted = (sectionIndex) => { |
| return !!generatedSections.value[sectionIndex] |
| } |
| |
| |
| const leftPanel = ref(null) |
| const rightPanel = ref(null) |
| |
| |
| const addLog = (msg) => { |
| emit('add-log', msg) |
| } |
| |
| const toggleSectionCollapse = (idx) => { |
| if (!generatedSections.value[idx + 1]) return |
| const newSet = new Set(collapsedSections.value) |
| if (newSet.has(idx)) { |
| newSet.delete(idx) |
| } else { |
| newSet.add(idx) |
| } |
| collapsedSections.value = newSet |
| } |
| |
| const selectChatTarget = (target) => { |
| chatTarget.value = target |
| if (target === 'report_agent') { |
| showAgentDropdown.value = false |
| } |
| } |
| |
| |
| const saveChatHistory = () => { |
| if (chatHistory.value.length === 0) return |
| |
| if (chatTarget.value === 'report_agent') { |
| chatHistoryCache.value['report_agent'] = [...chatHistory.value] |
| } else if (selectedAgentIndex.value !== null) { |
| chatHistoryCache.value[`agent_${selectedAgentIndex.value}`] = [...chatHistory.value] |
| } |
| } |
| |
| const selectReportAgentChat = () => { |
| |
| saveChatHistory() |
| |
| activeTab.value = 'chat' |
| chatTarget.value = 'report_agent' |
| selectedAgent.value = null |
| selectedAgentIndex.value = null |
| showAgentDropdown.value = false |
| |
| |
| chatHistory.value = chatHistoryCache.value['report_agent'] || [] |
| } |
| |
| const selectSurveyTab = () => { |
| activeTab.value = 'survey' |
| selectedAgent.value = null |
| selectedAgentIndex.value = null |
| showAgentDropdown.value = false |
| } |
| |
| const toggleAgentDropdown = () => { |
| showAgentDropdown.value = !showAgentDropdown.value |
| if (showAgentDropdown.value) { |
| activeTab.value = 'chat' |
| chatTarget.value = 'agent' |
| } |
| } |
| |
| const selectAgent = (agent, idx) => { |
| |
| saveChatHistory() |
| |
| selectedAgent.value = agent |
| selectedAgentIndex.value = idx |
| chatTarget.value = 'agent' |
| showAgentDropdown.value = false |
| |
| |
| chatHistory.value = chatHistoryCache.value[`agent_${idx}`] || [] |
| addLog(`选择对话对象: ${agent.username}`) |
| } |
| |
| const formatTime = (timestamp) => { |
| if (!timestamp) return '' |
| try { |
| return new Date(timestamp).toLocaleTimeString('en-US', { |
| hour12: false, |
| hour: '2-digit', |
| minute: '2-digit' |
| }) |
| } catch { |
| return '' |
| } |
| } |
| |
| const renderMarkdown = (content) => { |
| if (!content) return '' |
| |
| let processedContent = content.replace(/^##\s+.+\n+/, '') |
| let html = processedContent.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre class="code-block"><code>$2</code></pre>') |
| html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>') |
| html = html.replace(/^#### (.+)$/gm, '<h5 class="md-h5">$1</h5>') |
| html = html.replace(/^### (.+)$/gm, '<h4 class="md-h4">$1</h4>') |
| html = html.replace(/^## (.+)$/gm, '<h3 class="md-h3">$1</h3>') |
| html = html.replace(/^# (.+)$/gm, '<h2 class="md-h2">$1</h2>') |
| html = html.replace(/^> (.+)$/gm, '<blockquote class="md-quote">$1</blockquote>') |
| |
| |
| html = html.replace(/^(\s*)- (.+)$/gm, (match, indent, text) => { |
| const level = Math.floor(indent.length / 2) |
| return `<li class="md-li" data-level="${level}">${text}</li>` |
| }) |
| html = html.replace(/^(\s*)(\d+)\. (.+)$/gm, (match, indent, num, text) => { |
| const level = Math.floor(indent.length / 2) |
| return `<li class="md-oli" data-level="${level}">${text}</li>` |
| }) |
| |
| |
| html = html.replace(/(<li class="md-li"[^>]*>.*?<\/li>\s*)+/g, '<ul class="md-ul">$&</ul>') |
| |
| html = html.replace(/(<li class="md-oli"[^>]*>.*?<\/li>\s*)+/g, '<ol class="md-ol">$&</ol>') |
| |
| |
| html = html.replace(/<\/li>\s+<li/g, '</li><li') |
| |
| html = html.replace(/<ul class="md-ul">\s+/g, '<ul class="md-ul">') |
| html = html.replace(/<ol class="md-ol">\s+/g, '<ol class="md-ol">') |
| |
| html = html.replace(/\s+<\/ul>/g, '</ul>') |
| html = html.replace(/\s+<\/ol>/g, '</ol>') |
| |
| html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>') |
| html = html.replace(/\*(.+?)\*/g, '<em>$1</em>') |
| html = html.replace(/_(.+?)_/g, '<em>$1</em>') |
| html = html.replace(/^---$/gm, '<hr class="md-hr">') |
| html = html.replace(/\n\n/g, '</p><p class="md-p">') |
| html = html.replace(/\n/g, '<br>') |
| html = '<p class="md-p">' + html + '</p>' |
| html = html.replace(/<p class="md-p"><\/p>/g, '') |
| html = html.replace(/<p class="md-p">(<h[2-5])/g, '$1') |
| html = html.replace(/(<\/h[2-5]>)<\/p>/g, '$1') |
| html = html.replace(/<p class="md-p">(<ul|<ol|<blockquote|<pre|<hr)/g, '$1') |
| html = html.replace(/(<\/ul>|<\/ol>|<\/blockquote>|<\/pre>)<\/p>/g, '$1') |
| |
| html = html.replace(/<br>\s*(<ul|<ol|<blockquote)/g, '$1') |
| html = html.replace(/(<\/ul>|<\/ol>|<\/blockquote>)\s*<br>/g, '$1') |
| |
| html = html.replace(/<p class="md-p">(<br>\s*)+(<ul|<ol|<blockquote|<pre|<hr)/g, '$2') |
| |
| html = html.replace(/(<br>\s*){2,}/g, '<br>') |
| |
| html = html.replace(/(<\/ol>|<\/ul>|<\/blockquote>)<br>(<p|<div)/g, '$1$2') |
| |
| |
| const tokens = html.split(/(<ol class="md-ol">(?:<li class="md-oli"[^>]*>[\s\S]*?<\/li>)+<\/ol>)/g) |
| let olCounter = 0 |
| let inSequence = false |
| for (let i = 0; i < tokens.length; i++) { |
| if (tokens[i].startsWith('<ol class="md-ol">')) { |
| const liCount = (tokens[i].match(/<li class="md-oli"/g) || []).length |
| if (liCount === 1) { |
| olCounter++ |
| if (olCounter > 1) { |
| tokens[i] = tokens[i].replace('<ol class="md-ol">', `<ol class="md-ol" start="${olCounter}">`) |
| } |
| inSequence = true |
| } else { |
| olCounter = 0 |
| inSequence = false |
| } |
| } else if (inSequence) { |
| if (/<h[2-5]/.test(tokens[i])) { |
| olCounter = 0 |
| inSequence = false |
| } |
| } |
| } |
| html = tokens.join('') |
| |
| return html |
| } |
| |
| |
| const sendMessage = async () => { |
| if (!chatInput.value.trim() || isSending.value) return |
| |
| const message = chatInput.value.trim() |
| chatInput.value = '' |
| |
| |
| chatHistory.value.push({ |
| role: 'user', |
| content: message, |
| timestamp: new Date().toISOString() |
| }) |
| |
| scrollToBottom() |
| isSending.value = true |
| |
| try { |
| if (chatTarget.value === 'report_agent') { |
| await sendToReportAgent(message) |
| } else { |
| await sendToAgent(message) |
| } |
| } catch (err) { |
| addLog(`发送失败: ${err.message}`) |
| chatHistory.value.push({ |
| role: 'assistant', |
| content: `抱歉,发生了错误: ${err.message}`, |
| timestamp: new Date().toISOString() |
| }) |
| } finally { |
| isSending.value = false |
| scrollToBottom() |
| |
| saveChatHistory() |
| } |
| } |
| |
| const sendToReportAgent = async (message) => { |
| addLog(`向 Report Agent 发送: ${message.substring(0, 50)}...`) |
| |
| |
| const historyForApi = chatHistory.value |
| .filter(msg => msg.role !== 'user' || msg.content !== message) |
| .slice(-10) |
| .map(msg => ({ |
| role: msg.role, |
| content: msg.content |
| })) |
| |
| const res = await chatWithReport({ |
| simulation_id: props.simulationId, |
| message: message, |
| chat_history: historyForApi |
| }) |
| |
| if (res.success && res.data) { |
| chatHistory.value.push({ |
| role: 'assistant', |
| content: res.data.response || res.data.answer || '无响应', |
| timestamp: new Date().toISOString() |
| }) |
| addLog('Report Agent 已回复') |
| } else { |
| throw new Error(res.error || '请求失败') |
| } |
| } |
| |
| const sendToAgent = async (message) => { |
| if (!selectedAgent.value || selectedAgentIndex.value === null) { |
| throw new Error('请先选择一个模拟个体') |
| } |
| |
| addLog(`向 ${selectedAgent.value.username} 发送: ${message.substring(0, 50)}...`) |
| |
| |
| let prompt = message |
| if (chatHistory.value.length > 1) { |
| const historyContext = chatHistory.value |
| .filter(msg => msg.content !== message) |
| .slice(-6) |
| .map(msg => `${msg.role === 'user' ? '提问者' : '你'}:${msg.content}`) |
| .join('\n') |
| prompt = `以下是我们之前的对话:\n${historyContext}\n\n现在我的新问题是:${message}` |
| } |
| |
| const res = await interviewAgents({ |
| simulation_id: props.simulationId, |
| interviews: [{ |
| agent_id: selectedAgentIndex.value, |
| prompt: prompt |
| }] |
| }) |
| |
| if (res.success && res.data) { |
| |
| |
| const resultData = res.data.result || res.data |
| const resultsDict = resultData.results || resultData |
| |
| |
| let responseContent = null |
| const agentId = selectedAgentIndex.value |
| |
| if (typeof resultsDict === 'object' && !Array.isArray(resultsDict)) { |
| |
| const redditKey = `reddit_${agentId}` |
| const twitterKey = `twitter_${agentId}` |
| const agentResult = resultsDict[redditKey] || resultsDict[twitterKey] || Object.values(resultsDict)[0] |
| if (agentResult) { |
| responseContent = agentResult.response || agentResult.answer |
| } |
| } else if (Array.isArray(resultsDict) && resultsDict.length > 0) { |
| |
| responseContent = resultsDict[0].response || resultsDict[0].answer |
| } |
| |
| if (responseContent) { |
| chatHistory.value.push({ |
| role: 'assistant', |
| content: responseContent, |
| timestamp: new Date().toISOString() |
| }) |
| addLog(`${selectedAgent.value.username} 已回复`) |
| } else { |
| throw new Error('无响应数据') |
| } |
| } else { |
| throw new Error(res.error || '请求失败') |
| } |
| } |
| |
| const scrollToBottom = () => { |
| nextTick(() => { |
| if (chatMessages.value) { |
| chatMessages.value.scrollTop = chatMessages.value.scrollHeight |
| } |
| }) |
| } |
| |
| |
| const toggleAgentSelection = (idx) => { |
| const newSet = new Set(selectedAgents.value) |
| if (newSet.has(idx)) { |
| newSet.delete(idx) |
| } else { |
| newSet.add(idx) |
| } |
| selectedAgents.value = newSet |
| } |
| |
| const selectAllAgents = () => { |
| const newSet = new Set() |
| profiles.value.forEach((_, idx) => newSet.add(idx)) |
| selectedAgents.value = newSet |
| } |
| |
| const clearAgentSelection = () => { |
| selectedAgents.value = new Set() |
| } |
| |
| const submitSurvey = async () => { |
| if (selectedAgents.value.size === 0 || !surveyQuestion.value.trim()) return |
| |
| isSurveying.value = true |
| addLog(`发送问卷给 ${selectedAgents.value.size} 个对象...`) |
| |
| try { |
| const interviews = Array.from(selectedAgents.value).map(idx => ({ |
| agent_id: idx, |
| prompt: surveyQuestion.value.trim() |
| })) |
| |
| const res = await interviewAgents({ |
| simulation_id: props.simulationId, |
| interviews: interviews |
| }) |
| |
| if (res.success && res.data) { |
| |
| |
| const resultData = res.data.result || res.data |
| const resultsDict = resultData.results || resultData |
| |
| |
| const surveyResultsList = [] |
| |
| for (const interview of interviews) { |
| const agentIdx = interview.agent_id |
| const agent = profiles.value[agentIdx] |
| |
| |
| let responseContent = '无响应' |
| |
| if (typeof resultsDict === 'object' && !Array.isArray(resultsDict)) { |
| const redditKey = `reddit_${agentIdx}` |
| const twitterKey = `twitter_${agentIdx}` |
| const agentResult = resultsDict[redditKey] || resultsDict[twitterKey] |
| if (agentResult) { |
| responseContent = agentResult.response || agentResult.answer || '无响应' |
| } |
| } else if (Array.isArray(resultsDict)) { |
| |
| const matchedResult = resultsDict.find(r => r.agent_id === agentIdx) |
| if (matchedResult) { |
| responseContent = matchedResult.response || matchedResult.answer || '无响应' |
| } |
| } |
| |
| surveyResultsList.push({ |
| agent_id: agentIdx, |
| agent_name: agent?.username || `Agent ${agentIdx}`, |
| profession: agent?.profession, |
| question: surveyQuestion.value.trim(), |
| answer: responseContent |
| }) |
| } |
| |
| surveyResults.value = surveyResultsList |
| addLog(`收到 ${surveyResults.value.length} 条回复`) |
| } else { |
| throw new Error(res.error || '请求失败') |
| } |
| } catch (err) { |
| addLog(`问卷发送失败: ${err.message}`) |
| } finally { |
| isSurveying.value = false |
| } |
| } |
| |
| |
| const loadReportData = async () => { |
| if (!props.reportId) return |
| |
| try { |
| addLog(`加载报告数据: ${props.reportId}`) |
| |
| |
| const reportRes = await getReport(props.reportId) |
| if (reportRes.success && reportRes.data) { |
| |
| await loadAgentLogs() |
| } |
| } catch (err) { |
| addLog(`加载报告失败: ${err.message}`) |
| } |
| } |
| |
| const loadAgentLogs = async () => { |
| if (!props.reportId) return |
| |
| try { |
| const res = await getAgentLog(props.reportId, 0) |
| if (res.success && res.data) { |
| const logs = res.data.logs || [] |
| |
| logs.forEach(log => { |
| if (log.action === 'planning_complete' && log.details?.outline) { |
| reportOutline.value = log.details.outline |
| } |
| |
| if (log.action === 'section_complete' && log.section_index < 100 && log.details?.content) { |
| generatedSections.value[log.section_index] = log.details.content |
| } |
| }) |
| |
| addLog('报告数据加载完成') |
| } |
| } catch (err) { |
| addLog(`加载报告日志失败: ${err.message}`) |
| } |
| } |
| |
| const loadProfiles = async () => { |
| if (!props.simulationId) return |
| |
| try { |
| const res = await getSimulationProfilesRealtime(props.simulationId, 'reddit') |
| if (res.success && res.data) { |
| profiles.value = res.data.profiles || [] |
| addLog(`加载了 ${profiles.value.length} 个模拟个体`) |
| } |
| } catch (err) { |
| addLog(`加载模拟个体失败: ${err.message}`) |
| } |
| } |
| |
| |
| const handleClickOutside = (e) => { |
| const dropdown = document.querySelector('.agent-dropdown') |
| if (dropdown && !dropdown.contains(e.target)) { |
| showAgentDropdown.value = false |
| } |
| } |
| |
| |
| onMounted(() => { |
| addLog('Step5 深度互动初始化') |
| loadReportData() |
| loadProfiles() |
| document.addEventListener('click', handleClickOutside) |
| }) |
| |
| onUnmounted(() => { |
| document.removeEventListener('click', handleClickOutside) |
| }) |
| |
| watch(() => props.reportId, (newId) => { |
| if (newId) { |
| loadReportData() |
| } |
| }, { immediate: true }) |
| |
| watch(() => props.simulationId, (newId) => { |
| if (newId) { |
| loadProfiles() |
| } |
| }, { immediate: true }) |
| </script> |
| |
| <style scoped> |
| .interaction-panel { |
| height: 100%; |
| display: flex; |
| flex-direction: column; |
| background: #F8F9FA; |
| font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif; |
| overflow: hidden; |
| } |
| |
| |
| .mono { |
| font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', 'Consolas', monospace; |
| } |
| |
| |
| .main-split-layout { |
| flex: 1; |
| display: flex; |
| overflow: hidden; |
| } |
| |
| |
| .left-panel.report-style { |
| width: 45%; |
| min-width: 450px; |
| background: #FFFFFF; |
| border-right: 1px solid #E5E7EB; |
| overflow-y: auto; |
| display: flex; |
| flex-direction: column; |
| padding: 30px 50px 60px 50px; |
| } |
| |
| .left-panel::-webkit-scrollbar { |
| width: 6px; |
| } |
| |
| .left-panel::-webkit-scrollbar-track { |
| background: transparent; |
| } |
| |
| .left-panel::-webkit-scrollbar-thumb { |
| background: transparent; |
| border-radius: 3px; |
| transition: background 0.3s ease; |
| } |
| |
| .left-panel:hover::-webkit-scrollbar-thumb { |
| background: rgba(0, 0, 0, 0.15); |
| } |
| |
| .left-panel::-webkit-scrollbar-thumb:hover { |
| background: rgba(0, 0, 0, 0.25); |
| } |
| |
| |
| .report-content-wrapper { |
| max-width: 800px; |
| margin: 0 auto; |
| width: 100%; |
| } |
| |
| .report-header-block { |
| margin-bottom: 30px; |
| } |
| |
| .report-meta { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| margin-bottom: 24px; |
| } |
| |
| .report-tag { |
| background: #000000; |
| color: #FFFFFF; |
| font-size: 11px; |
| font-weight: 700; |
| padding: 4px 8px; |
| letter-spacing: 0.05em; |
| text-transform: uppercase; |
| } |
| |
| .report-id { |
| font-size: 11px; |
| color: #9CA3AF; |
| font-weight: 500; |
| letter-spacing: 0.02em; |
| } |
| |
| .main-title { |
| font-family: 'Times New Roman', Times, serif; |
| font-size: 36px; |
| font-weight: 700; |
| color: #111827; |
| line-height: 1.2; |
| margin: 0 0 16px 0; |
| letter-spacing: -0.02em; |
| } |
| |
| .sub-title { |
| font-family: 'Times New Roman', Times, serif; |
| font-size: 16px; |
| color: #6B7280; |
| font-style: italic; |
| line-height: 1.6; |
| margin: 0 0 30px 0; |
| font-weight: 400; |
| } |
| |
| .header-divider { |
| height: 1px; |
| background: #E5E7EB; |
| width: 100%; |
| } |
| |
| |
| .sections-list { |
| display: flex; |
| flex-direction: column; |
| gap: 32px; |
| } |
| |
| .report-section-item { |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| .section-header-row { |
| display: flex; |
| align-items: baseline; |
| gap: 12px; |
| transition: background-color 0.2s ease; |
| padding: 8px 12px; |
| margin: -8px -12px; |
| border-radius: 8px; |
| } |
| |
| .section-header-row.clickable { |
| cursor: pointer; |
| } |
| |
| .section-header-row.clickable:hover { |
| background-color: #F9FAFB; |
| } |
| |
| .collapse-icon { |
| margin-left: auto; |
| color: #9CA3AF; |
| transition: transform 0.3s ease; |
| flex-shrink: 0; |
| align-self: center; |
| } |
| |
| .collapse-icon.is-collapsed { |
| transform: rotate(-90deg); |
| } |
| |
| .section-number { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 16px; |
| color: #E5E7EB; |
| font-weight: 500; |
| transition: color 0.3s ease; |
| } |
| |
| .section-title { |
| font-family: 'Times New Roman', Times, serif; |
| font-size: 24px; |
| font-weight: 600; |
| color: #111827; |
| margin: 0; |
| transition: color 0.3s ease; |
| } |
| |
| |
| .report-section-item.is-pending .section-number { |
| color: #E5E7EB; |
| } |
| .report-section-item.is-pending .section-title { |
| color: #D1D5DB; |
| } |
| |
| .report-section-item.is-active .section-number, |
| .report-section-item.is-completed .section-number { |
| color: #9CA3AF; |
| } |
| |
| .report-section-item.is-active .section-title, |
| .report-section-item.is-completed .section-title { |
| color: #111827; |
| } |
| |
| .section-body { |
| padding-left: 28px; |
| overflow: hidden; |
| } |
| |
| |
| .generated-content { |
| font-family: 'Inter', 'Noto Sans SC', system-ui, sans-serif; |
| font-size: 14px; |
| line-height: 1.8; |
| color: #374151; |
| } |
| |
| .generated-content :deep(p) { |
| margin-bottom: 1em; |
| } |
| |
| .generated-content :deep(.md-h2), |
| .generated-content :deep(.md-h3), |
| .generated-content :deep(.md-h4) { |
| font-family: 'Times New Roman', Times, serif; |
| color: #111827; |
| margin-top: 1.5em; |
| margin-bottom: 0.8em; |
| font-weight: 700; |
| } |
| |
| .generated-content :deep(.md-h2) { font-size: 20px; border-bottom: 1px solid #F3F4F6; padding-bottom: 8px; } |
| .generated-content :deep(.md-h3) { font-size: 18px; } |
| .generated-content :deep(.md-h4) { font-size: 16px; } |
| |
| .generated-content :deep(.md-ul), |
| .generated-content :deep(.md-ol) { |
| padding-left: 20px; |
| margin-bottom: 1em; |
| } |
| |
| .generated-content :deep(.md-li) { |
| margin-bottom: 0.5em; |
| } |
| |
| .generated-content :deep(.md-quote) { |
| border-left: 3px solid #E5E7EB; |
| padding-left: 16px; |
| margin: 1.5em 0; |
| color: #6B7280; |
| font-style: italic; |
| font-family: 'Times New Roman', Times, serif; |
| } |
| |
| .generated-content :deep(.code-block) { |
| background: #F9FAFB; |
| padding: 12px; |
| border-radius: 6px; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 12px; |
| overflow-x: auto; |
| margin: 1em 0; |
| border: 1px solid #E5E7EB; |
| } |
| |
| .generated-content :deep(strong) { |
| font-weight: 600; |
| color: #111827; |
| } |
| |
| |
| .loading-state { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| color: #6B7280; |
| font-size: 14px; |
| margin-top: 4px; |
| } |
| |
| .loading-icon { |
| width: 18px; |
| height: 18px; |
| animation: spin 1s linear infinite; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .loading-text { |
| font-family: 'Times New Roman', Times, serif; |
| font-size: 15px; |
| color: #4B5563; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| |
| .generated-content :deep(.md-h2) { |
| font-family: 'Times New Roman', Times, serif; |
| font-size: 18px; |
| margin-top: 0; |
| } |
| |
| |
| .waiting-placeholder { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 20px; |
| padding: 40px; |
| color: #9CA3AF; |
| } |
| |
| .waiting-animation { |
| position: relative; |
| width: 48px; |
| height: 48px; |
| } |
| |
| .waiting-ring { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| border: 2px solid #E5E7EB; |
| border-radius: 50%; |
| animation: ripple 2s cubic-bezier(0.4, 0, 0.2, 1) infinite; |
| } |
| |
| .waiting-ring:nth-child(2) { |
| animation-delay: 0.4s; |
| } |
| |
| .waiting-ring:nth-child(3) { |
| animation-delay: 0.8s; |
| } |
| |
| @keyframes ripple { |
| 0% { transform: scale(0.5); opacity: 1; } |
| 100% { transform: scale(2); opacity: 0; } |
| } |
| |
| .waiting-text { |
| font-size: 14px; |
| } |
| |
| |
| .right-panel { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| background: #FFFFFF; |
| overflow: hidden; |
| } |
| |
| |
| .action-bar { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 14px 20px; |
| border-bottom: 1px solid #E5E7EB; |
| background: linear-gradient(180deg, #FFFFFF 0%, #FAFBFC 100%); |
| gap: 16px; |
| } |
| |
| .action-bar-header { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| min-width: 160px; |
| } |
| |
| .action-bar-icon { |
| color: #1F2937; |
| flex-shrink: 0; |
| } |
| |
| .action-bar-text { |
| display: flex; |
| flex-direction: column; |
| gap: 2px; |
| } |
| |
| .action-bar-title { |
| font-size: 13px; |
| font-weight: 600; |
| color: #1F2937; |
| letter-spacing: -0.01em; |
| } |
| |
| .action-bar-subtitle { |
| font-size: 11px; |
| color: #9CA3AF; |
| } |
| |
| .action-bar-subtitle.mono { |
| font-family: 'JetBrains Mono', 'SF Mono', monospace; |
| } |
| |
| .action-bar-tabs { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| flex: 1; |
| justify-content: flex-end; |
| } |
| |
| .tab-pill { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| padding: 8px 14px; |
| font-size: 12px; |
| font-weight: 500; |
| color: #6B7280; |
| background: #F3F4F6; |
| border: 1px solid transparent; |
| border-radius: 20px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| white-space: nowrap; |
| } |
| |
| .tab-pill:hover { |
| background: #E5E7EB; |
| color: #374151; |
| } |
| |
| .tab-pill.active { |
| background: #1F2937; |
| color: #FFFFFF; |
| box-shadow: 0 2px 8px rgba(31, 41, 55, 0.15); |
| } |
| |
| .tab-pill svg { |
| flex-shrink: 0; |
| opacity: 0.7; |
| } |
| |
| .tab-pill.active svg { |
| opacity: 1; |
| } |
| |
| .tab-divider { |
| width: 1px; |
| height: 24px; |
| background: #E5E7EB; |
| margin: 0 6px; |
| } |
| |
| .agent-pill { |
| width: 200px; |
| justify-content: space-between; |
| } |
| |
| .agent-pill span { |
| flex: 1; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| text-align: left; |
| } |
| |
| .survey-pill { |
| background: #ECFDF5; |
| color: #047857; |
| } |
| |
| .survey-pill:hover { |
| background: #D1FAE5; |
| color: #065F46; |
| } |
| |
| .survey-pill.active { |
| background: #047857; |
| color: #FFFFFF; |
| box-shadow: 0 2px 8px rgba(4, 120, 87, 0.2); |
| } |
| |
| |
| .interaction-header { |
| padding: 16px 24px; |
| border-bottom: 1px solid #E5E7EB; |
| background: #FAFAFA; |
| } |
| |
| .tab-switcher { |
| display: flex; |
| gap: 8px; |
| } |
| |
| .tab-btn { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| padding: 10px 20px; |
| font-size: 13px; |
| font-weight: 600; |
| color: #6B7280; |
| background: transparent; |
| border: 1px solid #E5E7EB; |
| border-radius: 6px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .tab-btn:hover { |
| background: #F9FAFB; |
| border-color: #D1D5DB; |
| } |
| |
| .tab-btn.active { |
| background: #1F2937; |
| color: #FFFFFF; |
| border-color: #1F2937; |
| } |
| |
| .tab-btn svg { |
| flex-shrink: 0; |
| } |
| |
| |
| .chat-container { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| } |
| |
| |
| .report-agent-tools-card { |
| border-bottom: 1px solid #E5E7EB; |
| background: linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%); |
| } |
| |
| .tools-card-header { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 14px 20px; |
| } |
| |
| .tools-card-avatar { |
| width: 44px; |
| height: 44px; |
| min-width: 44px; |
| min-height: 44px; |
| background: linear-gradient(135deg, #1F2937 0%, #374151 100%); |
| color: #FFFFFF; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 18px; |
| font-weight: 600; |
| flex-shrink: 0; |
| box-shadow: 0 2px 8px rgba(31, 41, 55, 0.2); |
| } |
| |
| .tools-card-info { |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .tools-card-name { |
| font-size: 15px; |
| font-weight: 600; |
| color: #1F2937; |
| margin-bottom: 2px; |
| } |
| |
| .tools-card-subtitle { |
| font-size: 12px; |
| color: #6B7280; |
| } |
| |
| .tools-card-toggle { |
| width: 28px; |
| height: 28px; |
| background: #FFFFFF; |
| border: 1px solid #E5E7EB; |
| border-radius: 6px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: #6B7280; |
| transition: all 0.2s ease; |
| flex-shrink: 0; |
| } |
| |
| .tools-card-toggle:hover { |
| background: #F9FAFB; |
| border-color: #D1D5DB; |
| } |
| |
| .tools-card-toggle svg { |
| transition: transform 0.3s ease; |
| } |
| |
| .tools-card-toggle svg.is-expanded { |
| transform: rotate(180deg); |
| } |
| |
| .tools-card-body { |
| padding: 0 20px 16px 20px; |
| } |
| |
| .tools-grid { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: 10px; |
| } |
| |
| .tool-item { |
| display: flex; |
| gap: 10px; |
| padding: 12px; |
| background: #FFFFFF; |
| border-radius: 10px; |
| border: 1px solid #E5E7EB; |
| transition: all 0.2s ease; |
| } |
| |
| .tool-item:hover { |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
| } |
| |
| .tool-icon-wrapper { |
| width: 32px; |
| height: 32px; |
| min-width: 32px; |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| } |
| |
| .tool-purple .tool-icon-wrapper { |
| background: rgba(139, 92, 246, 0.1); |
| color: #8B5CF6; |
| } |
| |
| .tool-blue .tool-icon-wrapper { |
| background: rgba(59, 130, 246, 0.1); |
| color: #3B82F6; |
| } |
| |
| .tool-orange .tool-icon-wrapper { |
| background: rgba(249, 115, 22, 0.1); |
| color: #F97316; |
| } |
| |
| .tool-green .tool-icon-wrapper { |
| background: rgba(34, 197, 94, 0.1); |
| color: #22C55E; |
| } |
| |
| .tool-content { |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .tool-name { |
| font-size: 12px; |
| font-weight: 600; |
| color: #1F2937; |
| margin-bottom: 4px; |
| } |
| |
| .tool-desc { |
| font-size: 11px; |
| color: #6B7280; |
| line-height: 1.4; |
| display: -webkit-box; |
| -webkit-line-clamp: 2; |
| -webkit-box-orient: vertical; |
| overflow: hidden; |
| } |
| |
| |
| .agent-profile-card { |
| border-bottom: 1px solid #E5E7EB; |
| background: linear-gradient(135deg, #F8FAFC 0%, #F1F5F9 100%); |
| } |
| |
| .profile-card-header { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 14px 20px; |
| } |
| |
| .profile-card-avatar { |
| width: 44px; |
| height: 44px; |
| min-width: 44px; |
| min-height: 44px; |
| background: linear-gradient(135deg, #1F2937 0%, #374151 100%); |
| color: #FFFFFF; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 18px; |
| font-weight: 600; |
| flex-shrink: 0; |
| box-shadow: 0 2px 8px rgba(31, 41, 55, 0.2); |
| } |
| |
| .profile-card-info { |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .profile-card-name { |
| font-size: 15px; |
| font-weight: 600; |
| color: #1F2937; |
| margin-bottom: 2px; |
| } |
| |
| .profile-card-meta { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 12px; |
| color: #6B7280; |
| } |
| |
| .profile-card-handle { |
| color: #9CA3AF; |
| } |
| |
| .profile-card-profession { |
| padding: 2px 8px; |
| background: #E5E7EB; |
| border-radius: 4px; |
| font-size: 11px; |
| font-weight: 500; |
| } |
| |
| .profile-card-toggle { |
| width: 28px; |
| height: 28px; |
| background: #FFFFFF; |
| border: 1px solid #E5E7EB; |
| border-radius: 6px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: #6B7280; |
| transition: all 0.2s ease; |
| flex-shrink: 0; |
| } |
| |
| .profile-card-toggle:hover { |
| background: #F9FAFB; |
| border-color: #D1D5DB; |
| } |
| |
| .profile-card-toggle svg { |
| transition: transform 0.3s ease; |
| } |
| |
| .profile-card-toggle svg.is-expanded { |
| transform: rotate(180deg); |
| } |
| |
| .profile-card-body { |
| padding: 0 20px 16px 20px; |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| } |
| |
| .profile-card-label { |
| font-size: 11px; |
| font-weight: 600; |
| color: #9CA3AF; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| margin-bottom: 6px; |
| } |
| |
| .profile-card-bio { |
| background: #FFFFFF; |
| padding: 12px 14px; |
| border-radius: 8px; |
| border: 1px solid #E5E7EB; |
| } |
| |
| .profile-card-bio p { |
| margin: 0; |
| font-size: 13px; |
| line-height: 1.6; |
| color: #4B5563; |
| } |
| |
| |
| .target-selector { |
| padding: 16px 24px; |
| border-bottom: 1px solid #E5E7EB; |
| } |
| |
| .selector-label { |
| font-size: 11px; |
| font-weight: 600; |
| color: #9CA3AF; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| margin-bottom: 10px; |
| } |
| |
| .selector-options { |
| display: flex; |
| gap: 12px; |
| } |
| |
| .target-option { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| padding: 10px 16px; |
| font-size: 13px; |
| font-weight: 500; |
| color: #374151; |
| background: #F9FAFB; |
| border: 1px solid #E5E7EB; |
| border-radius: 6px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .target-option:hover { |
| border-color: #D1D5DB; |
| } |
| |
| .target-option.active { |
| background: #1F2937; |
| color: #FFFFFF; |
| border-color: #1F2937; |
| } |
| |
| |
| .agent-dropdown { |
| position: relative; |
| } |
| |
| .dropdown-arrow { |
| margin-left: 4px; |
| transition: transform 0.2s ease; |
| opacity: 0.6; |
| } |
| |
| .dropdown-arrow.open { |
| transform: rotate(180deg); |
| } |
| |
| .dropdown-menu { |
| position: absolute; |
| top: calc(100% + 6px); |
| left: 50%; |
| transform: translateX(-50%); |
| min-width: 240px; |
| background: #FFFFFF; |
| border: 1px solid #E5E7EB; |
| border-radius: 12px; |
| box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06); |
| max-height: 320px; |
| overflow-y: auto; |
| z-index: 100; |
| } |
| |
| .dropdown-header { |
| padding: 12px 16px 8px; |
| font-size: 11px; |
| font-weight: 600; |
| color: #9CA3AF; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| border-bottom: 1px solid #F3F4F6; |
| } |
| |
| .dropdown-item { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 10px 16px; |
| cursor: pointer; |
| transition: all 0.15s ease; |
| border-left: 3px solid transparent; |
| } |
| |
| .dropdown-item:hover { |
| background: #F9FAFB; |
| border-left-color: #1F2937; |
| } |
| |
| .dropdown-item:first-of-type { |
| margin-top: 4px; |
| } |
| |
| .dropdown-item:last-child { |
| margin-bottom: 4px; |
| } |
| |
| .agent-avatar { |
| width: 32px; |
| height: 32px; |
| min-width: 32px; |
| min-height: 32px; |
| background: linear-gradient(135deg, #1F2937 0%, #374151 100%); |
| color: #FFFFFF; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 12px; |
| font-weight: 600; |
| flex-shrink: 0; |
| box-shadow: 0 2px 4px rgba(31, 41, 55, 0.1); |
| } |
| |
| .agent-info { |
| display: flex; |
| flex-direction: column; |
| gap: 2px; |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .agent-name { |
| font-size: 13px; |
| font-weight: 600; |
| color: #1F2937; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .agent-role { |
| font-size: 11px; |
| color: #9CA3AF; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| |
| .chat-messages { |
| flex: 1; |
| overflow-y: auto; |
| padding: 24px; |
| display: flex; |
| flex-direction: column; |
| gap: 20px; |
| } |
| |
| .chat-empty { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 16px; |
| color: #9CA3AF; |
| } |
| |
| .empty-icon { |
| opacity: 0.3; |
| } |
| |
| .empty-text { |
| font-size: 14px; |
| text-align: center; |
| max-width: 280px; |
| line-height: 1.6; |
| } |
| |
| .chat-message { |
| display: flex; |
| gap: 12px; |
| } |
| |
| .chat-message.user { |
| flex-direction: row-reverse; |
| } |
| |
| .message-avatar { |
| width: 36px; |
| height: 36px; |
| min-width: 36px; |
| min-height: 36px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 14px; |
| font-weight: 600; |
| flex-shrink: 0; |
| } |
| |
| .chat-message.user .message-avatar { |
| background: #1F2937; |
| color: #FFFFFF; |
| } |
| |
| .chat-message.assistant .message-avatar { |
| background: #F3F4F6; |
| color: #374151; |
| } |
| |
| .message-content { |
| max-width: 70%; |
| display: flex; |
| flex-direction: column; |
| gap: 6px; |
| } |
| |
| .chat-message.user .message-content { |
| align-items: flex-end; |
| } |
| |
| .message-header { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .chat-message.user .message-header { |
| flex-direction: row-reverse; |
| } |
| |
| .sender-name { |
| font-size: 12px; |
| font-weight: 600; |
| color: #374151; |
| } |
| |
| .message-time { |
| font-size: 11px; |
| color: #9CA3AF; |
| } |
| |
| .message-text { |
| padding: 10px 14px; |
| border-radius: 12px; |
| font-size: 14px; |
| line-height: 1.5; |
| } |
| |
| .chat-message.user .message-text { |
| background: #1F2937; |
| color: #FFFFFF; |
| border-bottom-right-radius: 4px; |
| } |
| |
| .chat-message.assistant .message-text { |
| background: #F3F4F6; |
| color: #374151; |
| border-bottom-left-radius: 4px; |
| } |
| |
| .message-text :deep(.md-p) { |
| margin: 0; |
| } |
| |
| .message-text :deep(.md-p:last-child) { |
| margin-bottom: 0; |
| } |
| |
| |
| .message-text { |
| counter-reset: list-counter; |
| } |
| |
| .message-text :deep(.md-ol) { |
| list-style: none; |
| padding-left: 0; |
| margin: 8px 0; |
| } |
| |
| .message-text :deep(.md-oli) { |
| counter-increment: list-counter; |
| display: flex; |
| gap: 8px; |
| margin: 4px 0; |
| } |
| |
| .message-text :deep(.md-oli)::before { |
| content: counter(list-counter) "."; |
| font-weight: 600; |
| color: #374151; |
| min-width: 20px; |
| flex-shrink: 0; |
| } |
| |
| |
| .message-text :deep(.md-ul) { |
| padding-left: 20px; |
| margin: 8px 0; |
| } |
| |
| .message-text :deep(.md-li) { |
| margin: 4px 0; |
| } |
| |
| |
| .typing-indicator { |
| display: flex; |
| gap: 4px; |
| padding: 10px 14px; |
| background: #F3F4F6; |
| border-radius: 12px; |
| border-bottom-left-radius: 4px; |
| } |
| |
| .typing-indicator span { |
| width: 8px; |
| height: 8px; |
| background: #9CA3AF; |
| border-radius: 50%; |
| animation: typing 1.4s infinite ease-in-out; |
| } |
| |
| .typing-indicator span:nth-child(1) { animation-delay: 0s; } |
| .typing-indicator span:nth-child(2) { animation-delay: 0.2s; } |
| .typing-indicator span:nth-child(3) { animation-delay: 0.4s; } |
| |
| @keyframes typing { |
| 0%, 60%, 100% { transform: translateY(0); } |
| 30% { transform: translateY(-8px); } |
| } |
| |
| |
| .chat-input-area { |
| padding: 16px 24px; |
| border-top: 1px solid #E5E7EB; |
| display: flex; |
| gap: 12px; |
| align-items: flex-end; |
| } |
| |
| .chat-input { |
| flex: 1; |
| padding: 12px 16px; |
| font-size: 14px; |
| border: 1px solid #E5E7EB; |
| border-radius: 8px; |
| resize: none; |
| font-family: inherit; |
| line-height: 1.5; |
| transition: border-color 0.2s ease; |
| } |
| |
| .chat-input:focus { |
| outline: none; |
| border-color: #1F2937; |
| } |
| |
| .chat-input:disabled { |
| background: #F9FAFB; |
| cursor: not-allowed; |
| } |
| |
| .send-btn { |
| width: 44px; |
| height: 44px; |
| background: #1F2937; |
| color: #FFFFFF; |
| border: none; |
| border-radius: 8px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: background 0.2s ease; |
| } |
| |
| .send-btn:hover:not(:disabled) { |
| background: #374151; |
| } |
| |
| .send-btn:disabled { |
| background: #E5E7EB; |
| color: #9CA3AF; |
| cursor: not-allowed; |
| } |
| |
| |
| .survey-container { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| } |
| |
| .survey-setup { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| padding: 24px; |
| border-bottom: 1px solid #E5E7EB; |
| overflow: hidden; |
| } |
| |
| .setup-section { |
| margin-bottom: 24px; |
| } |
| |
| .setup-section:first-child { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| min-height: 0; |
| } |
| |
| .setup-section:last-child { |
| margin-bottom: 0; |
| } |
| |
| .section-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 12px; |
| } |
| |
| .setup-section .section-header .section-title { |
| font-size: 13px; |
| font-weight: 600; |
| color: #374151; |
| } |
| |
| .selection-count { |
| font-size: 12px; |
| color: #9CA3AF; |
| } |
| |
| |
| .agents-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
| gap: 10px; |
| flex: 1; |
| overflow-y: auto; |
| padding: 4px; |
| align-content: start; |
| } |
| |
| .agent-checkbox { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| padding: 10px 12px; |
| background: #F9FAFB; |
| border: 1px solid #E5E7EB; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .agent-checkbox:hover { |
| border-color: #D1D5DB; |
| } |
| |
| .agent-checkbox.checked { |
| background: #F0FDF4; |
| border-color: #10B981; |
| } |
| |
| .agent-checkbox input { |
| display: none; |
| } |
| |
| .checkbox-avatar { |
| width: 28px; |
| height: 28px; |
| min-width: 28px; |
| min-height: 28px; |
| background: #E5E7EB; |
| color: #374151; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 11px; |
| font-weight: 600; |
| flex-shrink: 0; |
| } |
| |
| .agent-checkbox.checked .checkbox-avatar { |
| background: #10B981; |
| color: #FFFFFF; |
| } |
| |
| .checkbox-info { |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .checkbox-name { |
| display: block; |
| font-size: 12px; |
| font-weight: 600; |
| color: #1F2937; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .checkbox-role { |
| display: block; |
| font-size: 10px; |
| color: #9CA3AF; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .checkbox-indicator { |
| width: 20px; |
| height: 20px; |
| border: 2px solid #E5E7EB; |
| border-radius: 4px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| transition: all 0.2s ease; |
| } |
| |
| .agent-checkbox.checked .checkbox-indicator { |
| background: #10B981; |
| border-color: #10B981; |
| color: #FFFFFF; |
| } |
| |
| .checkbox-indicator svg { |
| opacity: 0; |
| transform: scale(0.5); |
| transition: all 0.2s ease; |
| } |
| |
| .agent-checkbox.checked .checkbox-indicator svg { |
| opacity: 1; |
| transform: scale(1); |
| } |
| |
| .selection-actions { |
| display: flex; |
| gap: 8px; |
| margin-top: 12px; |
| } |
| |
| .action-link { |
| font-size: 12px; |
| color: #6B7280; |
| background: none; |
| border: none; |
| cursor: pointer; |
| padding: 0; |
| } |
| |
| .action-link:hover { |
| color: #1F2937; |
| text-decoration: underline; |
| } |
| |
| .action-divider { |
| color: #E5E7EB; |
| } |
| |
| |
| .survey-input { |
| width: 100%; |
| padding: 14px 16px; |
| font-size: 14px; |
| border: 1px solid #E5E7EB; |
| border-radius: 8px; |
| resize: none; |
| font-family: inherit; |
| line-height: 1.5; |
| transition: border-color 0.2s ease; |
| } |
| |
| .survey-input:focus { |
| outline: none; |
| border-color: #1F2937; |
| } |
| |
| .survey-submit-btn { |
| width: 100%; |
| padding: 14px 24px; |
| font-size: 14px; |
| font-weight: 600; |
| color: #FFFFFF; |
| background: #1F2937; |
| border: none; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: background 0.2s ease; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| margin-top: 20px; |
| } |
| |
| .survey-submit-btn:hover:not(:disabled) { |
| background: #374151; |
| } |
| |
| .survey-submit-btn:disabled { |
| background: #E5E7EB; |
| color: #9CA3AF; |
| cursor: not-allowed; |
| } |
| |
| .loading-spinner { |
| width: 18px; |
| height: 18px; |
| border: 2px solid rgba(255, 255, 255, 0.3); |
| border-top-color: #FFFFFF; |
| border-radius: 50%; |
| animation: spin 0.8s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| |
| .survey-results { |
| flex: 1; |
| overflow-y: auto; |
| padding: 24px; |
| } |
| |
| .results-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 20px; |
| } |
| |
| .results-title { |
| font-size: 14px; |
| font-weight: 600; |
| color: #1F2937; |
| } |
| |
| .results-count { |
| font-size: 12px; |
| color: #9CA3AF; |
| } |
| |
| .results-list { |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| } |
| |
| .result-card { |
| background: #F9FAFB; |
| border: 1px solid #E5E7EB; |
| border-radius: 12px; |
| padding: 20px; |
| } |
| |
| .result-header { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| margin-bottom: 12px; |
| } |
| |
| .result-avatar { |
| width: 36px; |
| height: 36px; |
| min-width: 36px; |
| min-height: 36px; |
| background: #1F2937; |
| color: #FFFFFF; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 14px; |
| font-weight: 600; |
| flex-shrink: 0; |
| } |
| |
| .result-info { |
| display: flex; |
| flex-direction: column; |
| gap: 2px; |
| } |
| |
| .result-name { |
| font-size: 14px; |
| font-weight: 600; |
| color: #1F2937; |
| } |
| |
| .result-role { |
| font-size: 12px; |
| color: #9CA3AF; |
| } |
| |
| .result-question { |
| display: flex; |
| align-items: flex-start; |
| gap: 8px; |
| padding: 12px 14px; |
| background: #FFFFFF; |
| border-radius: 8px; |
| margin-bottom: 12px; |
| font-size: 13px; |
| color: #6B7280; |
| } |
| |
| .result-question svg { |
| flex-shrink: 0; |
| margin-top: 2px; |
| } |
| |
| .result-answer { |
| font-size: 14px; |
| line-height: 1.7; |
| color: #374151; |
| } |
| |
| |
| :deep(.md-p) { |
| margin: 0 0 12px 0; |
| } |
| |
| :deep(.md-h2) { |
| font-size: 20px; |
| font-weight: 700; |
| color: #1F2937; |
| margin: 24px 0 12px 0; |
| } |
| |
| :deep(.md-h3) { |
| font-size: 16px; |
| font-weight: 600; |
| color: #374151; |
| margin: 20px 0 10px 0; |
| } |
| |
| :deep(.md-h4) { |
| font-size: 14px; |
| font-weight: 600; |
| color: #4B5563; |
| margin: 16px 0 8px 0; |
| } |
| |
| :deep(.md-h5) { |
| font-size: 13px; |
| font-weight: 600; |
| color: #6B7280; |
| margin: 12px 0 6px 0; |
| } |
| |
| :deep(.md-ul), :deep(.md-ol) { |
| margin: 12px 0; |
| padding-left: 24px; |
| } |
| |
| :deep(.md-li), :deep(.md-oli) { |
| margin: 6px 0; |
| } |
| |
| |
| .chat-messages :deep(.md-quote), |
| .result-answer :deep(.md-quote) { |
| margin: 12px 0; |
| padding: 12px 16px; |
| background: #F9FAFB; |
| border-left: 3px solid #1F2937; |
| color: #4B5563; |
| } |
| |
| :deep(.code-block) { |
| margin: 12px 0; |
| padding: 12px 16px; |
| background: #1F2937; |
| border-radius: 6px; |
| overflow-x: auto; |
| } |
| |
| :deep(.code-block code) { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 13px; |
| color: #E5E7EB; |
| } |
| |
| :deep(.inline-code) { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 13px; |
| background: #F3F4F6; |
| padding: 2px 6px; |
| border-radius: 4px; |
| color: #1F2937; |
| } |
| |
| :deep(.md-hr) { |
| border: none; |
| border-top: 1px solid #E5E7EB; |
| margin: 24px 0; |
| } |
| </style> |
| |