2026-06-18 22:12:24 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
deleteAiWorkbenchConversation,
|
|
|
|
|
loadAiWorkbenchConversationHistory,
|
|
|
|
|
saveAiWorkbenchConversation
|
|
|
|
|
} from '../src/utils/aiWorkbenchConversationStore.js'
|
|
|
|
|
|
|
|
|
|
function installLocalStorageMock() {
|
|
|
|
|
const store = new Map()
|
|
|
|
|
globalThis.window = {
|
|
|
|
|
localStorage: {
|
|
|
|
|
getItem(key) {
|
|
|
|
|
return store.has(key) ? store.get(key) : null
|
|
|
|
|
},
|
|
|
|
|
setItem(key, value) {
|
|
|
|
|
store.set(key, String(value))
|
|
|
|
|
},
|
|
|
|
|
removeItem(key) {
|
|
|
|
|
store.delete(key)
|
|
|
|
|
},
|
|
|
|
|
clear() {
|
|
|
|
|
store.clear()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return store
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('AI workbench conversation store persists scoped history for sidebar sessions', () => {
|
|
|
|
|
installLocalStorageMock()
|
|
|
|
|
const user = { username: 'caoxiaozhu', email: 'caoxiaozhu@xf.com', name: '曹笑竹' }
|
|
|
|
|
const anotherUser = { username: 'budget-user' }
|
|
|
|
|
|
|
|
|
|
saveAiWorkbenchConversation(user, {
|
|
|
|
|
id: 'conv-first',
|
|
|
|
|
title: '',
|
|
|
|
|
updatedAt: Date.now() - 3000,
|
|
|
|
|
messages: [
|
|
|
|
|
{ id: 'u1', role: 'user', content: '帮我核对差旅报销口径' },
|
|
|
|
|
{ id: 'a1', role: 'assistant', content: '我会根据制度和票据要求继续核对。' }
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
saveAiWorkbenchConversation(user, {
|
|
|
|
|
id: 'conv-second',
|
|
|
|
|
title: '预算占用分析',
|
|
|
|
|
updatedAt: Date.now(),
|
|
|
|
|
stewardState: { intent: 'budget_check' },
|
|
|
|
|
messages: [
|
|
|
|
|
{ id: 'u2', role: 'user', content: '分析本月预算占用' },
|
|
|
|
|
{ id: 'a2', role: 'assistant', content: '本月预算占用需要结合部门额度和已提交单据。' }
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const history = loadAiWorkbenchConversationHistory(user)
|
|
|
|
|
assert.equal(history.length, 2)
|
|
|
|
|
assert.equal(history[0].id, 'conv-second')
|
|
|
|
|
assert.equal(history[0].title, '预算占用分析')
|
|
|
|
|
assert.equal(history[0].stewardState.intent, 'budget_check')
|
|
|
|
|
assert.equal(history[1].title, '帮我核对差旅报销口径')
|
|
|
|
|
assert.equal(history[1].prompt, '帮我核对差旅报销口径')
|
|
|
|
|
assert.ok(history[0].time)
|
|
|
|
|
assert.deepEqual(loadAiWorkbenchConversationHistory(anotherUser), [])
|
|
|
|
|
|
|
|
|
|
const nextHistory = deleteAiWorkbenchConversation(user, 'conv-second')
|
|
|
|
|
assert.equal(nextHistory.length, 1)
|
|
|
|
|
assert.equal(nextHistory[0].id, 'conv-first')
|
|
|
|
|
})
|
2026-06-26 22:42:23 +08:00
|
|
|
|
|
|
|
|
test('AI workbench conversation store preserves stewardRemainingTasks on messages', () => {
|
|
|
|
|
installLocalStorageMock()
|
|
|
|
|
const user = { username: 'caoxiaozhu' }
|
|
|
|
|
const remainingTasks = [
|
|
|
|
|
{ task_id: 't2', task_type: 'reimbursement', ontology_fields: { expense_type: 'meal' } }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
saveAiWorkbenchConversation(user, {
|
|
|
|
|
id: 'conv-multi-task',
|
|
|
|
|
title: '出差+招待费',
|
|
|
|
|
updatedAt: Date.now(),
|
|
|
|
|
messages: [
|
|
|
|
|
{ id: 'u1', role: 'user', content: '出差+报销招待费' },
|
|
|
|
|
{
|
|
|
|
|
id: 'a1',
|
|
|
|
|
role: 'assistant',
|
|
|
|
|
content: '申请草稿已保存',
|
|
|
|
|
stewardRemainingTasks: remainingTasks
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const history = loadAiWorkbenchConversationHistory(user)
|
|
|
|
|
assert.equal(history.length, 1)
|
|
|
|
|
// 历史摘要不要求保留 stewardRemainingTasks,但加载完整会话时消息上应保留。
|
|
|
|
|
// 这里通过 saveAiWorkbenchConversation 的往返确认 normalizeMessage 不会丢弃该字段。
|
|
|
|
|
const stored = JSON.parse(globalThis.window.localStorage.getItem(
|
|
|
|
|
'x-financial:workbench-ai-conversations:caoxiaozhu'
|
|
|
|
|
))
|
|
|
|
|
const conversation = stored.find((item) => item.id === 'conv-multi-task')
|
|
|
|
|
const persistedMessage = conversation.messages.find((m) => m.id === 'a1')
|
|
|
|
|
assert.deepEqual(persistedMessage.stewardRemainingTasks, remainingTasks)
|
|
|
|
|
})
|