fix: suppress idle and empty simulation actions
This commit is contained in:
parent
01fd47a363
commit
2929097ef8
|
|
@ -307,8 +307,9 @@ def _check_simulation_prepared(simulation_id: str) -> tuple:
|
|||
# - running: 正在运行,说明准备早就完成了
|
||||
# - completed: 运行完成,说明准备早就完成了
|
||||
# - stopped: 已停止,说明准备早就完成了
|
||||
# - paused: 手动停止后会写入 paused,配置仍然可复用
|
||||
# - failed: 运行失败(但准备是完成的)
|
||||
prepared_statuses = ["ready", "preparing", "running", "completed", "stopped", "failed"]
|
||||
prepared_statuses = ["ready", "preparing", "running", "completed", "stopped", "paused", "failed"]
|
||||
if status in prepared_statuses and config_generated:
|
||||
# 获取文件统计信息
|
||||
profiles_file = os.path.join(simulation_dir, "reddit_profiles.json")
|
||||
|
|
@ -1599,6 +1600,17 @@ def start_simulation():
|
|||
}), 400
|
||||
|
||||
logger.info(f"启用图谱记忆更新: simulation_id={simulation_id}, graph_id={graph_id}")
|
||||
|
||||
existing_run_state = SimulationRunner.get_run_state(simulation_id)
|
||||
if existing_run_state and existing_run_state.runner_status.value not in ["starting", "running"]:
|
||||
logger.info(
|
||||
f"清理已结束的旧运行记录后重新启动: "
|
||||
f"simulation_id={simulation_id}, runner_status={existing_run_state.runner_status.value}"
|
||||
)
|
||||
cleanup_result = SimulationRunner.cleanup_simulation_logs(simulation_id)
|
||||
if not cleanup_result.get("success"):
|
||||
logger.warning(f"清理旧运行记录时出现警告: {cleanup_result.get('errors')}")
|
||||
force_restarted = True
|
||||
|
||||
# 启动模拟
|
||||
run_state = SimulationRunner.start_simulation(
|
||||
|
|
|
|||
|
|
@ -608,7 +608,7 @@ def load_config(config_path: str) -> Dict[str, Any]:
|
|||
|
||||
|
||||
# 需要过滤掉的非核心动作类型(这些动作对分析价值较低)
|
||||
FILTERED_ACTIONS = {'refresh', 'sign_up'}
|
||||
FILTERED_ACTIONS = {'refresh', 'sign_up', 'do_nothing'}
|
||||
|
||||
# 动作类型映射表(数据库中的名称 -> 标准名称)
|
||||
ACTION_TYPE_MAP = {
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@
|
|||
<!-- Main Content: Dual Timeline -->
|
||||
<div class="main-content-area" ref="scrollContainer">
|
||||
<!-- Timeline Header -->
|
||||
<div class="timeline-header" v-if="allActions.length > 0">
|
||||
<div class="timeline-header" v-if="chronologicalActions.length > 0">
|
||||
<div class="timeline-stats">
|
||||
<span class="total-count">TOTAL EVENTS: <span class="mono">{{ allActions.length }}</span></span>
|
||||
<span class="total-count">TOTAL EVENTS: <span class="mono">{{ chronologicalActions.length }}</span></span>
|
||||
<span class="platform-breakdown">
|
||||
<span class="breakdown-item twitter">
|
||||
<svg class="mini-icon" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><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"></path></svg>
|
||||
|
|
@ -189,15 +189,32 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<!-- LIKE_POST: 点赞帖子 -->
|
||||
<template v-if="action.action_type === 'LIKE_POST'">
|
||||
<!-- LIKE_POST / DISLIKE_POST: 点赞/踩帖子 -->
|
||||
<template v-if="action.action_type === 'LIKE_POST' || action.action_type === 'DISLIKE_POST'">
|
||||
<div class="like-info">
|
||||
<svg class="icon-small filled" viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
<span class="like-label">Liked @{{ action.action_args?.post_author_name || 'User' }}'s post</span>
|
||||
<span class="like-label">{{ action.action_type === 'DISLIKE_POST' ? 'Disliked' : 'Liked' }} @{{ action.action_args?.post_author_name || 'User' }}'s post</span>
|
||||
</div>
|
||||
<div v-if="action.action_args?.post_content" class="liked-content">
|
||||
"{{ truncateContent(action.action_args.post_content, 120) }}"
|
||||
</div>
|
||||
<div v-else-if="action.action_args?.post_id" class="comment-context">
|
||||
<span>Post #{{ action.action_args.post_id }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- LIKE_COMMENT / DISLIKE_COMMENT: 点赞/踩评论 -->
|
||||
<template v-if="action.action_type === 'LIKE_COMMENT' || action.action_type === 'DISLIKE_COMMENT'">
|
||||
<div class="like-info">
|
||||
<svg class="icon-small filled" viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
<span class="like-label">{{ action.action_type === 'DISLIKE_COMMENT' ? 'Disliked' : 'Liked' }} @{{ action.action_args?.comment_author_name || 'User' }}'s comment</span>
|
||||
</div>
|
||||
<div v-if="action.action_args?.comment_content" class="liked-content">
|
||||
"{{ truncateContent(action.action_args.comment_content, 120) }}"
|
||||
</div>
|
||||
<div v-else-if="action.action_args?.comment_id" class="comment-context">
|
||||
<span>Comment #{{ action.action_args.comment_id }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- CREATE_COMMENT: 发表评论 -->
|
||||
|
|
@ -224,7 +241,7 @@
|
|||
<template v-if="action.action_type === 'FOLLOW'">
|
||||
<div class="follow-info">
|
||||
<svg class="icon-small" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
|
||||
<span class="follow-label">Followed @{{ action.action_args?.target_user || action.action_args?.user_id || 'User' }}</span>
|
||||
<span class="follow-label">Followed @{{ action.action_args?.target_user_name || action.action_args?.target_user || action.action_args?.user_id || 'User' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -249,7 +266,7 @@
|
|||
</template>
|
||||
|
||||
<!-- 通用回退:未知类型或有 content 但未被上述处理 -->
|
||||
<div v-if="!['CREATE_POST', 'QUOTE_POST', 'REPOST', 'LIKE_POST', 'CREATE_COMMENT', 'SEARCH_POSTS', 'FOLLOW', 'UPVOTE_POST', 'DOWNVOTE_POST', 'DO_NOTHING'].includes(action.action_type) && action.action_args?.content" class="content-text">
|
||||
<div v-if="!['CREATE_POST', 'QUOTE_POST', 'REPOST', 'LIKE_POST', 'DISLIKE_POST', 'LIKE_COMMENT', 'DISLIKE_COMMENT', 'CREATE_COMMENT', 'SEARCH_POSTS', 'FOLLOW', 'UPVOTE_POST', 'DOWNVOTE_POST', 'DO_NOTHING'].includes(action.action_type) && action.action_args?.content" class="content-text">
|
||||
{{ action.action_args.content }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -262,7 +279,7 @@
|
|||
</div>
|
||||
</TransitionGroup>
|
||||
|
||||
<div v-if="allActions.length === 0" class="waiting-state">
|
||||
<div v-if="chronologicalActions.length === 0" class="waiting-state">
|
||||
<div class="pulse-ring"></div>
|
||||
<span>Waiting for agent actions...</span>
|
||||
</div>
|
||||
|
|
@ -326,19 +343,21 @@ const allActions = ref([]) // 所有动作(增量累积)
|
|||
const actionIds = ref(new Set()) // 用于去重的动作ID集合
|
||||
const scrollContainer = ref(null)
|
||||
|
||||
const isDisplayableAction = (action) => action?.action_type !== 'DO_NOTHING'
|
||||
|
||||
// Computed
|
||||
// 按时间顺序显示动作(最新的在最后面,即底部)
|
||||
const chronologicalActions = computed(() => {
|
||||
return allActions.value
|
||||
return allActions.value.filter(isDisplayableAction)
|
||||
})
|
||||
|
||||
// 各平台动作计数
|
||||
const twitterActionsCount = computed(() => {
|
||||
return allActions.value.filter(a => a.platform === 'twitter').length
|
||||
return chronologicalActions.value.filter(a => a.platform === 'twitter').length
|
||||
})
|
||||
|
||||
const redditActionsCount = computed(() => {
|
||||
return allActions.value.filter(a => a.platform === 'reddit').length
|
||||
return chronologicalActions.value.filter(a => a.platform === 'reddit').length
|
||||
})
|
||||
|
||||
// 格式化模拟流逝时间(根据轮次和每轮分钟数计算)
|
||||
|
|
@ -379,15 +398,75 @@ const resetAllState = () => {
|
|||
stopPolling() // 停止之前可能存在的轮询
|
||||
}
|
||||
|
||||
const hasExistingRunData = (data) => {
|
||||
if (!data) return false
|
||||
const runnerStatus = data.runner_status
|
||||
const actionsCount = Number(data.total_actions_count || 0)
|
||||
+ Number(data.twitter_actions_count || 0)
|
||||
+ Number(data.reddit_actions_count || 0)
|
||||
return Boolean(data.started_at)
|
||||
|| actionsCount > 0
|
||||
|| ['starting', 'running', 'completed', 'stopped', 'paused', 'failed'].includes(runnerStatus)
|
||||
}
|
||||
|
||||
const isActiveRun = (data) => {
|
||||
return data?.runner_status === 'starting'
|
||||
|| data?.runner_status === 'running'
|
||||
|| data?.twitter_running
|
||||
|| data?.reddit_running
|
||||
}
|
||||
|
||||
const restoreExistingRun = async (data) => {
|
||||
runStatus.value = data
|
||||
prevTwitterRound.value = data.twitter_current_round || 0
|
||||
prevRedditRound.value = data.reddit_current_round || 0
|
||||
await fetchRunStatusDetail()
|
||||
|
||||
if (isActiveRun(data)) {
|
||||
phase.value = 1
|
||||
emit('update-status', 'processing')
|
||||
startStatusPolling()
|
||||
startDetailPolling()
|
||||
return
|
||||
}
|
||||
|
||||
phase.value = 2
|
||||
emit('update-status', data.runner_status === 'failed' ? 'error' : 'completed')
|
||||
if (data.error) {
|
||||
addLog(t('log.startFailed', { error: data.error }))
|
||||
}
|
||||
}
|
||||
|
||||
const initializeSimulationView = async () => {
|
||||
if (!props.simulationId) {
|
||||
addLog(t('log.errorMissingSimId'))
|
||||
return
|
||||
}
|
||||
|
||||
resetAllState()
|
||||
|
||||
try {
|
||||
const res = await getRunStatus(props.simulationId)
|
||||
if (res.success && hasExistingRunData(res.data)) {
|
||||
await restoreExistingRun(res.data)
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('恢复模拟运行状态失败:', err)
|
||||
}
|
||||
|
||||
await doStartSimulation({ reset: false })
|
||||
}
|
||||
|
||||
// 启动模拟
|
||||
const doStartSimulation = async () => {
|
||||
const doStartSimulation = async ({ reset = true } = {}) => {
|
||||
if (!props.simulationId) {
|
||||
addLog(t('log.errorMissingSimId'))
|
||||
return
|
||||
}
|
||||
|
||||
// 先重置所有状态,确保不会受到上一次模拟的影响
|
||||
resetAllState()
|
||||
if (reset) resetAllState()
|
||||
|
||||
isStarting.value = true
|
||||
startError.value = null
|
||||
|
|
@ -398,7 +477,7 @@ const doStartSimulation = async () => {
|
|||
const params = {
|
||||
simulation_id: props.simulationId,
|
||||
platform: 'parallel',
|
||||
force: true, // 强制重新开始
|
||||
force: false,
|
||||
enable_graph_memory_update: true // 开启动态图谱更新
|
||||
}
|
||||
|
||||
|
|
@ -597,8 +676,10 @@ const getActionTypeLabel = (type) => {
|
|||
'CREATE_POST': 'POST',
|
||||
'REPOST': 'REPOST',
|
||||
'LIKE_POST': 'LIKE',
|
||||
'DISLIKE_POST': 'DISLIKE',
|
||||
'CREATE_COMMENT': 'COMMENT',
|
||||
'LIKE_COMMENT': 'LIKE',
|
||||
'DISLIKE_COMMENT': 'DISLIKE',
|
||||
'DO_NOTHING': 'IDLE',
|
||||
'FOLLOW': 'FOLLOW',
|
||||
'SEARCH_POSTS': 'SEARCH',
|
||||
|
|
@ -614,8 +695,10 @@ const getActionTypeClass = (type) => {
|
|||
'CREATE_POST': 'badge-post',
|
||||
'REPOST': 'badge-action',
|
||||
'LIKE_POST': 'badge-action',
|
||||
'DISLIKE_POST': 'badge-action',
|
||||
'CREATE_COMMENT': 'badge-comment',
|
||||
'LIKE_COMMENT': 'badge-action',
|
||||
'DISLIKE_COMMENT': 'badge-action',
|
||||
'QUOTE_POST': 'badge-post',
|
||||
'FOLLOW': 'badge-meta',
|
||||
'SEARCH_POSTS': 'badge-meta',
|
||||
|
|
@ -690,7 +773,7 @@ watch(() => props.systemLogs?.length, () => {
|
|||
onMounted(() => {
|
||||
addLog(t('log.step3Init'))
|
||||
if (props.simulationId) {
|
||||
doStartSimulation()
|
||||
initializeSimulationView()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -1124,7 +1207,7 @@ onUnmounted(() => {
|
|||
}
|
||||
|
||||
/* Info Blocks (Quote, Repost, etc) */
|
||||
.quoted-block, .repost-content {
|
||||
.quoted-block, .repost-content, .liked-content, .voted-content {
|
||||
background: #F9F9F9;
|
||||
border: 1px solid #EEE;
|
||||
padding: 10px 12px;
|
||||
|
|
@ -1264,4 +1347,4 @@ onUnmounted(() => {
|
|||
animation: spin 0.8s linear infinite;
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue