MicroFish/frontend/src/components/Step5Interaction.vue

2182 lines
50 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="interaction-panel">
<!-- Main Split Layout -->
<div class="main-split-layout">
<!-- LEFT PANEL: Report Style -->
<div class="left-panel report-style" ref="leftPanel">
<div v-if="reportOutline" class="report-content-wrapper">
<!-- Report Header -->
<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>
<!-- Sections List -->
<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) }">
<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)">
<!-- Completed Content -->
<div v-if="generatedSections[idx + 1]" class="generated-content" v-html="renderMarkdown(generatedSections[idx + 1])"></div>
<!-- Loading State -->
<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>
<!-- Waiting State -->
<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>
<!-- RIGHT PANEL: Interaction Interface -->
<div class="right-panel" ref="rightPanel">
<!-- Unified Action Bar - Professional Design -->
<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>
<!-- Chat Mode -->
<div v-if="activeTab === 'chat'" class="chat-container">
<!-- Agent Profile Card -->
<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>
<!-- Chat Messages -->
<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>
<!-- Chat Input -->
<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>
<!-- Survey Mode -->
<div v-if="activeTab === 'survey'" class="survey-container">
<!-- Survey Setup -->
<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>
<!-- Survey Results -->
<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'])
// State
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)
// Chat State
const chatInput = ref('')
const chatHistory = ref([])
const isSending = ref(false)
const chatMessages = ref(null)
const chatInputRef = ref(null)
// Survey State
const selectedAgents = ref(new Set())
const surveyQuestion = ref('')
const surveyResults = ref([])
const isSurveying = ref(false)
// Report Data
const reportOutline = ref(null)
const generatedSections = ref({})
const collapsedSections = ref(new Set())
const currentSectionIndex = ref(null)
const profiles = ref([])
// Helper Methods
const isSectionCompleted = (sectionIndex) => {
return !!generatedSections.value[sectionIndex]
}
// Refs
const leftPanel = ref(null)
const rightPanel = ref(null)
// Methods
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 selectReportAgentChat = () => {
activeTab.value = 'chat'
chatTarget.value = 'report_agent'
selectedAgent.value = null
selectedAgentIndex.value = null
showAgentDropdown.value = false
chatHistory.value = []
}
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) => {
selectedAgent.value = agent
selectedAgentIndex.value = idx
chatTarget.value = 'agent'
showAgentDropdown.value = false
chatHistory.value = [] // Reset chat history for new agent
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(/^- (.+)$/gm, '<li class="md-li">$1</li>')
html = html.replace(/(<li class="md-li">[\s\S]*?<\/li>)(\s*<li)/g, '$1$2')
html = html.replace(/(<li class="md-li">.*<\/li>)+/g, '<ul class="md-ul">$&</ul>')
html = html.replace(/^\d+\. (.+)$/gm, '<li class="md-oli">$1</li>')
html = html.replace(/(<li class="md-oli">.*<\/li>)+/g, '<ol class="md-ol">$&</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')
return html
}
// Chat Methods
const sendMessage = async () => {
if (!chatInput.value.trim() || isSending.value) return
const message = chatInput.value.trim()
chatInput.value = ''
// Add user message
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()
}
}
const sendToReportAgent = async (message) => {
addLog(`向 Report Agent 发送: ${message.substring(0, 50)}...`)
// Build chat history for API
const historyForApi = chatHistory.value
.filter(msg => msg.role !== 'user' || msg.content !== message)
.slice(-10) // Keep last 10 messages
.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)}...`)
// Build prompt with chat history
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 results = res.data.results || res.data
if (results && results.length > 0) {
chatHistory.value.push({
role: 'assistant',
content: results[0].response || results[0].answer || '无响应',
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
}
})
}
// Survey Methods
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 results = res.data.results || res.data
surveyResults.value = results.map((result, i) => {
const agentIdx = interviews[i].agent_id
const agent = profiles.value[agentIdx]
return {
agent_id: agentIdx,
agent_name: agent?.username || `Agent ${agentIdx}`,
profession: agent?.profession,
question: surveyQuestion.value.trim(),
answer: result.response || result.answer || '无响应'
}
})
addLog(`收到 ${surveyResults.value.length} 条回复`)
} else {
throw new Error(res.error || '请求失败')
}
} catch (err) {
addLog(`问卷发送失败: ${err.message}`)
} finally {
isSurveying.value = false
}
}
// Load Report Data
const loadReportData = async () => {
if (!props.reportId) return
try {
addLog(`加载报告数据: ${props.reportId}`)
// Get report info
const reportRes = await getReport(props.reportId)
if (reportRes.success && reportRes.data) {
// Load agent logs to get report outline and sections
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}`)
}
}
// Click outside to close dropdown
const handleClickOutside = (e) => {
const dropdown = document.querySelector('.agent-dropdown')
if (dropdown && !dropdown.contains(e.target)) {
showAgentDropdown.value = false
}
}
// Lifecycle
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', -apple-system, BlinkMacSystemFont, sans-serif;
overflow: hidden;
}
/* Utility Classes */
.mono {
font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', 'Consolas', monospace;
}
/* Main Split Layout */
.main-split-layout {
flex: 1;
display: flex;
overflow: hidden;
}
/* Left Panel - Report Style (与 Step4Report.vue 完全一致) */
.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 Header */
.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 */
.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;
}
/* States */
.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 */
.generated-content {
font-family: 'Inter', -apple-system, 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 */
.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); }
}
/* Content Styles Override */
.generated-content :deep(.md-h2) {
font-family: 'Times New Roman', Times, serif;
font-size: 18px;
margin-top: 0;
}
/* Waiting Placeholder */
.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 - Interaction */
.right-panel {
flex: 1;
display: flex;
flex-direction: column;
background: #FFFFFF;
overflow: hidden;
}
/* Action Bar - Professional Design */
.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 */
.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 */
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Agent Profile Card */
.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 */
.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 */
.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 */
.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: 12px 16px;
border-radius: 12px;
font-size: 14px;
line-height: 1.6;
}
.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;
}
/* Typing Indicator */
.typing-indicator {
display: flex;
gap: 4px;
padding: 12px 16px;
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 */
.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 */
.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 */
.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 */
.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 */
.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;
}
/* Markdown Styles */
: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>