diff --git a/frontend/src/components/Step1GraphBuild.vue b/frontend/src/components/Step1GraphBuild.vue index 0e1ad49..de33a3f 100644 --- a/frontend/src/components/Step1GraphBuild.vue +++ b/frontend/src/components/Step1GraphBuild.vue @@ -156,6 +156,7 @@
+

POST /api/simulation/create

图谱构建已完成,请进入下一步进行模拟环境搭建

-

Event Orchestration

+

POST /api/simulation/prepare

基于叙事方向,自动生成初始激活事件与热点话题,引导模拟世界的初始状态

@@ -369,7 +369,19 @@
- 叙事引导方向 + + + + + + + + + + + + 叙事引导方向 +

{{ simulationConfig.event_config.narrative_direction }}

@@ -419,20 +431,67 @@
+

POST /api/simulation/start

模拟环境已准备完成,可以开始运行模拟

-
+ + +
+
+
⏱️
+
+

系统自动配置的模拟轮数为 {{ autoGeneratedRounds }} 轮,每轮代表现实中 1 小时的时间流逝。

+

完整模拟耗时较长,建议将轮数设置为 50 轮(预计耗时约 30 分钟)以快速预览效果。

+
+
+ +
+ + +
+
+ 模拟轮数 + {{ customMaxRounds }} 轮 +
+ +
+ 10 + 50 (推荐) + 120 +
+

预计耗时:约 {{ Math.round(customMaxRounds * 0.6) }} 分钟

+
+ +
+ ℹ️ + 将使用自动配置的 {{ autoGeneratedRounds }} 轮完整模拟 +
+
+
+ +
@@ -572,6 +631,11 @@ const simulationConfig = ref(null) const selectedProfile = ref(null) const showProfilesDetail = ref(true) +// 模拟轮数配置 +const useCustomRounds = ref(true) // 默认使用自定义轮数(推荐) +const customMaxRounds = ref(50) // 默认推荐50轮 +const autoGeneratedRounds = ref(120) // 自动生成的轮数(从配置中读取) + // Watch stage to update phase watch(currentStage, (newStage) => { if (newStage === '生成Agent人设' || newStage === 'generating_profiles') { @@ -588,6 +652,16 @@ watch(currentStage, (newStage) => { } }) +// Watch simulationConfig to update autoGeneratedRounds +watch(simulationConfig, (newConfig) => { + if (newConfig?.time_config) { + const totalHours = newConfig.time_config.total_simulation_hours || 120 + const minutesPerRound = newConfig.time_config.minutes_per_round || 60 + const calculatedRounds = Math.floor((totalHours * 60) / minutesPerRound) + autoGeneratedRounds.value = calculatedRounds || 120 + } +}, { immediate: true }) + // Polling timer let pollTimer = null let profilesTimer = null @@ -622,6 +696,23 @@ const addLog = (msg) => { emit('add-log', msg) } +// 处理开始模拟按钮点击 +const handleStartSimulation = () => { + // 构建传递给父组件的参数 + const params = {} + + if (useCustomRounds.value) { + // 用户自定义轮数,传递 max_rounds 参数 + params.maxRounds = customMaxRounds.value + addLog(`开始模拟,自定义轮数: ${customMaxRounds.value} 轮`) + } else { + // 用户选择保持自动生成的轮数,不传递 max_rounds 参数 + addLog(`开始模拟,使用自动配置轮数: ${autoGeneratedRounds.value} 轮`) + } + + emit('next-step', params) +} + const truncateBio = (bio) => { if (bio.length > 80) { return bio.substring(0, 80) + '...' @@ -942,7 +1033,7 @@ onUnmounted(() => { } .badge.success { background: #E8F5E9; color: #2E7D32; } -.badge.processing { background: #FFF3E0; color: #E65100; } +.badge.processing { background: #FF5722; color: #FFF; } .badge.pending { background: #F5F5F5; color: #999; } .badge.accent { background: #E3F2FD; color: #1565C0; } @@ -988,7 +1079,7 @@ onUnmounted(() => { } .action-btn.primary:hover:not(:disabled) { - background: #FF5722; + opacity: 0.8; } .action-btn.secondary { @@ -1011,6 +1102,15 @@ onUnmounted(() => { margin-top: 16px; } +.action-group.dual { + display: grid; + grid-template-columns: 1fr 1fr; +} + +.action-group.dual .action-btn { + width: 100%; +} + /* Info Card */ .info-card { background: #F5F5F5; @@ -1923,14 +2023,25 @@ onUnmounted(() => { } .narrative-box .box-label { - display: block; + display: flex; + align-items: center; + gap: 8px; color: #666; - font-size: 12px; + font-size: 13px; letter-spacing: 0.5px; margin-bottom: 12px; font-weight: 600; } +.special-icon { + filter: drop-shadow(0 2px 4px rgba(255, 87, 34, 0.2)); + transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.narrative-box:hover .special-icon { + transform: rotate(180deg); +} + .narrative-text { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 14px; @@ -2039,5 +2150,223 @@ onUnmounted(() => { line-height: 1.5; margin: 0; } + +/* 模拟轮数配置样式 */ +.rounds-config-section { + margin: 20px 0; + padding: 16px; + background: #FAFAFA; + border-radius: 8px; + border: 1px solid #E5E5E5; +} + +.rounds-notice { + display: flex; + gap: 12px; + align-items: flex-start; + padding: 14px; + background: #FFF8E1; + border-radius: 6px; + border-left: 3px solid #FFC107; + margin-bottom: 16px; +} + +.notice-icon { + font-size: 20px; + line-height: 1; +} + +.notice-content { + flex: 1; +} + +.notice-main { + font-size: 13px; + color: #333; + line-height: 1.6; + margin: 0 0 6px 0; +} + +.notice-main strong { + color: #E65100; +} + +.notice-sub { + font-size: 12px; + color: #666; + line-height: 1.5; + margin: 0; +} + +.notice-sub strong { + color: #2E7D32; +} + +.rounds-control { + padding: 0 4px; +} + +.custom-checkbox { + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + gap: 10px; +} + +.custom-checkbox input[type="checkbox"] { + display: none; +} + +.checkmark { + width: 18px; + height: 18px; + border: 2px solid #CCC; + border-radius: 4px; + position: relative; + transition: all 0.2s; + flex-shrink: 0; +} + +.custom-checkbox input:checked + .checkmark { + background: #FF5722; + border-color: #FF5722; +} + +.custom-checkbox input:checked + .checkmark::after { + content: ''; + position: absolute; + left: 5px; + top: 2px; + width: 5px; + height: 9px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.checkbox-label { + font-size: 13px; + font-weight: 500; + color: #333; + display: flex; + align-items: center; + gap: 8px; +} + +.recommend-badge { + font-size: 10px; + font-weight: 600; + color: #FFF; + background: #4CAF50; + padding: 2px 6px; + border-radius: 3px; + text-transform: uppercase; +} + +.slider-container { + margin-top: 16px; + padding: 16px; + background: #FFF; + border-radius: 6px; + border: 1px solid #E5E5E5; +} + +.slider-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.slider-label { + font-size: 12px; + color: #666; + font-weight: 500; +} + +.slider-value { + font-family: 'JetBrains Mono', monospace; + font-size: 16px; + font-weight: 700; + color: #FF5722; +} + +.rounds-slider { + width: 100%; + height: 6px; + -webkit-appearance: none; + appearance: none; + background: linear-gradient(to right, #E0E0E0, #E0E0E0); + border-radius: 3px; + outline: none; + cursor: pointer; +} + +.rounds-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: #FF5722; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 6px rgba(255, 87, 34, 0.3); + transition: transform 0.2s; +} + +.rounds-slider::-webkit-slider-thumb:hover { + transform: scale(1.1); +} + +.rounds-slider::-moz-range-thumb { + width: 20px; + height: 20px; + background: #FF5722; + border: none; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 6px rgba(255, 87, 34, 0.3); +} + +.slider-marks { + display: flex; + justify-content: space-between; + margin-top: 8px; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: #999; +} + +.mark-recommend { + color: #4CAF50; + font-weight: 600; +} + +.slider-estimate { + margin: 12px 0 0 0; + font-size: 12px; + color: #666; + text-align: center; + padding: 8px; + background: #F5F5F5; + border-radius: 4px; +} + +.auto-mode-hint { + margin-top: 12px; + padding: 12px; + background: #F5F5F5; + border-radius: 6px; + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: #666; +} + +.hint-icon { + font-size: 14px; +} diff --git a/frontend/src/views/MainView.vue b/frontend/src/views/MainView.vue index 333efe9..2a152d9 100644 --- a/frontend/src/views/MainView.vue +++ b/frontend/src/views/MainView.vue @@ -156,10 +156,15 @@ const toggleMaximize = (target) => { } } -const handleNextStep = () => { +const handleNextStep = (params = {}) => { if (currentStep.value < 5) { currentStep.value++ addLog(`进入 Step ${currentStep.value}: ${stepNames[currentStep.value - 1]}`) + + // 如果是从 Step 2 进入 Step 3,记录模拟轮数配置 + if (currentStep.value === 3 && params.maxRounds) { + addLog(`自定义模拟轮数: ${params.maxRounds} 轮`) + } } } diff --git a/frontend/src/views/SimulationView.vue b/frontend/src/views/SimulationView.vue index db567a2..12053e8 100644 --- a/frontend/src/views/SimulationView.vue +++ b/frontend/src/views/SimulationView.vue @@ -145,10 +145,24 @@ const handleGoBack = () => { } } -const handleNextStep = () => { +const handleNextStep = (params = {}) => { addLog('进入 Step 3: 开始模拟') - // TODO: 跳转到 Step 3 - alert('Step 3: 开始模拟 - Coming soon...') + + // 记录模拟轮数配置 + if (params.maxRounds) { + addLog(`自定义模拟轮数: ${params.maxRounds} 轮`) + } else { + addLog('使用自动配置的模拟轮数') + } + + // TODO: 调用 startSimulation API 并跳转到 Step 3 + // 可以在这里调用 /api/simulation/start 接口 + // const startParams = { + // simulation_id: currentSimulationId.value, + // ...(params.maxRounds && { max_rounds: params.maxRounds }) + // } + + alert(`Step 3: 开始模拟 - Coming soon...\n${params.maxRounds ? `轮数: ${params.maxRounds}` : '使用自动配置轮数'}`) } // --- Data Logic ---