Translate frontend UI to English

This commit is contained in:
_Yusaki 2026-03-13 01:55:46 +07:00
parent 5f20558e78
commit d3121637c7
14 changed files with 485 additions and 485 deletions

View file

@ -4,11 +4,11 @@
<span class="panel-title">Graph Relationship Visualization</span>
<!-- 顶部工具栏 (Internal Top Right) -->
<div class="header-tools">
<button class="tool-btn" @click="$emit('refresh')" :disabled="loading" title="刷新图谱">
<button class="tool-btn" @click="$emit('refresh')" :disabled="loading" title="Refresh Graph">
<span class="icon-refresh" :class="{ 'spinning': loading }"></span>
<span class="btn-text">Refresh</span>
</button>
<button class="tool-btn" @click="$emit('toggle-maximize')" title="最大化/还原">
<button class="tool-btn" @click="$emit('toggle-maximize')" title="Maximize/Restore">
<span class="icon-maximize"></span>
</button>
</div>
@ -27,7 +27,7 @@
<path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-4.04z" />
</svg>
</div>
{{ isSimulating ? 'GraphRAG长短期记忆实时更新中' : '实时更新中...' }}
{{ isSimulating ? 'GraphRAG memory updating in real time' : 'Updating in real time...' }}
</div>
<!-- 模拟结束后的提示 -->
@ -39,8 +39,8 @@
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</div>
<span class="hint-text">还有少量内容处理中建议稍后手动刷新图谱</span>
<button class="hint-close-btn" @click="dismissFinishedHint" title="关闭提示">
<span class="hint-text">Some content is still being processed. Try refreshing the graph shortly.</span>
<button class="hint-close-btn" @click="dismissFinishedHint" title="Dismiss">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
@ -203,13 +203,13 @@
<!-- 加载状态 -->
<div v-else-if="loading" class="graph-state">
<div class="loading-spinner"></div>
<p>图谱数据加载中...</p>
<p>Loading graph data...</p>
</div>
<!-- 等待/空状态 -->
<div v-else class="graph-state">
<div class="empty-icon"></div>
<p class="empty-text">等待本体生成...</p>
<p class="empty-text">Waiting for ontology generation...</p>
</div>
</div>

View file

@ -13,7 +13,7 @@
<!-- 标题区域 -->
<div class="section-header">
<div class="section-line"></div>
<span class="section-title">推演记录</span>
<span class="section-title">Simulation History</span>
<div class="section-line"></div>
</div>
@ -36,16 +36,16 @@
<span
class="status-icon"
:class="{ available: project.project_id, unavailable: !project.project_id }"
title="图谱构建"
title="Graph Build"
></span>
<span
class="status-icon available"
title="环境搭建"
title="Env Setup"
></span>
<span
class="status-icon"
:class="{ available: project.report_id, unavailable: !project.report_id }"
title="分析报告"
title="Analysis Report"
></span>
</div>
</div>
@ -67,13 +67,13 @@
</div>
<!-- 如果有更多文件显示提示 -->
<div v-if="project.files.length > 3" class="files-more">
+{{ project.files.length - 3 }} 个文件
+{{ project.files.length - 3 }} more files
</div>
</div>
<!-- 无文件时的占位 -->
<div class="files-empty" v-else>
<span class="empty-file-icon"></span>
<span class="empty-file-text">暂无文件</span>
<span class="empty-file-text">No files</span>
</div>
</div>
@ -102,7 +102,7 @@
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<span class="loading-spinner"></span>
<span class="loading-text">加载中...</span>
<span class="loading-text">Loading...</span>
</div>
<!-- 历史回放详情弹窗 -->
@ -126,27 +126,27 @@
<div class="modal-body">
<!-- 模拟需求 -->
<div class="modal-section">
<div class="modal-label">模拟需求</div>
<div class="modal-requirement">{{ selectedProject.simulation_requirement || '' }}</div>
<div class="modal-label">Simulation Requirements</div>
<div class="modal-requirement">{{ selectedProject.simulation_requirement || 'None' }}</div>
</div>
<!-- 文件列表 -->
<div class="modal-section">
<div class="modal-label">关联文件</div>
<div class="modal-label">Associated Files</div>
<div class="modal-files" v-if="selectedProject.files && selectedProject.files.length > 0">
<div v-for="(file, index) in selectedProject.files" :key="index" class="modal-file-item">
<span class="file-tag" :class="getFileType(file.filename)">{{ getFileTypeLabel(file.filename) }}</span>
<span class="modal-file-name">{{ file.filename }}</span>
</div>
</div>
<div class="modal-empty" v-else>暂无关联文件</div>
<div class="modal-empty" v-else>No associated files</div>
</div>
</div>
<!-- 推演回放分割线 -->
<div class="modal-divider">
<span class="divider-line"></span>
<span class="divider-text">推演回放</span>
<span class="divider-text">Simulation Replay</span>
<span class="divider-line"></span>
</div>
@ -159,7 +159,7 @@
>
<span class="btn-step">Step1</span>
<span class="btn-icon"></span>
<span class="btn-text">图谱构建</span>
<span class="btn-text">Graph Build</span>
</button>
<button
class="modal-btn btn-simulation"
@ -167,7 +167,7 @@
>
<span class="btn-step">Step2</span>
<span class="btn-icon"></span>
<span class="btn-text">环境搭建</span>
<span class="btn-text">Env Setup</span>
</button>
<button
class="modal-btn btn-report"
@ -176,12 +176,12 @@
>
<span class="btn-step">Step4</span>
<span class="btn-icon"></span>
<span class="btn-text">分析报告</span>
<span class="btn-text">Report</span>
</button>
</div>
<!-- 不可回放提示 -->
<div class="modal-playback-hint">
<span class="hint-text">Step3开始模拟 Step5深度互动需在运行中启动不支持历史回放</span>
<span class="hint-text">Step 3 "Run Simulation" and Step 5 "Deep Interaction" must be started during a live run and cannot be replayed from history.</span>
</div>
</div>
</div>
@ -337,7 +337,7 @@ const truncateText = (text, maxLength) => {
// 20
const getSimulationTitle = (requirement) => {
if (!requirement) return '未命名模拟'
if (!requirement) return 'Untitled Simulation'
const title = requirement.slice(0, 20)
return requirement.length > 20 ? title + '...' : title
}
@ -353,8 +353,8 @@ const formatSimulationId = (simulationId) => {
const formatRounds = (simulation) => {
const current = simulation.current_round || 0
const total = simulation.total_rounds || 0
if (total === 0) return '未开始'
return `${current}/${total} `
if (total === 0) return 'Not Started'
return `${current}/${total} rounds`
}
//
@ -382,7 +382,7 @@ const getFileTypeLabel = (filename) => {
//
const truncateFilename = (filename, maxLength) => {
if (!filename) return '未知文件'
if (!filename) return 'Unknown file'
if (filename.length <= maxLength) return filename
const ext = filename.includes('.') ? '.' + filename.split('.').pop() : ''

View file

@ -6,25 +6,25 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">01</span>
<span class="step-title">本体生成</span>
<span class="step-title">Ontology Generation</span>
</div>
<div class="step-status">
<span v-if="currentPhase > 0" class="badge success">已完成</span>
<span v-else-if="currentPhase === 0" class="badge processing">生成中</span>
<span v-else class="badge pending">等待</span>
<span v-if="currentPhase > 0" class="badge success">Done</span>
<span v-else-if="currentPhase === 0" class="badge processing">Generating</span>
<span v-else class="badge pending">Waiting</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/graph/ontology/generate</p>
<p class="description">
LLM分析文档内容与模拟需求提取出现实种子自动生成合适的本体结构
LLM analyzes document content and simulation requirements, extracts reality seeds, and automatically generates a suitable ontology structure
</p>
<!-- Loading / Progress -->
<div v-if="currentPhase === 0 && ontologyProgress" class="progress-section">
<div class="spinner-sm"></div>
<span>{{ ontologyProgress.message || '正在分析文档...' }}</span>
<span>{{ ontologyProgress.message || 'Analyzing documents...' }}</span>
</div>
<!-- Detail Overlay -->
@ -110,34 +110,34 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">02</span>
<span class="step-title">GraphRAG构建</span>
<span class="step-title">GraphRAG Build</span>
</div>
<div class="step-status">
<span v-if="currentPhase > 1" class="badge success">已完成</span>
<span v-if="currentPhase > 1" class="badge success">Done</span>
<span v-else-if="currentPhase === 1" class="badge processing">{{ buildProgress?.progress || 0 }}%</span>
<span v-else class="badge pending">等待</span>
<span v-else class="badge pending">Waiting</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/graph/build</p>
<p class="description">
基于生成的本体将文档自动分块后调用 Zep 构建知识图谱提取实体和关系并形成时序记忆与社区摘要
Based on the generated ontology, documents are automatically chunked and sent to Zep to build a knowledge graph, extracting entities and relations with temporal memory and community summaries
</p>
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{ graphStats.nodes }}</span>
<span class="stat-label">实体节点</span>
<span class="stat-label">Entity Nodes</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ graphStats.edges }}</span>
<span class="stat-label">关系边</span>
<span class="stat-label">Relation Edges</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ graphStats.types }}</span>
<span class="stat-label">SCHEMA类型</span>
<span class="stat-label">Schema Types</span>
</div>
</div>
</div>
@ -148,23 +148,23 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">03</span>
<span class="step-title">构建完成</span>
<span class="step-title">Build Complete</span>
</div>
<div class="step-status">
<span v-if="currentPhase >= 2" class="badge accent">进行中</span>
<span v-if="currentPhase >= 2" class="badge accent">In Progress</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/simulation/create</p>
<p class="description">图谱构建已完成请进入下一步进行模拟环境搭建</p>
<p class="description">Graph build is complete. Proceed to the next step for simulation environment setup.</p>
<button
class="action-btn"
:disabled="currentPhase < 2 || creatingSimulation"
@click="handleEnterEnvSetup"
>
<span v-if="creatingSimulation" class="spinner-sm"></span>
{{ creatingSimulation ? '创建中...' : '进入环境搭建 ➝' }}
{{ creatingSimulation ? 'Creating...' : 'Enter Env Setup ➝' }}
</button>
</div>
</div>
@ -233,11 +233,11 @@ const handleEnterEnvSetup = async () => {
})
} else {
console.error('创建模拟失败:', res.error)
alert('创建模拟失败: ' + (res.error || '未知错误'))
alert('Failed to create simulation: ' + (res.error || 'Unknown error'))
}
} catch (err) {
console.error('创建模拟异常:', err)
alert('创建模拟异常: ' + err.message)
alert('Simulation creation error: ' + err.message)
} finally {
creatingSimulation.value = false
}

View file

@ -6,18 +6,18 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">01</span>
<span class="step-title">模拟实例初始化</span>
<span class="step-title">Simulation Instance Init</span>
</div>
<div class="step-status">
<span v-if="phase > 0" class="badge success">已完成</span>
<span v-else class="badge processing">初始化</span>
<span v-if="phase > 0" class="badge success">Complete</span>
<span v-else class="badge processing">Initializing</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/simulation/create</p>
<p class="description">
新建simulation实例拉取模拟世界参数模版
Create a new simulation instance and pull world parameter templates
</p>
<div v-if="simulationId" class="info-card">
@ -35,7 +35,7 @@
</div>
<div class="info-row">
<span class="info-label">Task ID</span>
<span class="info-value mono">{{ taskId || '异步任务已完成' }}</span>
<span class="info-value mono">{{ taskId || 'Async task complete' }}</span>
</div>
</div>
</div>
@ -46,41 +46,41 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">02</span>
<span class="step-title">生成 Agent 人设</span>
<span class="step-title">Generate Agent Personas</span>
</div>
<div class="step-status">
<span v-if="phase > 1" class="badge success">已完成</span>
<span v-if="phase > 1" class="badge success">Complete</span>
<span v-else-if="phase === 1" class="badge processing">{{ prepareProgress }}%</span>
<span v-else class="badge pending">等待</span>
<span v-else class="badge pending">Waiting</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/simulation/prepare</p>
<p class="description">
结合上下文自动调用工具从知识图谱梳理实体与关系初始化模拟个体并基于现实种子赋予他们独特的行为与记忆
Using context, automatically extract entities and relations from the knowledge graph, initialize simulation agents, and assign them unique behaviors and memories based on real-world seeds
</p>
<!-- Profiles Stats -->
<div v-if="profiles.length > 0" class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{ profiles.length }}</span>
<span class="stat-label">当前Agent数</span>
<span class="stat-label">Current Agents</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ expectedTotal || '-' }}</span>
<span class="stat-label">预期Agent总数</span>
<span class="stat-label">Expected Total Agents</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ totalTopicsCount }}</span>
<span class="stat-label">现实种子当前关联话题数</span>
<span class="stat-label">Related Topics from Seeds</span>
</div>
</div>
<!-- Profiles List Preview -->
<div v-if="profiles.length > 0" class="profiles-preview">
<div class="preview-header">
<span class="preview-title">已生成的 Agent 人设</span>
<span class="preview-title">Generated Agent Personas</span>
</div>
<div class="profiles-list">
<div
@ -94,9 +94,9 @@
<span class="profile-username">@{{ profile.name || `agent_${idx}` }}</span>
</div>
<div class="profile-meta">
<span class="profile-profession">{{ profile.profession || '未知职业' }}</span>
<span class="profile-profession">{{ profile.profession || 'Unknown' }}</span>
</div>
<p class="profile-bio">{{ profile.bio || '暂无简介' }}</p>
<p class="profile-bio">{{ profile.bio || 'No bio available' }}</p>
<div v-if="profile.interested_topics?.length" class="profile-topics">
<span
v-for="topic in profile.interested_topics.slice(0, 3)"
@ -118,19 +118,19 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">03</span>
<span class="step-title">生成双平台模拟配置</span>
<span class="step-title">Generate Dual-Platform Config</span>
</div>
<div class="step-status">
<span v-if="phase > 2" class="badge success">已完成</span>
<span v-else-if="phase === 2" class="badge processing">生成中</span>
<span v-else class="badge pending">等待</span>
<span v-if="phase > 2" class="badge success">Complete</span>
<span v-else-if="phase === 2" class="badge processing">Generating</span>
<span v-else class="badge pending">Waiting</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/simulation/prepare</p>
<p class="description">
LLM 根据模拟需求与现实种子智能设置世界时间流速推荐算法每个个体的活跃时间段发言频率事件触发等参数
LLM intelligently configures world time flow, recommendation algorithms, activity schedules, posting frequency, and event triggers based on simulation requirements and real-world seeds
</p>
<!-- Config Preview -->
@ -139,40 +139,40 @@
<div class="config-block">
<div class="config-grid">
<div class="config-item">
<span class="config-item-label">模拟时长</span>
<span class="config-item-value">{{ simulationConfig.time_config?.total_simulation_hours || '-' }} 小时</span>
<span class="config-item-label">Duration</span>
<span class="config-item-value">{{ simulationConfig.time_config?.total_simulation_hours || '-' }} hours</span>
</div>
<div class="config-item">
<span class="config-item-label">每轮时长</span>
<span class="config-item-value">{{ simulationConfig.time_config?.minutes_per_round || '-' }} 分钟</span>
<span class="config-item-label">Round Duration</span>
<span class="config-item-value">{{ simulationConfig.time_config?.minutes_per_round || '-' }} min</span>
</div>
<div class="config-item">
<span class="config-item-label">总轮次</span>
<span class="config-item-value">{{ Math.floor((simulationConfig.time_config?.total_simulation_hours * 60 / simulationConfig.time_config?.minutes_per_round)) || '-' }} </span>
<span class="config-item-label">Total Rounds</span>
<span class="config-item-value">{{ Math.floor((simulationConfig.time_config?.total_simulation_hours * 60 / simulationConfig.time_config?.minutes_per_round)) || '-' }} rounds</span>
</div>
<div class="config-item">
<span class="config-item-label">每小时活跃</span>
<span class="config-item-label">Active per Hour</span>
<span class="config-item-value">{{ simulationConfig.time_config?.agents_per_hour_min }}-{{ simulationConfig.time_config?.agents_per_hour_max }}</span>
</div>
</div>
<div class="time-periods">
<div class="period-item">
<span class="period-label">高峰时段</span>
<span class="period-label">Peak Hours</span>
<span class="period-hours">{{ simulationConfig.time_config?.peak_hours?.join(':00, ') }}:00</span>
<span class="period-multiplier">×{{ simulationConfig.time_config?.peak_activity_multiplier }}</span>
</div>
<div class="period-item">
<span class="period-label">工作时段</span>
<span class="period-label">Work Hours</span>
<span class="period-hours">{{ simulationConfig.time_config?.work_hours?.[0] }}:00-{{ simulationConfig.time_config?.work_hours?.slice(-1)[0] }}:00</span>
<span class="period-multiplier">×{{ simulationConfig.time_config?.work_activity_multiplier }}</span>
</div>
<div class="period-item">
<span class="period-label">早间时段</span>
<span class="period-label">Morning Hours</span>
<span class="period-hours">{{ simulationConfig.time_config?.morning_hours?.[0] }}:00-{{ simulationConfig.time_config?.morning_hours?.slice(-1)[0] }}:00</span>
<span class="period-multiplier">×{{ simulationConfig.time_config?.morning_activity_multiplier }}</span>
</div>
<div class="period-item">
<span class="period-label">低谷时段</span>
<span class="period-label">Off-Peak Hours</span>
<span class="period-hours">{{ simulationConfig.time_config?.off_peak_hours?.[0] }}:00-{{ simulationConfig.time_config?.off_peak_hours?.slice(-1)[0] }}:00</span>
<span class="period-multiplier">×{{ simulationConfig.time_config?.off_peak_activity_multiplier }}</span>
</div>
@ -182,8 +182,8 @@
<!-- Agent 配置 -->
<div class="config-block">
<div class="config-block-header">
<span class="config-block-title">Agent 配置</span>
<span class="config-block-badge">{{ simulationConfig.agent_configs?.length || 0 }} </span>
<span class="config-block-title">Agent Configuration</span>
<span class="config-block-badge">{{ simulationConfig.agent_configs?.length || 0 }} </span>
</div>
<div class="agents-cards">
<div
@ -205,7 +205,7 @@
<!-- 活跃时间轴 -->
<div class="agent-timeline">
<span class="timeline-label">活跃时段</span>
<span class="timeline-label">Active Hours</span>
<div class="mini-timeline">
<div
v-for="hour in 24"
@ -228,34 +228,34 @@
<div class="agent-params">
<div class="param-group">
<div class="param-item">
<span class="param-label">发帖/</span>
<span class="param-label">Posts/hr</span>
<span class="param-value">{{ agent.posts_per_hour }}</span>
</div>
<div class="param-item">
<span class="param-label">评论/</span>
<span class="param-label">Comments/hr</span>
<span class="param-value">{{ agent.comments_per_hour }}</span>
</div>
<div class="param-item">
<span class="param-label">响应延迟</span>
<span class="param-label">Response Delay</span>
<span class="param-value">{{ agent.response_delay_min }}-{{ agent.response_delay_max }}min</span>
</div>
</div>
<div class="param-group">
<div class="param-item">
<span class="param-label">活跃度</span>
<span class="param-label">Activity Level</span>
<span class="param-value with-bar">
<span class="mini-bar" :style="{ width: (agent.activity_level * 100) + '%' }"></span>
{{ (agent.activity_level * 100).toFixed(0) }}%
</span>
</div>
<div class="param-item">
<span class="param-label">情感倾向</span>
<span class="param-label">Sentiment</span>
<span class="param-value" :class="agent.sentiment_bias > 0 ? 'positive' : agent.sentiment_bias < 0 ? 'negative' : 'neutral'">
{{ agent.sentiment_bias > 0 ? '+' : '' }}{{ agent.sentiment_bias?.toFixed(1) }}
</span>
</div>
<div class="param-item">
<span class="param-label">影响力</span>
<span class="param-label">Influence</span>
<span class="param-value highlight">{{ agent.influence_weight?.toFixed(1) }}</span>
</div>
</div>
@ -267,59 +267,59 @@
<!-- 平台配置 -->
<div class="config-block">
<div class="config-block-header">
<span class="config-block-title">推荐算法配置</span>
<span class="config-block-title">Recommendation Algorithm Config</span>
</div>
<div class="platforms-grid">
<div v-if="simulationConfig.twitter_config" class="platform-card">
<div class="platform-card-header">
<span class="platform-name">平台 1广场 / 信息流</span>
<span class="platform-name">Platform 1: Feed / Timeline</span>
</div>
<div class="platform-params">
<div class="param-row">
<span class="param-label">时效权重</span>
<span class="param-label">Recency Weight</span>
<span class="param-value">{{ simulationConfig.twitter_config.recency_weight }}</span>
</div>
<div class="param-row">
<span class="param-label">热度权重</span>
<span class="param-label">Popularity Weight</span>
<span class="param-value">{{ simulationConfig.twitter_config.popularity_weight }}</span>
</div>
<div class="param-row">
<span class="param-label">相关性权重</span>
<span class="param-label">Relevance Weight</span>
<span class="param-value">{{ simulationConfig.twitter_config.relevance_weight }}</span>
</div>
<div class="param-row">
<span class="param-label">病毒阈值</span>
<span class="param-label">Viral Threshold</span>
<span class="param-value">{{ simulationConfig.twitter_config.viral_threshold }}</span>
</div>
<div class="param-row">
<span class="param-label">回音室强度</span>
<span class="param-label">Echo Chamber Intensity</span>
<span class="param-value">{{ simulationConfig.twitter_config.echo_chamber_strength }}</span>
</div>
</div>
</div>
<div v-if="simulationConfig.reddit_config" class="platform-card">
<div class="platform-card-header">
<span class="platform-name">平台 2话题 / 社区</span>
<span class="platform-name">Platform 2: Topics / Community</span>
</div>
<div class="platform-params">
<div class="param-row">
<span class="param-label">时效权重</span>
<span class="param-label">Recency Weight</span>
<span class="param-value">{{ simulationConfig.reddit_config.recency_weight }}</span>
</div>
<div class="param-row">
<span class="param-label">热度权重</span>
<span class="param-label">Popularity Weight</span>
<span class="param-value">{{ simulationConfig.reddit_config.popularity_weight }}</span>
</div>
<div class="param-row">
<span class="param-label">相关性权重</span>
<span class="param-label">Relevance Weight</span>
<span class="param-value">{{ simulationConfig.reddit_config.relevance_weight }}</span>
</div>
<div class="param-row">
<span class="param-label">病毒阈值</span>
<span class="param-label">Viral Threshold</span>
<span class="param-value">{{ simulationConfig.reddit_config.viral_threshold }}</span>
</div>
<div class="param-row">
<span class="param-label">回音室强度</span>
<span class="param-label">Echo Chamber Intensity</span>
<span class="param-value">{{ simulationConfig.reddit_config.echo_chamber_strength }}</span>
</div>
</div>
@ -330,7 +330,7 @@
<!-- LLM 配置推理 -->
<div v-if="simulationConfig.generation_reasoning" class="config-block">
<div class="config-block-header">
<span class="config-block-title">LLM 配置推理</span>
<span class="config-block-title">LLM Config Reasoning</span>
</div>
<div class="reasoning-content">
<div
@ -351,19 +351,19 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">04</span>
<span class="step-title">初始激活编排</span>
<span class="step-title">Initial Activation Orchestration</span>
</div>
<div class="step-status">
<span v-if="phase > 3" class="badge success">已完成</span>
<span v-else-if="phase === 3" class="badge processing">编排中</span>
<span v-else class="badge pending">等待</span>
<span v-if="phase > 3" class="badge success">Complete</span>
<span v-else-if="phase === 3" class="badge processing">Orchestrating</span>
<span v-else class="badge pending">Waiting</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/simulation/prepare</p>
<p class="description">
基于叙事方向自动生成初始激活事件与热点话题引导模拟世界的初始状态
Based on narrative direction, automatically generate initial activation events and trending topics to guide the simulation world's initial state
</p>
<div v-if="simulationConfig?.event_config" class="orchestration-content">
@ -380,14 +380,14 @@
</linearGradient>
</defs>
</svg>
叙事引导方向
Narrative Direction
</span>
<p class="narrative-text">{{ simulationConfig.event_config.narrative_direction }}</p>
</div>
<!-- 热点话题 -->
<div class="topics-section">
<span class="box-label">初始热点话题</span>
<span class="box-label">Initial Trending Topics</span>
<div class="hot-topics-grid">
<span v-for="topic in simulationConfig.event_config.hot_topics" :key="topic" class="hot-topic-tag">
# {{ topic }}
@ -397,7 +397,7 @@
<!-- 初始帖子流 -->
<div class="initial-posts-section">
<span class="box-label">初始激活序列 ({{ simulationConfig.event_config.initial_posts.length }})</span>
<span class="box-label">Initial Activation Sequence ({{ simulationConfig.event_config.initial_posts.length }})</span>
<div class="posts-timeline">
<div v-for="(post, idx) in simulationConfig.event_config.initial_posts" :key="idx" class="timeline-item">
<div class="timeline-marker"></div>
@ -423,29 +423,29 @@
<div class="card-header">
<div class="step-info">
<span class="step-num">05</span>
<span class="step-title">准备完成</span>
<span class="step-title">Setup Complete</span>
</div>
<div class="step-status">
<span v-if="phase >= 4" class="badge processing">进行中</span>
<span v-else class="badge pending">等待</span>
<span v-if="phase >= 4" class="badge processing">In Progress</span>
<span v-else class="badge pending">Waiting</span>
</div>
</div>
<div class="card-content">
<p class="api-note">POST /api/simulation/start</p>
<p class="description">模拟环境已准备完成可以开始运行模拟</p>
<p class="description">Simulation environment is ready. You can now start the simulation</p>
<!-- 模拟轮数配置 - 只有在配置生成完成且轮数计算出来后才显示 -->
<div v-if="simulationConfig && autoGeneratedRounds" class="rounds-config-section">
<div class="rounds-header">
<div class="header-left">
<span class="section-title">模拟轮数设定</span>
<span class="section-desc">MiroFish 自动规划推演现实 <span class="desc-highlight">{{ simulationConfig?.time_config?.total_simulation_hours || '-' }}</span> 小时每轮代表现实 <span class="desc-highlight">{{ simulationConfig?.time_config?.minutes_per_round || '-' }}</span> 分钟时间流逝</span>
<span class="section-title">Simulation Rounds Config</span>
<span class="section-desc">MiroFish auto-planned <span class="desc-highlight">{{ simulationConfig?.time_config?.total_simulation_hours || '-' }}</span> hours of real-time, each round representing <span class="desc-highlight">{{ simulationConfig?.time_config?.minutes_per_round || '-' }}</span> minutes of elapsed time</span>
</div>
<label class="switch-control">
<input type="checkbox" v-model="useCustomRounds">
<span class="switch-track"></span>
<span class="switch-label">自定义</span>
<span class="switch-label">Custom</span>
</label>
</div>
@ -454,10 +454,10 @@
<div class="slider-display">
<div class="slider-main-value">
<span class="val-num">{{ customMaxRounds }}</span>
<span class="val-unit"></span>
<span class="val-unit">rounds</span>
</div>
<div class="slider-meta-info">
<span>若Agent规模为100预计耗时约 {{ Math.round(customMaxRounds * 0.6) }} 分钟</span>
<span>For 100 agents: estimated time ~ {{ Math.round(customMaxRounds * 0.6) }} min</span>
</div>
</div>
@ -478,7 +478,7 @@
:class="{ active: customMaxRounds === 40 }"
@click="customMaxRounds = 40"
:style="{ position: 'absolute', left: `calc(${(40 - 10) / (autoGeneratedRounds - 10) * 100}% - 30px)` }"
>40 (推荐)</span>
>40 (Recommended)</span>
<span>{{ autoGeneratedRounds }}</span>
</div>
</div>
@ -488,7 +488,7 @@
<div class="auto-info-card">
<div class="auto-value">
<span class="val-num">{{ autoGeneratedRounds }}</span>
<span class="val-unit"></span>
<span class="val-unit">rounds</span>
</div>
<div class="auto-content">
<div class="auto-meta-row">
@ -497,11 +497,11 @@
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
若Agent规模为100预计耗时 {{ Math.round(autoGeneratedRounds * 0.6) }} 分钟
For 100 agents: estimated time {{ Math.round(autoGeneratedRounds * 0.6) }} min
</span>
</div>
<div class="auto-desc">
<p class="highlight-tip" @click="useCustomRounds = true">若首次运行强烈建议切换至自定义模式减少模拟轮数以便快速预览效果并降低报错风险 </p>
<p class="highlight-tip" @click="useCustomRounds = true">For your first run, we strongly recommend switching to Custom mode with fewer rounds for a quick preview and to reduce error risk </p>
</div>
</div>
</div>
@ -514,14 +514,14 @@
class="action-btn secondary"
@click="$emit('go-back')"
>
返回图谱构建
Back to Graph Build
</button>
<button
class="action-btn primary"
:disabled="phase < 4"
@click="handleStartSimulation"
>
开始双世界并行模拟
Start Dual-World Simulation
</button>
</div>
</div>
@ -547,32 +547,32 @@
<!-- 基本信息 -->
<div class="modal-info-grid">
<div class="info-item">
<span class="info-label">事件外显年龄</span>
<span class="info-value">{{ selectedProfile.age || '-' }} </span>
<span class="info-label">Apparent Age</span>
<span class="info-value">{{ selectedProfile.age || '-' }} yrs</span>
</div>
<div class="info-item">
<span class="info-label">事件外显性别</span>
<span class="info-value">{{ { male: '男', female: '女', other: '其他' }[selectedProfile.gender] || selectedProfile.gender }}</span>
<span class="info-label">Apparent Gender</span>
<span class="info-value">{{ { male: 'Male', female: 'Female', other: 'Other' }[selectedProfile.gender] || selectedProfile.gender }}</span>
</div>
<div class="info-item">
<span class="info-label">国家/地区</span>
<span class="info-label">Country/Region</span>
<span class="info-value">{{ selectedProfile.country || '-' }}</span>
</div>
<div class="info-item">
<span class="info-label">事件外显MBTI</span>
<span class="info-label">Apparent MBTI</span>
<span class="info-value mbti">{{ selectedProfile.mbti || '-' }}</span>
</div>
</div>
<!-- 简介 -->
<div class="modal-section">
<span class="section-label">人设简介</span>
<p class="section-bio">{{ selectedProfile.bio || '暂无简介' }}</p>
<span class="section-label">Persona Bio</span>
<p class="section-bio">{{ selectedProfile.bio || 'No bio available' }}</p>
</div>
<!-- 关注话题 -->
<div class="modal-section" v-if="selectedProfile.interested_topics?.length">
<span class="section-label">现实种子关联话题</span>
<span class="section-label">Related Topics from Seeds</span>
<div class="topics-grid">
<span
v-for="topic in selectedProfile.interested_topics"
@ -584,25 +584,25 @@
<!-- 详细人设 -->
<div class="modal-section" v-if="selectedProfile.persona">
<span class="section-label">详细人设背景</span>
<span class="section-label">Detailed Persona Background</span>
<!-- 人设维度概览 -->
<div class="persona-dimensions">
<div class="dimension-card">
<span class="dim-title">事件全景经历</span>
<span class="dim-desc">在此事件中的完整行为轨迹</span>
<span class="dim-title">Event Panorama</span>
<span class="dim-desc">Complete behavioral trajectory in this event</span>
</div>
<div class="dimension-card">
<span class="dim-title">行为模式侧写</span>
<span class="dim-desc">经验总结与行事风格偏好</span>
<span class="dim-title">Behavioral Profile</span>
<span class="dim-desc">Experience summary and behavioral preferences</span>
</div>
<div class="dimension-card">
<span class="dim-title">独特记忆印记</span>
<span class="dim-desc">基于现实种子形成的记忆</span>
<span class="dim-title">Unique Memory Imprint</span>
<span class="dim-desc">Memories formed from real-world seeds</span>
</div>
<div class="dimension-card">
<span class="dim-title">社会关系网络</span>
<span class="dim-desc">个体链接与交互图谱</span>
<span class="dim-title">Social Network</span>
<span class="dim-desc">Individual connections and interaction graph</span>
</div>
</div>
@ -680,7 +680,7 @@ watch(currentStage, (newStage) => {
phase.value = 2
//
if (!configTimer) {
addLog('开始生成双平台模拟配置...')
addLog('Generating dual-platform simulation config...')
startConfigPolling()
}
} else if (newStage === '准备模拟脚本' || newStage === 'copying_scripts') {
@ -745,10 +745,10 @@ const handleStartSimulation = () => {
if (useCustomRounds.value) {
// max_rounds
params.maxRounds = customMaxRounds.value
addLog(`开始模拟,自定义轮数: ${customMaxRounds.value}`)
addLog(`Starting simulation, custom rounds: ${customMaxRounds.value}`)
} else {
// max_rounds
addLog(`开始模拟,使用自动配置轮数: ${autoGeneratedRounds.value}`)
addLog(`Starting simulation, auto-configured rounds: ${autoGeneratedRounds.value}`)
}
emit('next-step', params)
@ -768,15 +768,15 @@ const selectProfile = (profile) => {
//
const startPrepareSimulation = async () => {
if (!props.simulationId) {
addLog('错误:缺少 simulationId')
addLog('Error: missing simulationId')
emit('update-status', 'error')
return
}
//
phase.value = 1
addLog(`模拟实例已创建: ${props.simulationId}`)
addLog('正在准备模拟环境...')
addLog(`Simulation instance created: ${props.simulationId}`)
addLog('Preparing simulation environment...')
emit('update-status', 'processing')
try {
@ -788,35 +788,35 @@ const startPrepareSimulation = async () => {
if (res.success && res.data) {
if (res.data.already_prepared) {
addLog('检测到已有完成的准备工作,直接使用')
addLog('Existing preparation detected, using it directly')
await loadPreparedData()
return
}
taskId.value = res.data.task_id
addLog(`准备任务已启动`)
addLog(`Preparation task started`)
addLog(` └─ Task ID: ${res.data.task_id}`)
// Agentprepare
if (res.data.expected_entities_count) {
expectedTotal.value = res.data.expected_entities_count
addLog(`从Zep图谱读取到 ${res.data.expected_entities_count} 个实体`)
addLog(`Read ${res.data.expected_entities_count} entities from Zep graph`)
if (res.data.entity_types && res.data.entity_types.length > 0) {
addLog(` └─ 实体类型: ${res.data.entity_types.join(', ')}`)
addLog(` └─ Entity types: ${res.data.entity_types.join(', ')}`)
}
}
addLog('开始轮询准备进度...')
addLog('Polling preparation progress...')
//
startPolling()
// Profiles
startProfilesPolling()
} else {
addLog(`准备失败: ${res.error || '未知错误'}`)
addLog(`Preparation failed: ${res.error || 'Unknown error'}`)
emit('update-status', 'error')
}
} catch (err) {
addLog(`准备异常: ${err.message}`)
addLog(`Preparation error: ${err.message}`)
emit('update-status', 'error')
}
}
@ -890,12 +890,12 @@ const pollPrepareStatus = async () => {
//
if (data.status === 'completed' || data.status === 'ready' || data.already_prepared) {
addLog('✓ 准备工作已完成')
addLog('✓ Preparation complete')
stopPolling()
stopProfilesPolling()
await loadPreparedData()
} else if (data.status === 'failed') {
addLog(`准备失败: ${data.error || '未知错误'}`)
addLog(`Preparation failed: ${data.error || 'Unknown error'}`)
stopPolling()
stopProfilesPolling()
}
@ -934,13 +934,13 @@ const fetchProfilesRealtime = async () => {
const latestProfile = profiles.value[currentCount - 1]
const profileName = latestProfile?.name || latestProfile?.username || `Agent_${currentCount}`
if (currentCount === 1) {
addLog(`开始生成Agent人设...`)
addLog(`Generating agent personas...`)
}
addLog(`→ Agent人设 ${currentCount}/${total}: ${profileName} (${latestProfile?.profession || '未知职业'})`)
addLog(`→ Agent persona ${currentCount}/${total}: ${profileName} (${latestProfile?.profession || 'Unknown'})`)
//
if (expectedTotal.value && currentCount >= expectedTotal.value) {
addLog(`全部 ${currentCount} 个Agent人设生成完成`)
addLog(`All ${currentCount} agent personas generated`)
}
}
}
@ -974,41 +974,41 @@ const fetchConfigRealtime = async () => {
if (data.generation_stage && data.generation_stage !== lastLoggedConfigStage) {
lastLoggedConfigStage = data.generation_stage
if (data.generation_stage === 'generating_profiles') {
addLog('正在生成Agent人设配置...')
addLog('Generating agent persona configs...')
} else if (data.generation_stage === 'generating_config') {
addLog('正在调用LLM生成模拟配置参数...')
addLog('Calling LLM to generate simulation config parameters...')
}
}
//
if (data.config_generated && data.config) {
simulationConfig.value = data.config
addLog('✓ 模拟配置生成完成')
addLog('✓ Simulation config generated')
//
if (data.summary) {
addLog(` ├─ Agent数量: ${data.summary.total_agents}`)
addLog(` ├─ 模拟时长: ${data.summary.simulation_hours}小时`)
addLog(` ├─ 初始帖子: ${data.summary.initial_posts_count}`)
addLog(` ├─ 热点话题: ${data.summary.hot_topics_count}`)
addLog(` └─ 平台配置: Twitter ${data.summary.has_twitter_config ? '✓' : '✗'}, Reddit ${data.summary.has_reddit_config ? '✓' : '✗'}`)
addLog(` ├─ Agents: ${data.summary.total_agents}`)
addLog(` ├─ Duration: ${data.summary.simulation_hours} hours`)
addLog(` ├─ Initial posts: ${data.summary.initial_posts_count}`)
addLog(` ├─ Trending topics: ${data.summary.hot_topics_count}`)
addLog(` └─ Platform config: Twitter ${data.summary.has_twitter_config ? '✓' : '✗'}, Reddit ${data.summary.has_reddit_config ? '✓' : '✗'}`)
}
//
if (data.config.time_config) {
const tc = data.config.time_config
addLog(`时间配置: 每轮${tc.minutes_per_round}分钟, 共${Math.floor((tc.total_simulation_hours * 60) / tc.minutes_per_round)}`)
addLog(`Time config: ${tc.minutes_per_round} min/round, ${Math.floor((tc.total_simulation_hours * 60) / tc.minutes_per_round)} total rounds`)
}
//
if (data.config.event_config?.narrative_direction) {
const narrative = data.config.event_config.narrative_direction
addLog(`叙事方向: ${narrative.length > 50 ? narrative.substring(0, 50) + '...' : narrative}`)
addLog(`Narrative: ${narrative.length > 50 ? narrative.substring(0, 50) + '...' : narrative}`)
}
stopConfigPolling()
phase.value = 4
addLog('✓ 环境搭建完成,可以开始模拟')
addLog('✓ Environment setup complete, ready to simulate')
emit('update-status', 'completed')
}
}
@ -1019,11 +1019,11 @@ const fetchConfigRealtime = async () => {
const loadPreparedData = async () => {
phase.value = 2
addLog('正在加载已有配置数据...')
addLog('Loading existing config data...')
// Profiles
await fetchProfilesRealtime()
addLog(`已加载 ${profiles.value.length} 个Agent人设`)
addLog(`Loaded ${profiles.value.length} agent personas`)
// 使
try {
@ -1031,26 +1031,26 @@ const loadPreparedData = async () => {
if (res.success && res.data) {
if (res.data.config_generated && res.data.config) {
simulationConfig.value = res.data.config
addLog('✓ 模拟配置加载成功')
addLog('✓ Simulation config loaded')
//
if (res.data.summary) {
addLog(` ├─ Agent数量: ${res.data.summary.total_agents}`)
addLog(` ├─ 模拟时长: ${res.data.summary.simulation_hours}小时`)
addLog(` └─ 初始帖子: ${res.data.summary.initial_posts_count}`)
addLog(` ├─ Agents: ${res.data.summary.total_agents}`)
addLog(` ├─ Duration: ${res.data.summary.simulation_hours} hours`)
addLog(` └─ Initial posts: ${res.data.summary.initial_posts_count}`)
}
addLog('✓ 环境搭建完成,可以开始模拟')
addLog('✓ Environment setup complete, ready to simulate')
phase.value = 4
emit('update-status', 'completed')
} else {
//
addLog('配置生成中,开始轮询等待...')
addLog('Config generation in progress, polling...')
startConfigPolling()
}
}
} catch (err) {
addLog(`加载配置失败: ${err.message}`)
addLog(`Failed to load config: ${err.message}`)
emit('update-status', 'error')
}
}
@ -1068,7 +1068,7 @@ watch(() => props.systemLogs?.length, () => {
onMounted(() => {
//
if (props.simulationId) {
addLog('Step2 环境搭建初始化')
addLog('Step2 environment setup initialized')
startPrepareSimulation()
}
})

View file

@ -97,7 +97,7 @@
@click="handleNextStep"
>
<span v-if="isGeneratingReport" class="loading-spinner-small"></span>
{{ isGeneratingReport ? '启动中...' : '开始生成结果报告' }}
{{ isGeneratingReport ? 'Starting...' : 'Generate Report' }}
<span v-if="!isGeneratingReport" class="arrow-icon"></span>
</button>
</div>
@ -379,7 +379,7 @@ const resetAllState = () => {
//
const doStartSimulation = async () => {
if (!props.simulationId) {
addLog('错误:缺少 simulationId')
addLog('Error: missing simulationId')
return
}
@ -388,7 +388,7 @@ const doStartSimulation = async () => {
isStarting.value = true
startError.value = null
addLog('正在启动双平台并行模拟...')
addLog('Starting dual-platform parallel simulation...')
emit('update-status', 'processing')
try {
@ -401,18 +401,18 @@ const doStartSimulation = async () => {
if (props.maxRounds) {
params.max_rounds = props.maxRounds
addLog(`设置最大模拟轮数: ${props.maxRounds}`)
addLog(`Max simulation rounds set to: ${props.maxRounds}`)
}
addLog('已开启动态图谱更新模式')
addLog('Dynamic graph update mode enabled')
const res = await startSimulation(params)
if (res.success && res.data) {
if (res.data.force_restarted) {
addLog('✓ 已清理旧的模拟日志,重新开始模拟')
addLog('✓ Old simulation logs cleared, restarting simulation')
}
addLog('✓ 模拟引擎启动成功')
addLog('✓ Simulation engine started successfully')
addLog(` ├─ PID: ${res.data.process_pid || '-'}`)
phase.value = 1
@ -421,13 +421,13 @@ const doStartSimulation = async () => {
startStatusPolling()
startDetailPolling()
} else {
startError.value = res.error || '启动失败'
addLog(`启动失败: ${res.error || '未知错误'}`)
startError.value = res.error || 'Start failed'
addLog(`Start failed: ${res.error || 'Unknown error'}`)
emit('update-status', 'error')
}
} catch (err) {
startError.value = err.message
addLog(`启动异常: ${err.message}`)
addLog(`Start error: ${err.message}`)
emit('update-status', 'error')
} finally {
isStarting.value = false
@ -439,21 +439,21 @@ const handleStopSimulation = async () => {
if (!props.simulationId) return
isStopping.value = true
addLog('正在停止模拟...')
addLog('Stopping simulation...')
try {
const res = await stopSimulation({ simulation_id: props.simulationId })
if (res.success) {
addLog('✓ 模拟已停止')
addLog('✓ Simulation stopped')
phase.value = 2
stopPolling()
emit('update-status', 'completed')
} else {
addLog(`停止失败: ${res.error || '未知错误'}`)
addLog(`Stop failed: ${res.error || 'Unknown error'}`)
}
} catch (err) {
addLog(`停止异常: ${err.message}`)
addLog(`Stop error: ${err.message}`)
} finally {
isStopping.value = false
}
@ -517,9 +517,9 @@ const fetchRunStatus = async () => {
if (isCompleted || platformsCompleted) {
if (platformsCompleted && !isCompleted) {
addLog('✓ 检测到所有平台模拟已结束')
addLog('✓ All platform simulations completed')
}
addLog('✓ 模拟已完成')
addLog('✓ Simulation complete')
phase.value = 2
stopPolling()
emit('update-status', 'completed')
@ -640,17 +640,17 @@ const formatActionTime = (timestamp) => {
const handleNextStep = async () => {
if (!props.simulationId) {
addLog('错误:缺少 simulationId')
addLog('Error: missing simulationId')
return
}
if (isGeneratingReport.value) {
addLog('报告生成请求已发送,请稍候...')
addLog('Report generation request sent, please wait...')
return
}
isGeneratingReport.value = true
addLog('正在启动报告生成...')
addLog('Starting report generation...')
try {
const res = await generateReport({
@ -660,16 +660,16 @@ const handleNextStep = async () => {
if (res.success && res.data) {
const reportId = res.data.report_id
addLog(`报告生成任务已启动: ${reportId}`)
addLog(`Report generation started: ${reportId}`)
//
router.push({ name: 'Report', params: { reportId } })
} else {
addLog(`启动报告生成失败: ${res.error || '未知错误'}`)
addLog(`Failed to start report generation: ${res.error || 'Unknown error'}`)
isGeneratingReport.value = false
}
} catch (err) {
addLog(`启动报告生成异常: ${err.message}`)
addLog(`Report generation error: ${err.message}`)
isGeneratingReport.value = false
}
}
@ -685,7 +685,7 @@ watch(() => props.systemLogs?.length, () => {
})
onMounted(() => {
addLog('Step3 模拟运行初始化')
addLog('Step3 simulation run initialized')
if (props.simulationId) {
doStartSimulation()
}

View file

@ -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">Generating {{ section.title }}...</span>
</div>
</div>
</div>
@ -129,7 +129,7 @@
<!-- Next Step Button - 在完成后显示 -->
<button v-if="isComplete" class="next-step-btn" @click="goToInteraction">
<span>进入深度互动</span>
<span>Enter Deep Interaction</span>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"></line>
<polyline points="12 5 19 12 12 19"></polyline>
@ -1003,7 +1003,7 @@ const InsightDisplay = {
]),
props.result.query && h('div', { class: 'header-topic' }, props.result.query),
props.result.simulationRequirement && h('div', { class: 'header-scenario' }, [
h('span', { class: 'scenario-label' }, '预测场景: '),
h('span', { class: 'scenario-label' }, 'Prediction Scenario: '),
h('span', { class: 'scenario-text' }, props.result.simulationRequirement)
])
]),
@ -1014,25 +1014,25 @@ const InsightDisplay = {
class: ['insight-tab', { active: activeTab.value === 'facts' }],
onClick: () => { activeTab.value = 'facts' }
}, [
h('span', { class: 'tab-label' }, `当前关键记忆 (${props.result.facts.length})`)
h('span', { class: 'tab-label' }, `Key Memories (${props.result.facts.length})`)
]),
h('button', {
class: ['insight-tab', { active: activeTab.value === 'entities' }],
onClick: () => { activeTab.value = 'entities' }
}, [
h('span', { class: 'tab-label' }, `核心实体 (${props.result.entities.length})`)
h('span', { class: 'tab-label' }, `Core Entities (${props.result.entities.length})`)
]),
h('button', {
class: ['insight-tab', { active: activeTab.value === 'relations' }],
onClick: () => { activeTab.value = 'relations' }
}, [
h('span', { class: 'tab-label' }, `关系链 (${props.result.relations.length})`)
h('span', { class: 'tab-label' }, `Relation Chains (${props.result.relations.length})`)
]),
props.result.subQueries.length > 0 && h('button', {
class: ['insight-tab', { active: activeTab.value === 'subqueries' }],
onClick: () => { activeTab.value = 'subqueries' }
}, [
h('span', { class: 'tab-label' }, `子问题 (${props.result.subQueries.length})`)
h('span', { class: 'tab-label' }, `Sub-Queries (${props.result.subQueries.length})`)
])
]),
@ -1041,8 +1041,8 @@ const InsightDisplay = {
// Facts Tab
activeTab.value === 'facts' && props.result.facts.length > 0 && h('div', { class: 'facts-panel' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '时序记忆中所关联的最新关键事实'),
h('span', { class: 'panel-count' }, `${props.result.facts.length} `)
h('span', { class: 'panel-title' }, 'Latest Key Facts from Temporal Memory'),
h('span', { class: 'panel-count' }, `${props.result.facts.length} total`)
]),
h('div', { class: 'facts-list' },
(expandedFacts.value ? props.result.facts : props.result.facts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
@ -1055,35 +1055,35 @@ const InsightDisplay = {
props.result.facts.length > INITIAL_SHOW_COUNT && h('button', {
class: 'expand-btn',
onClick: () => { expandedFacts.value = !expandedFacts.value }
}, expandedFacts.value ? `收起 ▲` : `展开全部 ${props.result.facts.length}`)
}, expandedFacts.value ? `Collapse ▲` : `Show all ${props.result.facts.length}`)
]),
// Entities Tab
activeTab.value === 'entities' && props.result.entities.length > 0 && h('div', { class: 'entities-panel' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '核心实体'),
h('span', { class: 'panel-count' }, `${props.result.entities.length} `)
h('span', { class: 'panel-title' }, 'Core Entities'),
h('span', { class: 'panel-count' }, `${props.result.entities.length} total`)
]),
h('div', { class: 'entities-grid' },
(expandedEntities.value ? props.result.entities : props.result.entities.slice(0, 12)).map((entity, i) =>
h('div', { class: 'entity-tag', key: i, title: entity.summary || '' }, [
h('span', { class: 'entity-name' }, entity.name),
h('span', { class: 'entity-type' }, entity.type),
entity.relatedFactsCount > 0 && h('span', { class: 'entity-fact-count' }, `${entity.relatedFactsCount}`)
entity.relatedFactsCount > 0 && h('span', { class: 'entity-fact-count' }, `${entity.relatedFactsCount}`)
])
)
),
props.result.entities.length > 12 && h('button', {
class: 'expand-btn',
onClick: () => { expandedEntities.value = !expandedEntities.value }
}, expandedEntities.value ? `收起 ▲` : `展开全部 ${props.result.entities.length}`)
}, expandedEntities.value ? `Collapse ▲` : `Show all ${props.result.entities.length}`)
]),
// Relations Tab
activeTab.value === 'relations' && props.result.relations.length > 0 && h('div', { class: 'relations-panel' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '关系链'),
h('span', { class: 'panel-count' }, `${props.result.relations.length} `)
h('span', { class: 'panel-title' }, 'Relation Chains'),
h('span', { class: 'panel-count' }, `${props.result.relations.length} total`)
]),
h('div', { class: 'relations-list' },
(expandedRelations.value ? props.result.relations : props.result.relations.slice(0, INITIAL_SHOW_COUNT)).map((rel, i) =>
@ -1101,14 +1101,14 @@ const InsightDisplay = {
props.result.relations.length > INITIAL_SHOW_COUNT && h('button', {
class: 'expand-btn',
onClick: () => { expandedRelations.value = !expandedRelations.value }
}, expandedRelations.value ? `收起 ▲` : `展开全部 ${props.result.relations.length}`)
}, expandedRelations.value ? `Collapse ▲` : `Show all ${props.result.relations.length}`)
]),
// Sub-queries Tab
activeTab.value === 'subqueries' && props.result.subQueries.length > 0 && h('div', { class: 'subqueries-panel' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '漂移查询生成分析子问题'),
h('span', { class: 'panel-count' }, `${props.result.subQueries.length} `)
h('span', { class: 'panel-title' }, 'Drift Query Sub-Questions'),
h('span', { class: 'panel-count' }, `${props.result.subQueries.length} total`)
]),
h('div', { class: 'subqueries-list' },
props.result.subQueries.map((sq, i) =>
@ -1121,9 +1121,9 @@ const InsightDisplay = {
]),
// Empty state
activeTab.value === 'facts' && props.result.facts.length === 0 && h('div', { class: 'empty-state' }, '暂无当前关键记忆'),
activeTab.value === 'entities' && props.result.entities.length === 0 && h('div', { class: 'empty-state' }, '暂无核心实体'),
activeTab.value === 'relations' && props.result.relations.length === 0 && h('div', { class: 'empty-state' }, '暂无关系链')
activeTab.value === 'facts' && props.result.facts.length === 0 && h('div', { class: 'empty-state' }, 'No key memories'),
activeTab.value === 'entities' && props.result.entities.length === 0 && h('div', { class: 'empty-state' }, 'No core entities'),
activeTab.value === 'relations' && props.result.relations.length === 0 && h('div', { class: 'empty-state' }, 'No relation chains')
])
])
}
@ -1176,19 +1176,19 @@ const PanoramaDisplay = {
class: ['panorama-tab', { active: activeTab.value === 'active' }],
onClick: () => { activeTab.value = 'active' }
}, [
h('span', { class: 'tab-label' }, `当前有效记忆 (${props.result.activeFacts.length})`)
h('span', { class: 'tab-label' }, `Active Memories (${props.result.activeFacts.length})`)
]),
h('button', {
class: ['panorama-tab', { active: activeTab.value === 'historical' }],
onClick: () => { activeTab.value = 'historical' }
}, [
h('span', { class: 'tab-label' }, `历史记忆 (${props.result.historicalFacts.length})`)
h('span', { class: 'tab-label' }, `Historical Memories (${props.result.historicalFacts.length})`)
]),
h('button', {
class: ['panorama-tab', { active: activeTab.value === 'entities' }],
onClick: () => { activeTab.value = 'entities' }
}, [
h('span', { class: 'tab-label' }, `涉及实体 (${props.result.entities.length})`)
h('span', { class: 'tab-label' }, `Involved Entities (${props.result.entities.length})`)
])
]),
@ -1197,8 +1197,8 @@ const PanoramaDisplay = {
// Active Facts Tab
activeTab.value === 'active' && h('div', { class: 'facts-panel active-facts' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '当前有效记忆'),
h('span', { class: 'panel-count' }, `${props.result.activeFacts.length} `)
h('span', { class: 'panel-title' }, 'Active Memories'),
h('span', { class: 'panel-count' }, `${props.result.activeFacts.length} total`)
]),
props.result.activeFacts.length > 0 ? h('div', { class: 'facts-list' },
(expandedActive.value ? props.result.activeFacts : props.result.activeFacts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
@ -1207,18 +1207,18 @@ const PanoramaDisplay = {
h('div', { class: 'fact-content' }, fact)
])
)
) : h('div', { class: 'empty-state' }, '暂无当前有效记忆'),
) : h('div', { class: 'empty-state' }, 'No active memories'),
props.result.activeFacts.length > INITIAL_SHOW_COUNT && h('button', {
class: 'expand-btn',
onClick: () => { expandedActive.value = !expandedActive.value }
}, expandedActive.value ? `收起 ▲` : `展开全部 ${props.result.activeFacts.length}`)
}, expandedActive.value ? `Collapse ▲` : `Show all ${props.result.activeFacts.length}`)
]),
// Historical Facts Tab
activeTab.value === 'historical' && h('div', { class: 'facts-panel historical-facts' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '历史记忆'),
h('span', { class: 'panel-count' }, `${props.result.historicalFacts.length} `)
h('span', { class: 'panel-title' }, 'Historical Memories'),
h('span', { class: 'panel-count' }, `${props.result.historicalFacts.length} total`)
]),
props.result.historicalFacts.length > 0 ? h('div', { class: 'facts-list' },
(expandedHistorical.value ? props.result.historicalFacts : props.result.historicalFacts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
@ -1239,18 +1239,18 @@ const PanoramaDisplay = {
])
])
)
) : h('div', { class: 'empty-state' }, '暂无历史记忆'),
) : h('div', { class: 'empty-state' }, 'No historical memories'),
props.result.historicalFacts.length > INITIAL_SHOW_COUNT && h('button', {
class: 'expand-btn',
onClick: () => { expandedHistorical.value = !expandedHistorical.value }
}, expandedHistorical.value ? `收起 ▲` : `展开全部 ${props.result.historicalFacts.length}`)
}, expandedHistorical.value ? `Collapse ▲` : `Show all ${props.result.historicalFacts.length}`)
]),
// Entities Tab
activeTab.value === 'entities' && h('div', { class: 'entities-panel' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '涉及实体'),
h('span', { class: 'panel-count' }, `${props.result.entities.length} `)
h('span', { class: 'panel-title' }, 'Involved Entities'),
h('span', { class: 'panel-count' }, `${props.result.entities.length} total`)
]),
props.result.entities.length > 0 ? h('div', { class: 'entities-grid' },
(expandedEntities.value ? props.result.entities : props.result.entities.slice(0, 8)).map((entity, i) =>
@ -1259,11 +1259,11 @@ const PanoramaDisplay = {
entity.type && h('span', { class: 'entity-type' }, entity.type)
])
)
) : h('div', { class: 'empty-state' }, '暂无涉及实体'),
) : h('div', { class: 'empty-state' }, 'No involved entities'),
props.result.entities.length > 8 && h('button', {
class: 'expand-btn',
onClick: () => { expandedEntities.value = !expandedEntities.value }
}, expandedEntities.value ? `收起 ▲` : `展开全部 ${props.result.entities.length}`)
}, expandedEntities.value ? `Collapse ▲` : `Show all ${props.result.entities.length}`)
])
])
])
@ -1467,7 +1467,7 @@ const InterviewDisplay = {
// Selection Reason -
props.result.interviews[activeIndex.value]?.selectionReason && h('div', { class: 'selection-reason' }, [
h('div', { class: 'reason-label' }, '选择理由'),
h('div', { class: 'reason-label' }, 'Selection Reason'),
h('div', { class: 'reason-content' }, props.result.interviews[activeIndex.value].selectionReason)
]),
@ -1512,7 +1512,7 @@ const InterviewDisplay = {
h('line', { x1: '2', y1: '12', x2: '22', y2: '12' }),
h('path', { d: 'M12 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' })
]),
h('span', {}, '世界1')
h('span', {}, 'World 1')
]),
h('button', {
class: ['platform-btn', { active: currentPlatform === 'reddit' }],
@ -1521,7 +1521,7 @@ const InterviewDisplay = {
h('svg', { class: 'platform-icon', viewBox: '0 0 24 24', width: 12, height: 12, fill: 'none', stroke: 'currentColor', 'stroke-width': 2 }, [
h('path', { d: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' })
]),
h('span', {}, '世界2')
h('span', {}, 'World 2')
])
])
]),
@ -1610,7 +1610,7 @@ const QuickSearchDisplay = {
])
]),
props.result.query && h('div', { class: 'header-query' }, [
h('span', { class: 'query-label' }, '搜索: '),
h('span', { class: 'query-label' }, 'Search: '),
h('span', { class: 'query-text' }, props.result.query)
])
]),
@ -1621,19 +1621,19 @@ const QuickSearchDisplay = {
class: ['quicksearch-tab', { active: activeTab.value === 'facts' }],
onClick: () => { activeTab.value = 'facts' }
}, [
h('span', { class: 'tab-label' }, `事实 (${props.result.facts.length})`)
h('span', { class: 'tab-label' }, `Facts (${props.result.facts.length})`)
]),
hasEdges.value && h('button', {
class: ['quicksearch-tab', { active: activeTab.value === 'edges' }],
onClick: () => { activeTab.value = 'edges' }
}, [
h('span', { class: 'tab-label' }, `关系 (${props.result.edges.length})`)
h('span', { class: 'tab-label' }, `Edges (${props.result.edges.length})`)
]),
hasNodes.value && h('button', {
class: ['quicksearch-tab', { active: activeTab.value === 'nodes' }],
onClick: () => { activeTab.value = 'nodes' }
}, [
h('span', { class: 'tab-label' }, `节点 (${props.result.nodes.length})`)
h('span', { class: 'tab-label' }, `Nodes (${props.result.nodes.length})`)
])
]),
@ -1642,8 +1642,8 @@ const QuickSearchDisplay = {
// Facts (always show if no tabs, or when facts tab is active)
((!showTabs.value) || activeTab.value === 'facts') && h('div', { class: 'facts-panel' }, [
!showTabs.value && h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '搜索结果'),
h('span', { class: 'panel-count' }, `${props.result.facts.length} `)
h('span', { class: 'panel-title' }, 'Search Results'),
h('span', { class: 'panel-count' }, `${props.result.facts.length} total`)
]),
props.result.facts.length > 0 ? h('div', { class: 'facts-list' },
(expandedFacts.value ? props.result.facts : props.result.facts.slice(0, INITIAL_SHOW_COUNT)).map((fact, i) =>
@ -1652,18 +1652,18 @@ const QuickSearchDisplay = {
h('div', { class: 'fact-content' }, fact)
])
)
) : h('div', { class: 'empty-state' }, '未找到相关结果'),
) : h('div', { class: 'empty-state' }, 'No results found'),
props.result.facts.length > INITIAL_SHOW_COUNT && h('button', {
class: 'expand-btn',
onClick: () => { expandedFacts.value = !expandedFacts.value }
}, expandedFacts.value ? `收起 ▲` : `展开全部 ${props.result.facts.length}`)
}, expandedFacts.value ? `Collapse ▲` : `Show all ${props.result.facts.length}`)
]),
// Edges Tab
activeTab.value === 'edges' && hasEdges.value && h('div', { class: 'edges-panel' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '相关关系'),
h('span', { class: 'panel-count' }, `${props.result.edges.length} `)
h('span', { class: 'panel-title' }, 'Related Edges'),
h('span', { class: 'panel-count' }, `${props.result.edges.length} total`)
]),
h('div', { class: 'edges-list' },
props.result.edges.map((edge, i) =>
@ -1683,8 +1683,8 @@ const QuickSearchDisplay = {
// Nodes Tab
activeTab.value === 'nodes' && hasNodes.value && h('div', { class: 'nodes-panel' }, [
h('div', { class: 'panel-header' }, [
h('span', { class: 'panel-title' }, '相关节点'),
h('span', { class: 'panel-count' }, `${props.result.nodes.length} `)
h('span', { class: 'panel-title' }, 'Related Nodes'),
h('span', { class: 'panel-count' }, `${props.result.nodes.length} total`)
]),
h('div', { class: 'nodes-grid' },
props.result.nodes.map((node, i) =>
@ -1776,7 +1776,7 @@ const activeStep = computed(() => {
if (doneSteps.length > 0) return doneSteps[doneSteps.length - 1]
//
return steps[0] || { noLabel: '--', title: '等待开始', status: 'todo', meta: '' }
return steps[0] || { noLabel: '--', title: 'Waiting to Start', status: 'todo', meta: '' }
})
const workflowSteps = computed(() => {
@ -2006,8 +2006,8 @@ const getActionLabel = (action) => {
}
const getLogLevelClass = (log) => {
if (log.includes('ERROR') || log.includes('错误')) return 'error'
if (log.includes('WARNING') || log.includes('警告')) return 'warning'
if (log.includes('ERROR') || log.includes('Error')) return 'error'
if (log.includes('WARNING') || log.includes('Warning')) return 'warning'
// INFO 使 success
return ''
}

View file

@ -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">Generating {{ section.title }}...</span>
</div>
</div>
</div>
@ -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>Chat with Report Agent</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 : 'Chat with Any Agent' }}</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">Select Chat Partner</div>
<div
v-for="(agent, idx) in profiles"
:key="idx"
@ -126,7 +126,7 @@
<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 || 'Unknown' }}</span>
</div>
</div>
</div>
@ -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>Send Survey to Agents</span>
</button>
</div>
</div>
@ -155,7 +155,7 @@
<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-subtitle">Quick chat with the report agent. Has access to 4 specialized tools and MiroFish's full memory</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">InsightForge Deep Attribution</div>
<div class="tool-desc">Aligns real-world seed data with simulation state using Global/Local Memory for cross-temporal deep attribution analysis</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">PanoramaSearch Full Trace</div>
<div class="tool-desc">Graph-based BFS algorithm that reconstructs event propagation paths and captures full information flow topology</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">QuickSearch Quick Lookup</div>
<div class="tool-desc">GraphRAG-based instant query interface, optimized for quick extraction of node properties and discrete facts</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">InterviewSubAgent Virtual Interview</div>
<div class="tool-desc">Autonomous interview agent that conducts parallel multi-turn conversations with simulated agents, collecting unstructured opinion data and psychological states</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 || 'Unknown' }}</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">Bio</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' ? 'Chat with the Report Agent to explore report details' : 'Chat with a simulated agent to learn their perspective' }}
</p>
</div>
<div
@ -292,7 +292,7 @@
<textarea
v-model="chatInput"
class="chat-input"
placeholder="输入您的问题..."
placeholder="Type your question..."
@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">Select Survey Recipients</span>
<span class="selection-count">Selected {{ selectedAgents.size }} / {{ 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 || 'Unknown' }}</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">Select All</button>
<span class="action-divider">|</span>
<button class="action-link" @click="clearAgentSelection">清空</button>
<button class="action-link" @click="clearAgentSelection">Clear</button>
</div>
</div>
<div class="setup-section">
<div class="section-header">
<span class="section-title">问卷问题</span>
<span class="section-title">Survey Question</span>
</div>
<textarea
v-model="surveyQuestion"
class="survey-input"
placeholder="输入您想问所有被选中对象的问题..."
placeholder="Enter the question you want to ask all selected agents..."
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>Send Survey</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">Survey Results</span>
<span class="results-count">{{ surveyResults.length }} responses</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 || 'Unknown' }}</span>
</div>
</div>
<div class="result-question">
@ -535,7 +535,7 @@ const selectAgent = (agent, idx) => {
// Agent
chatHistory.value = chatHistoryCache.value[`agent_${idx}`] || []
addLog(`选择对话对象: ${agent.username}`)
addLog(`Selected chat partner: ${agent.username}`)
}
const formatTime = (timestamp) => {
@ -662,10 +662,10 @@ const sendMessage = async () => {
await sendToAgent(message)
}
} catch (err) {
addLog(`发送失败: ${err.message}`)
addLog(`Send failed: ${err.message}`)
chatHistory.value.push({
role: 'assistant',
content: `抱歉,发生了错误: ${err.message}`,
content: `Sorry, an error occurred: ${err.message}`,
timestamp: new Date().toISOString()
})
} finally {
@ -677,7 +677,7 @@ const sendMessage = async () => {
}
const sendToReportAgent = async (message) => {
addLog(`向 Report Agent 发送: ${message.substring(0, 50)}...`)
addLog(`Sent to Report Agent: ${message.substring(0, 50)}...`)
// Build chat history for API
const historyForApi = chatHistory.value
@ -697,21 +697,21 @@ 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 || 'No response',
timestamp: new Date().toISOString()
})
addLog('Report Agent 已回复')
addLog('Report Agent replied')
} else {
throw new Error(res.error || '请求失败')
throw new Error(res.error || 'Request failed')
}
}
const sendToAgent = async (message) => {
if (!selectedAgent.value || selectedAgentIndex.value === null) {
throw new Error('请先选择一个模拟个体')
throw new Error('Please select a simulated agent first')
}
addLog(` ${selectedAgent.value.username} 发送: ${message.substring(0, 50)}...`)
addLog(`Sent to ${selectedAgent.value.username}: ${message.substring(0, 50)}...`)
// Build prompt with chat history
let prompt = message
@ -719,9 +719,9 @@ const sendToAgent = async (message) => {
const historyContext = chatHistory.value
.filter(msg => msg.content !== message)
.slice(-6)
.map(msg => `${msg.role === 'user' ? '提问者' : '你'}${msg.content}`)
.map(msg => `${msg.role === 'user' ? 'User' : 'You'}: ${msg.content}`)
.join('\n')
prompt = `以下是我们之前的对话:\n${historyContext}\n\n现在我的新问题是${message}`
prompt = `Here is our previous conversation:\n${historyContext}\n\nMy new question is: ${message}`
}
const res = await interviewAgents({
@ -761,12 +761,12 @@ const sendToAgent = async (message) => {
content: responseContent,
timestamp: new Date().toISOString()
})
addLog(`${selectedAgent.value.username} 已回复`)
addLog(`${selectedAgent.value.username} replied`)
} else {
throw new Error('无响应数据')
throw new Error('No response data')
}
} else {
throw new Error(res.error || '请求失败')
throw new Error(res.error || 'Request failed')
}
}
@ -803,7 +803,7 @@ const submitSurvey = async () => {
if (selectedAgents.value.size === 0 || !surveyQuestion.value.trim()) return
isSurveying.value = true
addLog(`发送问卷给 ${selectedAgents.value.size} 个对象...`)
addLog(`Sending survey to ${selectedAgents.value.size} agents...`)
try {
const interviews = Array.from(selectedAgents.value).map(idx => ({
@ -830,20 +830,20 @@ const submitSurvey = async () => {
const agent = profiles.value[agentIdx]
// 使 reddit twitter
let responseContent = '无响应'
let responseContent = 'No response'
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 || 'No response'
}
} 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 || 'No response'
}
}
@ -857,12 +857,12 @@ const submitSurvey = async () => {
}
surveyResults.value = surveyResultsList
addLog(`收到 ${surveyResults.value.length} 条回复`)
addLog(`Received ${surveyResults.value.length} responses`)
} else {
throw new Error(res.error || '请求失败')
throw new Error(res.error || 'Request failed')
}
} catch (err) {
addLog(`问卷发送失败: ${err.message}`)
addLog(`Survey send failed: ${err.message}`)
} finally {
isSurveying.value = false
}
@ -873,7 +873,7 @@ const loadReportData = async () => {
if (!props.reportId) return
try {
addLog(`加载报告数据: ${props.reportId}`)
addLog(`Loading report data: ${props.reportId}`)
// Get report info
const reportRes = await getReport(props.reportId)
@ -882,7 +882,7 @@ const loadReportData = async () => {
await loadAgentLogs()
}
} catch (err) {
addLog(`加载报告失败: ${err.message}`)
addLog(`Failed to load report: ${err.message}`)
}
}
@ -904,10 +904,10 @@ const loadAgentLogs = async () => {
}
})
addLog('报告数据加载完成')
addLog('Report data loaded')
}
} catch (err) {
addLog(`加载报告日志失败: ${err.message}`)
addLog(`Failed to load report logs: ${err.message}`)
}
}
@ -918,10 +918,10 @@ const loadProfiles = async () => {
const res = await getSimulationProfilesRealtime(props.simulationId, 'reddit')
if (res.success && res.data) {
profiles.value = res.data.profiles || []
addLog(`加载了 ${profiles.value.length} 个模拟个体`)
addLog(`Loaded ${profiles.value.length} simulated agents`)
}
} catch (err) {
addLog(`加载模拟个体失败: ${err.message}`)
addLog(`Failed to load simulated agents: ${err.message}`)
}
}
@ -935,7 +935,7 @@ const handleClickOutside = (e) => {
// Lifecycle
onMounted(() => {
addLog('Step5 深度互动初始化')
addLog('Step5 deep interaction initialized')
loadReportData()
loadProfiles()
document.addEventListener('click', handleClickOutside)

View file

@ -5,7 +5,7 @@
<div class="nav-brand">MIROFISH</div>
<div class="nav-links">
<a href="https://github.com/666ghj/MiroFish" target="_blank" class="github-link">
访问我们的Github主页 <span class="arrow"></span>
Visit our GitHub <span class="arrow"></span>
</a>
</div>
</nav>
@ -15,21 +15,21 @@
<section class="hero-section">
<div class="hero-left">
<div class="tag-row">
<span class="orange-tag">简洁通用的群体智能引擎</span>
<span class="version-text">/ v0.1-预览版</span>
<span class="orange-tag">A Versatile Collective Intelligence Engine</span>
<span class="version-text">/ v0.1-preview</span>
</div>
<h1 class="main-title">
上传任意报告<br>
<span class="gradient-text">即刻推演未来</span>
Upload Any Report<br>
<span class="gradient-text">Simulate the Future</span>
</h1>
<div class="hero-desc">
<p>
即使只有一段文字<span class="highlight-bold">MiroFish</span> 也能基于其中的现实种子全自动生成与之对应的至多<span class="highlight-orange">百万级Agent</span>构成的平行世界通过上帝视角注入变量在复杂的群体交互中寻找动态环境下的<span class="highlight-code">局部最优解</span>
Even from a single piece of text, <span class="highlight-bold">MiroFish</span> can extract reality seeds and automatically generate a parallel world with up to <span class="highlight-orange">millions of Agents</span>. Inject variables from a god's-eye view and find the <span class="highlight-code">"local optimum" in complex group interactions under dynamic conditions.</span>
</p>
<p class="slogan-text">
让未来在 Agent 群中预演让决策在百战后胜出<span class="blinking-cursor">_</span>
Let the future rehearse among Agents. Let decisions win after a thousand battles.<span class="blinking-cursor">_</span>
</p>
</div>
@ -53,65 +53,65 @@
<!-- 左栏状态与步骤 -->
<div class="left-panel">
<div class="panel-header">
<span class="status-dot"></span> 系统状态
<span class="status-dot"></span> System Status
</div>
<h2 class="section-title">准备就绪</h2>
<h2 class="section-title">Ready</h2>
<p class="section-desc">
预测引擎待命中可上传多份非结构化数据以初始化模拟序列
Prediction engine standing by. Upload unstructured data to initialize a simulation sequence.
</p>
<!-- 数据指标卡片 -->
<div class="metrics-row">
<div class="metric-card">
<div class="metric-value">低成本</div>
<div class="metric-label">常规模拟平均5$/</div>
<div class="metric-value">Low Cost</div>
<div class="metric-label">~$5 per simulation on average</div>
</div>
<div class="metric-card">
<div class="metric-value">高可用</div>
<div class="metric-label">最多百万级Agent模拟</div>
<div class="metric-value">Scalable</div>
<div class="metric-label">Simulate millions of Agents</div>
</div>
</div>
<!-- 项目模拟步骤介绍 (新增区域) -->
<div class="steps-container">
<div class="steps-header">
<span class="diamond-icon"></span> 工作流序列
<span class="diamond-icon"></span> Workflow Sequence
</div>
<div class="workflow-list">
<div class="workflow-item">
<span class="step-num">01</span>
<div class="step-info">
<div class="step-title">图谱构建</div>
<div class="step-desc">现实种子提取 & 个体与群体记忆注入 & GraphRAG构建</div>
<div class="step-title">Graph Build</div>
<div class="step-desc">Reality seed extraction & individual/group memory injection & GraphRAG construction</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">02</span>
<div class="step-info">
<div class="step-title">环境搭建</div>
<div class="step-desc">实体关系抽取 & 人设生成 & 环境配置Agent注入仿真参数</div>
<div class="step-title">Env Setup</div>
<div class="step-desc">Entity-relation extraction & persona generation & environment config with agent simulation parameters</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">03</span>
<div class="step-info">
<div class="step-title">开始模拟</div>
<div class="step-desc">双平台并行模拟 & 自动解析预测需求 & 动态更新时序记忆</div>
<div class="step-title">Run Simulation</div>
<div class="step-desc">Dual-platform parallel simulation & auto-parse prediction needs & dynamic temporal memory updates</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">04</span>
<div class="step-info">
<div class="step-title">报告生成</div>
<div class="step-desc">ReportAgent拥有丰富的工具集与模拟后环境进行深度交互</div>
<div class="step-title">Report Generation</div>
<div class="step-desc">ReportAgent uses a rich toolset to deeply interact with the post-simulation environment</div>
</div>
</div>
<div class="workflow-item">
<span class="step-num">05</span>
<div class="step-info">
<div class="step-title">深度互动</div>
<div class="step-desc">与模拟世界中的任意一位进行对话 & 与ReportAgent进行对话</div>
<div class="step-title">Deep Interaction</div>
<div class="step-desc">Chat with any individual in the simulated world & converse with ReportAgent</div>
</div>
</div>
</div>
@ -124,8 +124,8 @@
<!-- 上传区域 -->
<div class="console-section">
<div class="console-header">
<span class="console-label">01 / 现实种子</span>
<span class="console-meta">支持格式: PDF, MD, TXT</span>
<span class="console-label">01 / Reality Seeds</span>
<span class="console-meta">Supported: PDF, MD, TXT</span>
</div>
<div
@ -148,8 +148,8 @@
<div v-if="files.length === 0" class="upload-placeholder">
<div class="upload-icon"></div>
<div class="upload-title">拖拽文件上传</div>
<div class="upload-hint">或点击浏览文件系统</div>
<div class="upload-title">Drag & drop files here</div>
<div class="upload-hint">or click to browse</div>
</div>
<div v-else class="file-list">
@ -164,23 +164,23 @@
<!-- 分割线 -->
<div class="console-divider">
<span>输入参数</span>
<span>Input Parameters</span>
</div>
<!-- 输入区域 -->
<div class="console-section">
<div class="console-header">
<span class="console-label">>_ 02 / 模拟提示词</span>
<span class="console-label">>_ 02 / Simulation Prompt</span>
</div>
<div class="input-wrapper">
<textarea
v-model="formData.simulationRequirement"
class="code-input"
placeholder="// 用自然语言输入模拟或预测需求(例.武大若发布撤销肖某处分的公告,会引发什么舆情走向)"
placeholder="// Describe your simulation or prediction in natural language (e.g., What public opinion trends would emerge if X university revokes the disciplinary action against a student?)"
rows="6"
:disabled="loading"
></textarea>
<div class="model-badge">引擎: MiroFish-V1.0</div>
<div class="model-badge">Engine: MiroFish-V1.0</div>
</div>
</div>
@ -191,8 +191,8 @@
@click="startSimulation"
:disabled="!canSubmit || loading"
>
<span v-if="!loading">启动引擎</span>
<span v-else>初始化中...</span>
<span v-if="!loading">Launch Engine</span>
<span v-else>Initializing...</span>
<span class="btn-arrow"></span>
</button>
</div>

View file

@ -15,7 +15,7 @@
:class="{ active: viewMode === mode }"
@click="viewMode = mode"
>
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
{{ { graph: 'Graph', split: 'Split', workbench: 'Workbench' }[mode] }}
</button>
</div>
</div>
@ -23,7 +23,7 @@
<div class="header-right">
<div class="workflow-step">
<span class="step-num">Step 5/5</span>
<span class="step-name">深度互动</span>
<span class="step-name">Deep Interaction</span>
</div>
<div class="step-divider"></div>
<span class="status-indicator" :class="statusClass">
@ -140,7 +140,7 @@ const toggleMaximize = (target) => {
// --- Data Logic ---
const loadReportData = async () => {
try {
addLog(`加载报告数据: ${currentReportId.value}`)
addLog(`Loading report data: ${currentReportId.value}`)
// report simulation_id
const reportRes = await getReport(currentReportId.value)
@ -159,7 +159,7 @@ const loadReportData = async () => {
const projRes = await getProject(simData.project_id)
if (projRes.success && projRes.data) {
projectData.value = projRes.data
addLog(`项目加载成功: ${projRes.data.project_id}`)
addLog(`Project loaded: ${projRes.data.project_id}`)
// graph
if (projRes.data.graph_id) {
@ -170,10 +170,10 @@ const loadReportData = async () => {
}
}
} else {
addLog(`获取报告信息失败: ${reportRes.error || '未知错误'}`)
addLog(`Failed to load report info: ${reportRes.error || 'Unknown error'}`)
}
} catch (err) {
addLog(`加载异常: ${err.message}`)
addLog(`Loading error: ${err.message}`)
}
}
@ -184,10 +184,10 @@ const loadGraph = async (graphId) => {
const res = await getGraphData(graphId)
if (res.success) {
graphData.value = res.data
addLog('图谱数据加载成功')
addLog('Graph data loaded')
}
} catch (err) {
addLog(`图谱加载失败: ${err.message}`)
addLog(`Graph loading failed: ${err.message}`)
} finally {
graphLoading.value = false
}
@ -208,7 +208,7 @@ watch(() => route.params.reportId, (newId) => {
}, { immediate: true })
onMounted(() => {
addLog('InteractionView 初始化')
addLog('InteractionView initialized')
loadReportData()
})
</script>

View file

@ -15,7 +15,7 @@
:class="{ active: viewMode === mode }"
@click="viewMode = mode"
>
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
{{ { graph: 'Graph', split: 'Split', workbench: 'Workbench' }[mode] }}
</button>
</div>
</div>
@ -91,7 +91,7 @@ const viewMode = ref('split') // graph | split | workbench
// Step State
const currentStep = ref(1) // 1: , 2: , 3: , 4: , 5:
const stepNames = ['图谱构建', '环境搭建', '开始模拟', '报告生成', '深度互动']
const stepNames = ['Graph Build', 'Env Setup', 'Run Simulation', 'Report Generation', 'Deep Interaction']
// Data State
const currentProjectId = ref(route.params.projectId)
@ -159,11 +159,11 @@ const toggleMaximize = (target) => {
const handleNextStep = (params = {}) => {
if (currentStep.value < 5) {
currentStep.value++
addLog(`进入 Step ${currentStep.value}: ${stepNames[currentStep.value - 1]}`)
addLog(`Entering Step ${currentStep.value}: ${stepNames[currentStep.value - 1]}`)
// Step 2 Step 3
if (currentStep.value === 3 && params.maxRounds) {
addLog(`自定义模拟轮数: ${params.maxRounds}`)
addLog(`Custom simulation rounds: ${params.maxRounds}`)
}
}
}
@ -171,7 +171,7 @@ const handleNextStep = (params = {}) => {
const handleGoBack = () => {
if (currentStep.value > 1) {
currentStep.value--
addLog(`返回 Step ${currentStep.value}: ${stepNames[currentStep.value - 1]}`)
addLog(`Returning to Step ${currentStep.value}: ${stepNames[currentStep.value - 1]}`)
}
}

View file

@ -7,7 +7,7 @@
<!-- 中间步骤指示器 -->
<div class="nav-center">
<div class="step-badge">STEP 01</div>
<div class="step-name">图谱构建</div>
<div class="step-name">Graph Build</div>
</div>
<div class="nav-status">
@ -23,20 +23,20 @@
<div class="panel-header">
<div class="header-left">
<span class="header-deco"></span>
<span class="header-title">实时知识图谱</span>
<span class="header-title">Live Knowledge Graph</span>
</div>
<div class="header-right">
<template v-if="graphData">
<span class="stat-item">{{ graphData.node_count || graphData.nodes?.length || 0 }} 节点</span>
<span class="stat-item">{{ graphData.node_count || graphData.nodes?.length || 0 }} Nodes</span>
<span class="stat-divider">|</span>
<span class="stat-item">{{ graphData.edge_count || graphData.edges?.length || 0 }} 关系</span>
<span class="stat-item">{{ graphData.edge_count || graphData.edges?.length || 0 }} Edges</span>
<span class="stat-divider">|</span>
</template>
<div class="action-buttons">
<button class="action-btn" @click="refreshGraph" :disabled="graphLoading" title="刷新图谱">
<button class="action-btn" @click="refreshGraph" :disabled="graphLoading" title="Refresh Graph">
<span class="icon-refresh" :class="{ 'spinning': graphLoading }"></span>
</button>
<button class="action-btn" @click="toggleFullScreen" :title="isFullScreen ? '退出全屏' : '全屏显示'">
<button class="action-btn" @click="toggleFullScreen" :title="isFullScreen ? 'Exit Fullscreen' : 'Fullscreen'">
<span class="icon-fullscreen">{{ isFullScreen ? '↙' : '↗' }}</span>
</button>
</div>
@ -50,7 +50,7 @@
<!-- 构建中提示 -->
<div v-if="currentPhase === 1" class="graph-building-hint">
<span class="building-dot"></span>
实时更新中...
Updating live...
</div>
<!-- 节点/边详情面板 -->
@ -171,7 +171,7 @@
<div class="loading-ring"></div>
<div class="loading-ring"></div>
</div>
<p class="loading-text">图谱数据加载中...</p>
<p class="loading-text">Loading graph data...</p>
</div>
<!-- 等待构建 -->
@ -189,8 +189,8 @@
<line x1="50" y1="72" x2="74" y2="66" stroke="#000" stroke-width="1"/>
</svg>
</div>
<p class="waiting-text">等待本体生成</p>
<p class="waiting-hint">生成完成后将自动开始构建图谱</p>
<p class="waiting-text">Waiting for Ontology Generation</p>
<p class="waiting-hint">Graph construction will begin automatically after generation</p>
</div>
<!-- 构建中但还没有数据 -->
@ -200,8 +200,8 @@
<div class="loading-ring"></div>
<div class="loading-ring"></div>
</div>
<p class="waiting-text">图谱构建中</p>
<p class="waiting-hint">数据即将显示...</p>
<p class="waiting-text">Building Graph</p>
<p class="waiting-hint">Data will appear shortly...</p>
</div>
<!-- 错误状态 -->
@ -225,7 +225,7 @@
<div class="right-panel" :class="{ 'hidden': isFullScreen }">
<div class="panel-header dark-header">
<span class="header-icon"></span>
<span class="header-title">构建流程</span>
<span class="header-title">Build Process</span>
</div>
<div class="process-content">
@ -234,7 +234,7 @@
<div class="phase-header">
<span class="phase-num">01</span>
<div class="phase-info">
<div class="phase-title">本体生成</div>
<div class="phase-title">Ontology Generation</div>
<div class="phase-api">/api/graph/ontology/generate</div>
</div>
<span class="phase-status" :class="getPhaseStatusClass(0)">
@ -244,15 +244,15 @@
<div class="phase-detail">
<div class="detail-section">
<div class="detail-label">接口说明</div>
<div class="detail-label">Description</div>
<div class="detail-content">
上传文档后LLM分析文档内容自动生成适合舆论模拟的本体结构实体类型 + 关系类型
After uploading documents, the LLM analyzes content and generates an ontology structure (entity types + relation types) suitable for simulation
</div>
</div>
<!-- 本体生成进度 -->
<div class="detail-section" v-if="ontologyProgress && currentPhase === 0">
<div class="detail-label">生成进度</div>
<div class="detail-label">Generation Progress</div>
<div class="ontology-progress">
<div class="progress-spinner"></div>
<span class="progress-text">{{ ontologyProgress.message }}</span>
@ -261,7 +261,7 @@
<!-- 已生成的本体信息 -->
<div class="detail-section" v-if="projectData?.ontology">
<div class="detail-label">生成的实体类型 ({{ projectData.ontology.entity_types?.length || 0 }})</div>
<div class="detail-label">Entity Types ({{ projectData.ontology.entity_types?.length || 0 }})</div>
<div class="entity-tags">
<span
v-for="entity in projectData.ontology.entity_types"
@ -274,7 +274,7 @@
</div>
<div class="detail-section" v-if="projectData?.ontology">
<div class="detail-label">生成的关系类型 ({{ projectData.ontology.relation_types?.length || 0 }})</div>
<div class="detail-label">Relation Types ({{ projectData.ontology.relation_types?.length || 0 }})</div>
<div class="relation-list">
<div
v-for="(rel, idx) in projectData.ontology.relation_types?.slice(0, 5) || []"
@ -288,14 +288,14 @@
<span class="rel-target">{{ rel.target_type }}</span>
</div>
<div v-if="(projectData.ontology.relation_types?.length || 0) > 5" class="relation-more">
+{{ projectData.ontology.relation_types.length - 5 }} 更多关系...
+{{ projectData.ontology.relation_types.length - 5 }} more relations...
</div>
</div>
</div>
<!-- 等待状态 -->
<div class="detail-section waiting-state" v-if="!projectData?.ontology && currentPhase === 0 && !ontologyProgress">
<div class="waiting-hint">等待本体生成...</div>
<div class="waiting-hint">Waiting for ontology generation...</div>
</div>
</div>
</div>
@ -305,7 +305,7 @@
<div class="phase-header">
<span class="phase-num">02</span>
<div class="phase-info">
<div class="phase-title">图谱构建</div>
<div class="phase-title">Graph Build</div>
<div class="phase-api">/api/graph/build</div>
</div>
<span class="phase-status" :class="getPhaseStatusClass(1)">
@ -315,20 +315,20 @@
<div class="phase-detail">
<div class="detail-section">
<div class="detail-label">接口说明</div>
<div class="detail-label">Description</div>
<div class="detail-content">
基于生成的本体将文档分块后调用 Zep API 构建知识图谱提取实体和关系
Based on the generated ontology, documents are chunked and processed via Zep API to build a knowledge graph, extracting entities and relations
</div>
</div>
<!-- 等待本体完成 -->
<div class="detail-section waiting-state" v-if="currentPhase < 1">
<div class="waiting-hint">等待本体生成完成...</div>
<div class="waiting-hint">Waiting for ontology generation to complete...</div>
</div>
<!-- 构建进度 -->
<div class="detail-section" v-if="buildProgress && currentPhase >= 1">
<div class="detail-label">构建进度</div>
<div class="detail-label">Build Progress</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: buildProgress.progress + '%' }"></div>
</div>
@ -339,19 +339,19 @@
</div>
<div class="detail-section" v-if="graphData">
<div class="detail-label">构建结果</div>
<div class="detail-label">Build Results</div>
<div class="build-result">
<div class="result-item">
<span class="result-value">{{ graphData.node_count }}</span>
<span class="result-label">实体节点</span>
<span class="result-label">Entity Nodes</span>
</div>
<div class="result-item">
<span class="result-value">{{ graphData.edge_count }}</span>
<span class="result-label">关系边</span>
<span class="result-label">Relation Edges</span>
</div>
<div class="result-item">
<span class="result-value">{{ entityTypes.length }}</span>
<span class="result-label">实体类型</span>
<span class="result-label">Entity Types</span>
</div>
</div>
</div>
@ -363,8 +363,8 @@
<div class="phase-header">
<span class="phase-num">03</span>
<div class="phase-info">
<div class="phase-title">构建完成</div>
<div class="phase-api">准备进入下一步骤</div>
<div class="phase-title">Build Complete</div>
<div class="phase-api">Ready for next step</div>
</div>
<span class="phase-status" :class="getPhaseStatusClass(2)">
{{ getPhaseStatusText(2) }}
@ -375,7 +375,7 @@
<!-- 下一步按钮 -->
<div class="next-step-section" v-if="currentPhase >= 2">
<button class="next-step-btn" @click="goToNextStep" :disabled="currentPhase < 2">
进入环境搭建
Proceed to Env Setup
<span class="btn-arrow"></span>
</button>
</div>
@ -385,23 +385,23 @@
<div class="project-panel">
<div class="project-header">
<span class="project-icon"></span>
<span class="project-title">项目信息</span>
<span class="project-title">Project Info</span>
</div>
<div class="project-details" v-if="projectData">
<div class="project-item">
<span class="item-label">项目名称</span>
<span class="item-label">Project Name</span>
<span class="item-value">{{ projectData.name }}</span>
</div>
<div class="project-item">
<span class="item-label">项目ID</span>
<span class="item-label">Project ID</span>
<span class="item-value code">{{ projectData.project_id }}</span>
</div>
<div class="project-item" v-if="projectData.graph_id">
<span class="item-label">图谱ID</span>
<span class="item-label">Graph ID</span>
<span class="item-value code">{{ projectData.graph_id }}</span>
</div>
<div class="project-item">
<span class="item-label">模拟需求</span>
<span class="item-label">Simulation Requirements</span>
<span class="item-value">{{ projectData.simulation_requirement || '-' }}</span>
</div>
</div>
@ -451,11 +451,11 @@ const statusClass = computed(() => {
})
const statusText = computed(() => {
if (error.value) return '构建失败'
if (currentPhase.value >= 2) return '构建完成'
if (currentPhase.value === 1) return '图谱构建中'
if (currentPhase.value === 0) return '本体生成中'
return '初始化中'
if (error.value) return 'Build Failed'
if (currentPhase.value >= 2) return 'Build Complete'
if (currentPhase.value === 1) return 'Building Graph'
if (currentPhase.value === 0) return 'Generating Ontology'
return 'Initializing'
})
const entityTypes = computed(() => {
@ -481,8 +481,8 @@ const goHome = () => {
}
const goToNextStep = () => {
// TODO:
alert('环境搭建功能开发中...')
// TODO: Proceed to Env Setup
alert('Environment setup feature is under development...')
}
const toggleFullScreen = () => {
@ -540,14 +540,14 @@ const getPhaseStatusClass = (phase) => {
}
const getPhaseStatusText = (phase) => {
if (currentPhase.value > phase) return '已完成'
if (currentPhase.value > phase) return 'Complete'
if (currentPhase.value === phase) {
if (phase === 1 && buildProgress.value) {
return `${buildProgress.value.progress}%`
}
return '进行中'
return 'In Progress'
}
return '等待中'
return 'Waiting'
}
// -
@ -569,7 +569,7 @@ const handleNewProject = async () => {
const pending = getPendingUpload()
if (!pending.isPending || pending.files.length === 0) {
error.value = '没有待上传的文件,请返回首页重新操作'
error.value = 'No files to upload. Please return to the home page and try again'
loading.value = false
return
}
@ -577,7 +577,7 @@ const handleNewProject = async () => {
try {
loading.value = true
currentPhase.value = 0 //
ontologyProgress.value = { message: '正在上传文件并分析文档...' }
ontologyProgress.value = { message: 'Uploading files and analyzing documents...' }
// FormData
const formDataObj = new FormData()
@ -608,11 +608,11 @@ const handleNewProject = async () => {
//
await startBuildGraph()
} else {
error.value = response.error || '本体生成失败'
error.value = response.error || 'Ontology generation failed'
}
} catch (err) {
console.error('Handle new project error:', err)
error.value = '项目初始化失败: ' + (err.message || '未知错误')
error.value = 'Project initialization failed: ' + (err.message || 'Unknown error')
} finally {
loading.value = false
}
@ -645,11 +645,11 @@ const loadProject = async () => {
await loadGraph(response.data.graph_id)
}
} else {
error.value = response.error || '加载项目失败'
error.value = response.error || 'Failed to load project'
}
} catch (err) {
console.error('Load project error:', err)
error.value = '加载项目失败: ' + (err.message || '未知错误')
error.value = 'Failed to load project: ' + (err.message || 'Unknown error')
} finally {
loading.value = false
}
@ -668,7 +668,7 @@ const updatePhaseByStatus = (status) => {
currentPhase.value = 2
break
case 'failed':
error.value = projectData.value?.error || '处理失败'
error.value = projectData.value?.error || 'Processing failed'
break
}
}
@ -680,13 +680,13 @@ const startBuildGraph = async () => {
//
buildProgress.value = {
progress: 0,
message: '正在启动图谱构建...'
message: 'Starting graph construction...'
}
const response = await buildGraph({ project_id: currentProjectId.value })
if (response.success) {
buildProgress.value.message = '图谱构建任务已启动...'
buildProgress.value.message = 'Graph construction task started...'
// task_id
const taskId = response.data.task_id
@ -697,12 +697,12 @@ const startBuildGraph = async () => {
//
startPollingTask(taskId)
} else {
error.value = response.error || '启动图谱构建失败'
error.value = response.error || 'Failed to start graph construction'
buildProgress.value = null
}
} catch (err) {
console.error('Build graph error:', err)
error.value = '启动图谱构建失败: ' + (err.message || '未知错误')
error.value = 'Failed to start graph construction: ' + (err.message || 'Unknown error')
buildProgress.value = null
}
}
@ -791,7 +791,7 @@ const pollTaskStatus = async (taskId) => {
//
buildProgress.value = {
progress: task.progress || 0,
message: task.message || '处理中...'
message: task.message || 'Processing...'
}
console.log('Task status:', task.status, 'Progress:', task.progress)
@ -806,7 +806,7 @@ const pollTaskStatus = async (taskId) => {
//
buildProgress.value = {
progress: 100,
message: '构建完成,正在加载图谱...'
message: 'Build complete, loading graph...'
}
// graph_id
@ -827,7 +827,7 @@ const pollTaskStatus = async (taskId) => {
} else if (task.status === 'failed') {
stopPolling()
stopGraphPolling()
error.value = '图谱构建失败: ' + (task.error || '未知错误')
error.value = 'Graph construction failed: ' + (task.error || 'Unknown error')
buildProgress.value = null
}
}
@ -905,7 +905,7 @@ const renderGraph = () => {
.attr('y', height / 2)
.attr('text-anchor', 'middle')
.attr('fill', '#999')
.text('等待图谱数据...')
.text('Waiting for graph data...')
return
}
@ -917,7 +917,7 @@ const renderGraph = () => {
const nodes = nodesData.map(n => ({
id: n.uuid,
name: n.name || '未命名',
name: n.name || 'Unnamed',
type: n.labels?.find(l => l !== 'Entity' && l !== 'Node') || 'Entity',
rawData: n //
}))
@ -933,8 +933,8 @@ const renderGraph = () => {
type: e.fact_type || e.name || 'RELATED_TO',
rawData: {
...e,
source_name: nodeMap[e.source_node_uuid]?.name || '未知',
target_name: nodeMap[e.target_node_uuid]?.name || '未知'
source_name: nodeMap[e.source_node_uuid]?.name || 'Unknown',
target_name: nodeMap[e.target_node_uuid]?.name || 'Unknown'
}
}))

View file

@ -15,7 +15,7 @@
:class="{ active: viewMode === mode }"
@click="viewMode = mode"
>
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
{{ { graph: 'Graph', split: 'Split', workbench: 'Workbench' }[mode] }}
</button>
</div>
</div>
@ -23,7 +23,7 @@
<div class="header-right">
<div class="workflow-step">
<span class="step-num">Step 4/5</span>
<span class="step-name">报告生成</span>
<span class="step-name">Report Generation</span>
</div>
<div class="step-divider"></div>
<span class="status-indicator" :class="statusClass">
@ -139,7 +139,7 @@ const toggleMaximize = (target) => {
// --- Data Logic ---
const loadReportData = async () => {
try {
addLog(`加载报告数据: ${currentReportId.value}`)
addLog(`Loading report data: ${currentReportId.value}`)
// report simulation_id
const reportRes = await getReport(currentReportId.value)
@ -158,7 +158,7 @@ const loadReportData = async () => {
const projRes = await getProject(simData.project_id)
if (projRes.success && projRes.data) {
projectData.value = projRes.data
addLog(`项目加载成功: ${projRes.data.project_id}`)
addLog(`Project loaded: ${projRes.data.project_id}`)
// graph
if (projRes.data.graph_id) {
@ -169,10 +169,10 @@ const loadReportData = async () => {
}
}
} else {
addLog(`获取报告信息失败: ${reportRes.error || '未知错误'}`)
addLog(`Failed to load report info: ${reportRes.error || 'Unknown error'}`)
}
} catch (err) {
addLog(`加载异常: ${err.message}`)
addLog(`Loading error: ${err.message}`)
}
}
@ -183,10 +183,10 @@ const loadGraph = async (graphId) => {
const res = await getGraphData(graphId)
if (res.success) {
graphData.value = res.data
addLog('图谱数据加载成功')
addLog('Graph data loaded')
}
} catch (err) {
addLog(`图谱加载失败: ${err.message}`)
addLog(`Graph loading failed: ${err.message}`)
} finally {
graphLoading.value = false
}
@ -207,7 +207,7 @@ watch(() => route.params.reportId, (newId) => {
}, { immediate: true })
onMounted(() => {
addLog('ReportView 初始化')
addLog('ReportView initialized')
loadReportData()
})
</script>

View file

@ -15,7 +15,7 @@
:class="{ active: viewMode === mode }"
@click="viewMode = mode"
>
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
{{ { graph: 'Graph', split: 'Split', workbench: 'Workbench' }[mode] }}
</button>
</div>
</div>
@ -23,7 +23,7 @@
<div class="header-right">
<div class="workflow-step">
<span class="step-num">Step 3/5</span>
<span class="step-name">开始模拟</span>
<span class="step-name">Run Simulation</span>
</div>
<div class="step-divider"></div>
<span class="status-indicator" :class="statusClass">
@ -146,7 +146,7 @@ const toggleMaximize = (target) => {
const handleGoBack = async () => {
// Step 2
addLog('准备返回 Step 2正在关闭模拟...')
addLog('Preparing to return to Step 2, stopping simulation...')
//
stopGraphRefresh()
@ -156,36 +156,36 @@ const handleGoBack = async () => {
const envStatusRes = await getEnvStatus({ simulation_id: currentSimulationId.value })
if (envStatusRes.success && envStatusRes.data?.env_alive) {
addLog('正在关闭模拟环境...')
addLog('Shutting down simulation environment...')
try {
await closeSimulationEnv({
simulation_id: currentSimulationId.value,
timeout: 10
})
addLog('✓ 模拟环境已关闭')
addLog('✓ Simulation environment closed')
} catch (closeErr) {
addLog(`关闭模拟环境失败,尝试强制停止...`)
addLog(`Failed to close environment, attempting force stop...`)
try {
await stopSimulation({ simulation_id: currentSimulationId.value })
addLog('✓ 模拟已强制停止')
addLog('✓ Simulation force-stopped')
} catch (stopErr) {
addLog(`强制停止失败: ${stopErr.message}`)
addLog(`Force stop failed: ${stopErr.message}`)
}
}
} else {
//
if (isSimulating.value) {
addLog('正在停止模拟进程...')
addLog('Stopping simulation process...')
try {
await stopSimulation({ simulation_id: currentSimulationId.value })
addLog('✓ 模拟已停止')
addLog('✓ Simulation stopped')
} catch (err) {
addLog(`停止模拟失败: ${err.message}`)
addLog(`Failed to stop simulation: ${err.message}`)
}
}
}
} catch (err) {
addLog(`检查模拟状态失败: ${err.message}`)
addLog(`Failed to check simulation status: ${err.message}`)
}
// Step 2 ()
@ -195,13 +195,13 @@ const handleGoBack = async () => {
const handleNextStep = () => {
// Step3Simulation
//
addLog('进入 Step 4: 报告生成')
addLog('Entering Step 4: Report Generation')
}
// --- Data Logic ---
const loadSimulationData = async () => {
try {
addLog(`加载模拟数据: ${currentSimulationId.value}`)
addLog(`Loading simulation data: ${currentSimulationId.value}`)
// simulation
const simRes = await getSimulation(currentSimulationId.value)
@ -213,10 +213,10 @@ const loadSimulationData = async () => {
const configRes = await getSimulationConfig(currentSimulationId.value)
if (configRes.success && configRes.data?.time_config?.minutes_per_round) {
minutesPerRound.value = configRes.data.time_config.minutes_per_round
addLog(`时间配置: 每轮 ${minutesPerRound.value} 分钟`)
addLog(`Time config: ${minutesPerRound.value} min/round`)
}
} catch (configErr) {
addLog(`获取时间配置失败,使用默认值: ${minutesPerRound.value}分钟/轮`)
addLog(`Failed to get time config, using default: ${minutesPerRound.value} min/round`)
}
// project
@ -224,7 +224,7 @@ const loadSimulationData = async () => {
const projRes = await getProject(simData.project_id)
if (projRes.success && projRes.data) {
projectData.value = projRes.data
addLog(`项目加载成功: ${projRes.data.project_id}`)
addLog(`Project loaded: ${projRes.data.project_id}`)
// graph
if (projRes.data.graph_id) {
@ -233,10 +233,10 @@ const loadSimulationData = async () => {
}
}
} else {
addLog(`加载模拟数据失败: ${simRes.error || '未知错误'}`)
addLog(`Failed to load simulation data: ${simRes.error || 'Unknown error'}`)
}
} catch (err) {
addLog(`加载异常: ${err.message}`)
addLog(`Loading error: ${err.message}`)
}
}
@ -252,11 +252,11 @@ const loadGraph = async (graphId) => {
if (res.success) {
graphData.value = res.data
if (!isSimulating.value) {
addLog('图谱数据加载成功')
addLog('Graph data loaded')
}
}
} catch (err) {
addLog(`图谱加载失败: ${err.message}`)
addLog(`Graph loading failed: ${err.message}`)
} finally {
graphLoading.value = false
}
@ -273,7 +273,7 @@ let graphRefreshTimer = null
const startGraphRefresh = () => {
if (graphRefreshTimer) return
addLog('开启图谱实时刷新 (30s)')
addLog('Graph live refresh enabled (30s)')
// 30
graphRefreshTimer = setInterval(refreshGraph, 30000)
}
@ -282,7 +282,7 @@ const stopGraphRefresh = () => {
if (graphRefreshTimer) {
clearInterval(graphRefreshTimer)
graphRefreshTimer = null
addLog('停止图谱实时刷新')
addLog('Graph live refresh stopped')
}
}
@ -295,11 +295,11 @@ watch(isSimulating, (newValue) => {
}, { immediate: true })
onMounted(() => {
addLog('SimulationRunView 初始化')
addLog('SimulationRunView initialized')
// maxRounds query
if (maxRounds.value) {
addLog(`自定义模拟轮数: ${maxRounds.value}`)
addLog(`Custom simulation rounds: ${maxRounds.value}`)
}
loadSimulationData()

View file

@ -15,7 +15,7 @@
:class="{ active: viewMode === mode }"
@click="viewMode = mode"
>
{{ { graph: '图谱', split: '双栏', workbench: '工作台' }[mode] }}
{{ { graph: 'Graph', split: 'Split', workbench: 'Workbench' }[mode] }}
</button>
</div>
</div>
@ -23,7 +23,7 @@
<div class="header-right">
<div class="workflow-step">
<span class="step-num">Step 2/5</span>
<span class="step-name">环境搭建</span>
<span class="step-name">Env Setup</span>
</div>
<div class="step-divider"></div>
<span class="status-indicator" :class="statusClass">
@ -146,13 +146,13 @@ const handleGoBack = () => {
}
const handleNextStep = (params = {}) => {
addLog('进入 Step 3: 开始模拟')
addLog('Entering Step 3: Run Simulation')
//
if (params.maxRounds) {
addLog(`自定义模拟轮数: ${params.maxRounds}`)
addLog(`Custom simulation rounds: ${params.maxRounds}`)
} else {
addLog('使用自动配置的模拟轮数')
addLog('Using auto-configured simulation rounds')
}
//
@ -184,7 +184,7 @@ const checkAndStopRunningSimulation = async () => {
const envStatusRes = await getEnvStatus({ simulation_id: currentSimulationId.value })
if (envStatusRes.success && envStatusRes.data?.env_alive) {
addLog('检测到模拟环境正在运行,正在关闭...')
addLog('Simulation environment detected running, shutting down...')
//
try {
@ -194,14 +194,14 @@ const checkAndStopRunningSimulation = async () => {
})
if (closeRes.success) {
addLog('✓ 模拟环境已关闭')
addLog('✓ Simulation environment closed')
} else {
addLog(`关闭模拟环境失败: ${closeRes.error || '未知错误'}`)
addLog(`Failed to close simulation environment: ${closeRes.error || 'Unknown error'}`)
//
await forceStopSimulation()
}
} catch (closeErr) {
addLog(`关闭模拟环境异常: ${closeErr.message}`)
addLog(`Error closing simulation environment: ${closeErr.message}`)
//
await forceStopSimulation()
}
@ -209,7 +209,7 @@ const checkAndStopRunningSimulation = async () => {
//
const simRes = await getSimulation(currentSimulationId.value)
if (simRes.success && simRes.data?.status === 'running') {
addLog('检测到模拟状态为运行中,正在停止...')
addLog('Simulation detected running, stopping...')
await forceStopSimulation()
}
}
@ -226,18 +226,18 @@ const forceStopSimulation = async () => {
try {
const stopRes = await stopSimulation({ simulation_id: currentSimulationId.value })
if (stopRes.success) {
addLog('✓ 模拟已强制停止')
addLog('✓ Simulation force-stopped')
} else {
addLog(`强制停止模拟失败: ${stopRes.error || '未知错误'}`)
addLog(`Failed to force-stop simulation: ${stopRes.error || 'Unknown error'}`)
}
} catch (err) {
addLog(`强制停止模拟异常: ${err.message}`)
addLog(`Error force-stopping simulation: ${err.message}`)
}
}
const loadSimulationData = async () => {
try {
addLog(`加载模拟数据: ${currentSimulationId.value}`)
addLog(`Loading simulation data: ${currentSimulationId.value}`)
// simulation
const simRes = await getSimulation(currentSimulationId.value)
@ -249,7 +249,7 @@ const loadSimulationData = async () => {
const projRes = await getProject(simData.project_id)
if (projRes.success && projRes.data) {
projectData.value = projRes.data
addLog(`项目加载成功: ${projRes.data.project_id}`)
addLog(`Project loaded: ${projRes.data.project_id}`)
// graph
if (projRes.data.graph_id) {
@ -258,10 +258,10 @@ const loadSimulationData = async () => {
}
}
} else {
addLog(`加载模拟数据失败: ${simRes.error || '未知错误'}`)
addLog(`Failed to load simulation data: ${simRes.error || 'Unknown error'}`)
}
} catch (err) {
addLog(`加载异常: ${err.message}`)
addLog(`Loading error: ${err.message}`)
}
}
@ -271,10 +271,10 @@ const loadGraph = async (graphId) => {
const res = await getGraphData(graphId)
if (res.success) {
graphData.value = res.data
addLog('图谱数据加载成功')
addLog('Graph data loaded')
}
} catch (err) {
addLog(`图谱加载失败: ${err.message}`)
addLog(`Graph loading failed: ${err.message}`)
} finally {
graphLoading.value = false
}
@ -287,7 +287,7 @@ const refreshGraph = () => {
}
onMounted(async () => {
addLog('SimulationView 初始化')
addLog('SimulationView initialized')
// Step 3
await checkAndStopRunningSimulation()