fix(interview): repair interview-history endpoint and surface past interviews in UI
The interview-history feature already existed (interviews are recorded to each platform's OASIS sqlite DB and `POST /api/simulation/interview/history` reads them) but was broken on two fronts: 1. Backend crash: get_interview_history sorts merged twitter+reddit results by "timestamp", but the two platforms store created_at with different types (one int-like, one datetime string), raising "'<' not supported between instances of 'int' and 'str'". Coerce the sort key to str so mixed-type timestamps sort safely. 2. Step 5 (Interaction) never loaded that history, so past interviews vanished on reload. Add getInterviewHistory() and load it on mount, seeding the per-agent chat cache (chronological, prompt-prefix stripped, deduped across platforms) so selecting an agent shows prior Q&A. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
96096ea0ff
commit
878481eef3
|
|
@ -1757,8 +1757,8 @@ class SimulationRunner:
|
|||
)
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史模拟列表(带项目详情)
|
||||
* 用于首页历史项目展示
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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')
|
||||
|
|
@ -941,6 +995,7 @@ onMounted(() => {
|
|||
addLog(t('log.step5Init'))
|
||||
loadReportData()
|
||||
loadProfiles()
|
||||
loadInterviewHistory()
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue