Files
X-Financial/web/src/composables/useOperationFeedback.js
caoxiaozhu 7989f3a159 feat: 新增风险图谱算法与系统仪表盘及操作反馈体系
后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL
校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计,
优化 agent 运行和编排执行链路,清理旧开发文档,前端新增
系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈
对话框和工作台日期选择器,优化报销创建和审批详情交互,
补充单元测试覆盖。
2026-05-30 15:46:51 +08:00

162 lines
4.9 KiB
JavaScript

import { ref } from 'vue'
import { createOperationFeedback } from '../services/operationFeedback.js'
const LOW_RATING_MAX = 3
function pickText(...values) {
for (const value of values) {
const normalized = String(value || '').trim()
if (normalized) {
return normalized
}
}
return ''
}
function resolveUserId(currentUser = {}) {
return pickText(
currentUser.username,
currentUser.email,
currentUser.name,
'anonymous'
)
}
export function normalizeOperationFeedbackContext(context = {}, currentUser = {}) {
const traceSummary = context.trace_summary || context.traceSummary || null
const result = context.result && typeof context.result === 'object' ? context.result : {}
const resultSummary = pickText(
context.result_summary,
context.resultSummary,
result.answer,
result.message
)
return {
runId: pickText(context.run_id, context.runId),
conversationId: pickText(context.conversation_id, context.conversationId),
userId: pickText(context.user_id, context.userId, resolveUserId(currentUser)),
agent: pickText(context.selected_agent, context.selectedAgent, context.agent),
source: pickText(context.source, 'user_message'),
sessionType: pickText(context.session_type, context.sessionType),
operationType: pickText(context.operation_type, context.operationType, 'assistant_round'),
operationStatus: pickText(context.operation_status, context.operationStatus, context.status),
routeReason: pickText(context.route_reason, context.routeReason),
entrySource: pickText(context.entry_source, context.entrySource),
traceSummary,
resultSummary: resultSummary.slice(0, 500)
}
}
export function buildOperationFeedbackPayload(context = {}, feedback = {}, currentUser = {}) {
const normalizedContext = normalizeOperationFeedbackContext(context, currentUser)
const rating = Number(feedback.rating || 0)
const reason = String(feedback.reason || '').trim()
return {
run_id: normalizedContext.runId || null,
conversation_id: normalizedContext.conversationId || null,
user_id: normalizedContext.userId || null,
agent: normalizedContext.agent || null,
source: normalizedContext.source || null,
session_type: normalizedContext.sessionType || null,
operation_type: normalizedContext.operationType || 'assistant_round',
operation_status: normalizedContext.operationStatus || null,
rating,
reason: reason || null,
context_json: {
entry_source: normalizedContext.entrySource,
route_reason: normalizedContext.routeReason,
trace_summary: normalizedContext.traceSummary,
result_summary: normalizedContext.resultSummary,
low_rating: rating > 0 && rating <= LOW_RATING_MAX
}
}
}
export function useOperationFeedback({ currentUser, toast } = {}) {
const operationFeedbackDialog = ref({
open: false,
submitting: false,
error: '',
context: null
})
const promptedRunIds = new Set()
function openOperationFeedback(context = {}) {
const normalized = normalizeOperationFeedbackContext(context, currentUser?.value || {})
if (!normalized.runId || promptedRunIds.has(normalized.runId)) {
return
}
if (normalized.operationStatus && normalized.operationStatus !== 'succeeded') {
return
}
promptedRunIds.add(normalized.runId)
globalThis.setTimeout(() => {
operationFeedbackDialog.value = {
open: true,
submitting: false,
error: '',
context: normalized
}
}, 320)
}
function closeOperationFeedback() {
if (operationFeedbackDialog.value.submitting) {
return
}
operationFeedbackDialog.value = {
...operationFeedbackDialog.value,
open: false,
error: ''
}
}
async function submitOperationFeedbackRating(feedback = {}) {
const rating = Number(feedback.rating || 0)
if (!Number.isInteger(rating) || rating < 1 || rating > 5) {
operationFeedbackDialog.value = {
...operationFeedbackDialog.value,
error: '请选择 1 到 5 星评分。'
}
return
}
const context = operationFeedbackDialog.value.context || {}
operationFeedbackDialog.value = {
...operationFeedbackDialog.value,
submitting: true,
error: ''
}
try {
await createOperationFeedback(
buildOperationFeedbackPayload(context, feedback, currentUser?.value || {})
)
operationFeedbackDialog.value = {
open: false,
submitting: false,
error: '',
context: null
}
toast?.('评价已记录,后续会纳入智能体质量统计。')
} catch (error) {
operationFeedbackDialog.value = {
...operationFeedbackDialog.value,
submitting: false,
error: error?.message || '评价提交失败,请稍后重试。'
}
}
}
return {
operationFeedbackDialog,
openOperationFeedback,
closeOperationFeedback,
submitOperationFeedbackRating
}
}