Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/app/services/simulation_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -1757,8 +1757,8 @@ def get_interview_history(
)
results.extend(platform_results)

# 按时间降序排序
results.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
# 按时间降序排序(不同平台 created_at 可能是 int 或 datetime 字符串,统一转字符串避免类型比较报错)
results.sort(key=lambda x: str(x.get("timestamp", "")), reverse=True)

# 如果查询了多个平台,限制总数
if len(platforms) > 1 and len(results) > limit:
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/api/simulation.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ export const interviewAgents = (data) => {
return requestWithRetry(() => service.post('/api/simulation/interview/batch', data), 3, 1000)
}

/**
* 获取某个模拟的采访历史(持久化记录,从 OASIS 数据库读取)
* @param {string} simulationId
* @param {Object} opts - { platform?, agent_id?, limit? }
*/
export const getInterviewHistory = (simulationId, opts = {}) => {
return service.post('/api/simulation/interview/history', {
simulation_id: simulationId,
...opts
})
}

/**
* 获取历史模拟列表(带项目详情)
* 用于首页历史项目展示
Expand Down
57 changes: 56 additions & 1 deletion frontend/src/components/Step5Interaction.vue
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { chatWithReport, getReport, getAgentLog } from '../api/report'
import { interviewAgents, getSimulationProfilesRealtime } from '../api/simulation'
import { interviewAgents, getSimulationProfilesRealtime, getInterviewHistory } from '../api/simulation'

const { t } = useI18n()

Expand Down Expand Up @@ -928,6 +928,60 @@ const loadProfiles = async () => {
}
}

// 去掉 optimize_interview_prompt 注入的前缀,仅展示用户真实问题
const stripInterviewPrefix = (p) => {
if (!p) return ''
for (const marker of ['我的问题是:', '我的问题是:', '问题:', '问题:']) {
const idx = p.indexOf(marker)
if (idx >= 0) return p.slice(idx + marker.length).trim()
}
return p
}

// 加载已持久化的采访历史(来自 OASIS 数据库),按 agent 填充对话缓存(刷新后仍可见)
const loadInterviewHistory = async () => {
if (!props.simulationId) return
try {
const res = await getInterviewHistory(props.simulationId, { limit: 200 })
// 后端返回 { history: [{ agent_id, prompt, response, platform, timestamp }] }(按时间降序)
const records = res.success && res.data ? (res.data.history || []) : []
if (!records.length) return

// 按 agent_id 分组,时间升序,按(问题+回答)去重(同一问题可能跨双平台各记一条)
const byAgent = {}
records
.slice()
.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp)))
.forEach(rec => {
const aid = rec.agent_id
if (!(aid in byAgent)) byAgent[aid] = { seen: new Set(), msgs: [] }
const prompt = stripInterviewPrefix(rec.prompt || '')
const answer = typeof rec.response === 'string' ? rec.response : JSON.stringify(rec.response)
const dedupeKey = prompt + '|' + answer
if (byAgent[aid].seen.has(dedupeKey)) return
byAgent[aid].seen.add(dedupeKey)
byAgent[aid].msgs.push(
{ role: 'user', content: prompt, timestamp: rec.timestamp },
{ role: 'assistant', content: answer, timestamp: rec.timestamp }
)
})

// 历史在前,本会话新对话在后
Object.keys(byAgent).forEach(aid => {
const key = `agent_${aid}`
chatHistoryCache.value[key] = [...byAgent[aid].msgs, ...(chatHistoryCache.value[key] || [])]
})

// 若已选中某个 Agent,立即刷新其对话视图
if (chatTarget.value !== 'report_agent' && selectedAgentIndex.value != null) {
chatHistory.value = chatHistoryCache.value[`agent_${selectedAgentIndex.value}`] || []
}
addLog(`Loaded ${records.length} interview record(s) from history`)
} catch (err) {
console.warn('加载采访历史失败:', err)
}
}

// Click outside to close dropdown
const handleClickOutside = (e) => {
const dropdown = document.querySelector('.agent-dropdown')
Expand All @@ -941,6 +995,7 @@ onMounted(() => {
addLog(t('log.step5Init'))
loadReportData()
loadProfiles()
loadInterviewHistory()
document.addEventListener('click', handleClickOutside)
})

Expand Down