feat(i18n): replace hardcoded Chinese in frontend components with i18n calls
Replace all user-visible hardcoded Chinese strings in 7 component files with $t() / t() calls using vue-i18n: - Step1GraphBuild: ontology generation, graph build, status badges - Step2EnvSetup: simulation setup, agent personas, platform config, time config, initial activation, modal profile details - Step3Simulation: report generation button - Step4Report: section loading text, interaction button - Step5Interaction: chat interface, survey UI, tool descriptions, error messages, agent selection - GraphPanel: graph status hints, loading states, tooltips - HistoryDatabase: history cards, modal, replay buttons Added missing translation keys to both zh.json and en.json locale files. Added useI18n imports to components that need script-level translations.
This commit is contained in:
@@ -58,7 +58,7 @@
|
||||
<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>
|
||||
<span class="loading-text">{{ $t('step4.generatingSection', { title: section.title }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,8 +85,8 @@
|
||||
<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>
|
||||
<span class="action-bar-title">{{ $t('step5.interactiveTools') }}</span>
|
||||
<span class="action-bar-subtitle mono">{{ $t('step5.agentsAvailable', { count: profiles.length }) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-bar-tabs">
|
||||
@@ -98,7 +98,7 @@
|
||||
<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>
|
||||
<span>{{ $t('step5.chatWithReportAgent') }}</span>
|
||||
</button>
|
||||
<div class="agent-dropdown" v-if="profiles.length > 0">
|
||||
<button
|
||||
@@ -110,13 +110,13 @@
|
||||
<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>
|
||||
<span>{{ selectedAgent ? selectedAgent.username : $t('step5.chatWithAgent') }}</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 class="dropdown-header">{{ $t('step5.selectChatTarget') }}</div>
|
||||
<div
|
||||
v-for="(agent, idx) in profiles"
|
||||
:key="idx"
|
||||
@@ -126,13 +126,13 @@
|
||||
<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>
|
||||
<span class="agent-role">{{ agent.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-divider"></div>
|
||||
<button
|
||||
<button
|
||||
class="tab-pill survey-pill"
|
||||
:class="{ active: activeTab === 'survey' }"
|
||||
@click="selectSurveyTab"
|
||||
@@ -141,7 +141,7 @@
|
||||
<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>
|
||||
<span>{{ $t('step5.sendSurvey') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,8 +154,8 @@
|
||||
<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 class="tools-card-name">{{ $t('step5.reportAgentChat') }}</div>
|
||||
<div class="tools-card-subtitle">{{ $t('step5.reportAgentDesc') }}</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">
|
||||
@@ -172,8 +172,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">InsightForge 深度归因</div>
|
||||
<div class="tool-desc">对齐现实世界种子数据与模拟环境状态,结合Global/Local Memory机制,提供跨时空的深度归因分析</div>
|
||||
<div class="tool-name">{{ $t('step5.toolInsightForge') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolInsightForgeDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-item tool-blue">
|
||||
@@ -184,8 +184,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">PanoramaSearch 全景追踪</div>
|
||||
<div class="tool-desc">基于图结构的广度遍历算法,重构事件传播路径,捕获全量信息流动的拓扑结构</div>
|
||||
<div class="tool-name">{{ $t('step5.toolPanoramaSearch') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolPanoramaSearchDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-item tool-orange">
|
||||
@@ -195,8 +195,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">QuickSearch 快速检索</div>
|
||||
<div class="tool-desc">基于 GraphRAG 的即时查询接口,优化索引效率,用于快速提取具体的节点属性与离散事实</div>
|
||||
<div class="tool-name">{{ $t('step5.toolQuickSearch') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolQuickSearchDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-item tool-green">
|
||||
@@ -208,8 +208,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="tool-content">
|
||||
<div class="tool-name">InterviewSubAgent 虚拟访谈</div>
|
||||
<div class="tool-desc">自主式访谈,能够并行与模拟世界中个体进行多轮对话,采集非结构化的观点数据与心理状态</div>
|
||||
<div class="tool-name">{{ $t('step5.toolInterviewSubAgent') }}</div>
|
||||
<div class="tool-desc">{{ $t('step5.toolInterviewSubAgentDesc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,7 +224,7 @@
|
||||
<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>
|
||||
<span class="profile-card-profession">{{ selectedAgent.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="profile-card-toggle" @click="showFullProfile = !showFullProfile">
|
||||
@@ -235,7 +235,7 @@
|
||||
</div>
|
||||
<div v-if="showFullProfile && selectedAgent.bio" class="profile-card-body">
|
||||
<div class="profile-card-bio">
|
||||
<div class="profile-card-label">简介</div>
|
||||
<div class="profile-card-label">{{ $t('step5.profileBio') }}</div>
|
||||
<p>{{ selectedAgent.bio }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -250,7 +250,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<p class="empty-text">
|
||||
{{ chatTarget === 'report_agent' ? '与 Report Agent 对话,深入了解报告内容' : '与模拟个体对话,了解他们的观点' }}
|
||||
{{ chatTarget === 'report_agent' ? $t('step5.chatEmptyReportAgent') : $t('step5.chatEmptyAgent') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
@@ -292,7 +292,7 @@
|
||||
<textarea
|
||||
v-model="chatInput"
|
||||
class="chat-input"
|
||||
placeholder="输入您的问题..."
|
||||
:placeholder="$t('step5.chatInputPlaceholder')"
|
||||
@keydown.enter.exact.prevent="sendMessage"
|
||||
:disabled="isSending || (!selectedAgent && chatTarget === 'agent')"
|
||||
rows="1"
|
||||
@@ -317,8 +317,8 @@
|
||||
<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>
|
||||
<span class="section-title">{{ $t('step5.selectSurveyTarget') }}</span>
|
||||
<span class="selection-count">{{ $t('step5.selectedCount', { selected: selectedAgents.size, total: profiles.length }) }}</span>
|
||||
</div>
|
||||
<div class="agents-grid">
|
||||
<label
|
||||
@@ -335,7 +335,7 @@
|
||||
<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>
|
||||
<span class="checkbox-role">{{ agent.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
<div class="checkbox-indicator">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="3">
|
||||
@@ -345,20 +345,20 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="selection-actions">
|
||||
<button class="action-link" @click="selectAllAgents">全选</button>
|
||||
<button class="action-link" @click="selectAllAgents">{{ $t('step5.selectAll') }}</button>
|
||||
<span class="action-divider">|</span>
|
||||
<button class="action-link" @click="clearAgentSelection">清空</button>
|
||||
<button class="action-link" @click="clearAgentSelection">{{ $t('step5.clearSelection') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setup-section">
|
||||
<div class="section-header">
|
||||
<span class="section-title">问卷问题</span>
|
||||
<span class="section-title">{{ $t('step5.surveyQuestions') }}</span>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="surveyQuestion"
|
||||
class="survey-input"
|
||||
placeholder="输入您想问所有被选中对象的问题..."
|
||||
:placeholder="$t('step5.surveyInputPlaceholder')"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
@@ -369,15 +369,15 @@
|
||||
@click="submitSurvey"
|
||||
>
|
||||
<span v-if="isSurveying" class="loading-spinner"></span>
|
||||
<span v-else>发送问卷</span>
|
||||
<span v-else>{{ $t('step5.submitSurvey') }}</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>
|
||||
<span class="results-title">{{ $t('step5.surveyResults') }}</span>
|
||||
<span class="results-count">{{ $t('step5.surveyResultsCount', { count: surveyResults.length }) }}</span>
|
||||
</div>
|
||||
<div class="results-list">
|
||||
<div
|
||||
@@ -389,7 +389,7 @@
|
||||
<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>
|
||||
<span class="result-role">{{ result.profession || $t('step2.unknownProfession') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-question">
|
||||
@@ -412,9 +412,12 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { chatWithReport, getReport, getAgentLog } from '../api/report'
|
||||
import { interviewAgents, getSimulationProfilesRealtime } from '../api/simulation'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
reportId: String,
|
||||
simulationId: String
|
||||
@@ -665,7 +668,7 @@ const sendMessage = async () => {
|
||||
addLog(`发送失败: ${err.message}`)
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content: `抱歉,发生了错误: ${err.message}`,
|
||||
content: t('step5.errorOccurred', { error: err.message }),
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} finally {
|
||||
@@ -697,18 +700,18 @@ const sendToReportAgent = async (message) => {
|
||||
if (res.success && res.data) {
|
||||
chatHistory.value.push({
|
||||
role: 'assistant',
|
||||
content: res.data.response || res.data.answer || '无响应',
|
||||
content: res.data.response || res.data.answer || t('step5.noResponse'),
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
addLog('Report Agent 已回复')
|
||||
} else {
|
||||
throw new Error(res.error || '请求失败')
|
||||
throw new Error(res.error || t('step5.requestFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
const sendToAgent = async (message) => {
|
||||
if (!selectedAgent.value || selectedAgentIndex.value === null) {
|
||||
throw new Error('请先选择一个模拟个体')
|
||||
throw new Error(t('step5.selectAgentFirst'))
|
||||
}
|
||||
|
||||
addLog(`向 ${selectedAgent.value.username} 发送: ${message.substring(0, 50)}...`)
|
||||
@@ -763,10 +766,10 @@ const sendToAgent = async (message) => {
|
||||
})
|
||||
addLog(`${selectedAgent.value.username} 已回复`)
|
||||
} else {
|
||||
throw new Error('无响应数据')
|
||||
throw new Error(t('step5.noResponse'))
|
||||
}
|
||||
} else {
|
||||
throw new Error(res.error || '请求失败')
|
||||
throw new Error(res.error || t('step5.requestFailed'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,20 +833,20 @@ const submitSurvey = async () => {
|
||||
const agent = profiles.value[agentIdx]
|
||||
|
||||
// 优先使用 reddit 平台回复,其次 twitter
|
||||
let responseContent = '无响应'
|
||||
|
||||
let responseContent = t('step5.noResponse')
|
||||
|
||||
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 || '无响应'
|
||||
responseContent = agentResult.response || agentResult.answer || t('step5.noResponse')
|
||||
}
|
||||
} else if (Array.isArray(resultsDict)) {
|
||||
// 兼容数组格式
|
||||
const matchedResult = resultsDict.find(r => r.agent_id === agentIdx)
|
||||
if (matchedResult) {
|
||||
responseContent = matchedResult.response || matchedResult.answer || '无响应'
|
||||
responseContent = matchedResult.response || matchedResult.answer || t('step5.noResponse')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,7 +862,7 @@ const submitSurvey = async () => {
|
||||
surveyResults.value = surveyResultsList
|
||||
addLog(`收到 ${surveyResults.value.length} 条回复`)
|
||||
} else {
|
||||
throw new Error(res.error || '请求失败')
|
||||
throw new Error(res.error || t('step5.requestFailed'))
|
||||
}
|
||||
} catch (err) {
|
||||
addLog(`问卷发送失败: ${err.message}`)
|
||||
|
||||
Reference in New Issue
Block a user