feat(web): update views and services
Views: - AppShellRouteView.vue: update route view - SettingsView.vue: update settings view - TravelReimbursementCreateView.vue: update travel form view - scripts/SettingsView.js: update settings view logic - scripts/TravelReimbursementCreateView.js: update travel form logic Services: - services/orchestrator.js: update orchestrator service client
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { useSystemState } from '../../composables/useSystemState.js'
|
||||
import { fetchSettings, saveSettings, testModelConnectivity } from '../../services/settings.js'
|
||||
@@ -19,14 +19,22 @@ const SECTION_DEFINITIONS = [
|
||||
longDesc: '统一维护企业名称、系统显示名称和版权信息,保存后会同步更新当前系统品牌展示。',
|
||||
actionLabel: '保存企业信息'
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
label: '管理员安全',
|
||||
title: '管理员账号与安全策略',
|
||||
desc: '账号、密码与登录安全',
|
||||
longDesc: '集中管理管理员账号、邮箱和登录安全策略。密码仅在当前输入时可见,不会写入浏览器草稿。',
|
||||
actionLabel: '保存安全设置'
|
||||
},
|
||||
{
|
||||
id: 'admin',
|
||||
label: '管理员安全',
|
||||
title: '管理员账号与安全策略',
|
||||
desc: '账号、密码与登录安全',
|
||||
longDesc: '集中管理管理员账号、邮箱和登录安全策略。密码仅在当前输入时可见,不会写入浏览器草稿。',
|
||||
actionLabel: '保存安全设置'
|
||||
},
|
||||
{
|
||||
id: 'session',
|
||||
label: '会话设置',
|
||||
title: '会话留存设置',
|
||||
desc: '会话保留天数',
|
||||
longDesc: '统一配置智能体会话的保留天数。超过保留期的历史会话会在后端清理,避免上下文和管理成本无限增长。',
|
||||
actionLabel: '保存会话设置'
|
||||
},
|
||||
{
|
||||
id: 'llm',
|
||||
label: '大语言模型',
|
||||
@@ -127,7 +135,11 @@ const MODEL_TEST_CONFIGS = {
|
||||
}
|
||||
}
|
||||
|
||||
const MODEL_API_KEY_CONFIGS = Object.values(MODEL_TEST_CONFIGS)
|
||||
const MODEL_API_KEY_CONFIGS = Object.values(MODEL_TEST_CONFIGS)
|
||||
const SESSION_RETENTION_OPTIONS = Array.from({ length: 10 }, (_item, index) => ({
|
||||
value: index + 1,
|
||||
label: `${index + 1} 天`
|
||||
}))
|
||||
|
||||
function normalizeValue(value) {
|
||||
return String(value ?? '').trim()
|
||||
@@ -168,18 +180,21 @@ function buildDefaultState(companyProfile, currentUser) {
|
||||
recordNumber: '',
|
||||
copyright: `Copyright © 2024-${CURRENT_YEAR} ${companyName}. All Rights Reserved.`
|
||||
},
|
||||
adminForm: {
|
||||
adminAccount,
|
||||
adminEmail,
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
adminForm: {
|
||||
adminAccount,
|
||||
adminEmail,
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
sessionTimeout: Number(import.meta.env.VITE_AUTH_IDLE_TIMEOUT_MINUTES || 30),
|
||||
noticeEmail: adminEmail,
|
||||
mfaEnabled: true,
|
||||
strongPassword: true,
|
||||
loginAlertEnabled: true,
|
||||
adminPasswordConfigured: false
|
||||
},
|
||||
strongPassword: true,
|
||||
loginAlertEnabled: true,
|
||||
adminPasswordConfigured: false
|
||||
},
|
||||
sessionForm: {
|
||||
conversationRetentionDays: 3
|
||||
},
|
||||
llmForm: {
|
||||
mainProvider: 'Codex',
|
||||
mainModel: 'codex-mini-latest',
|
||||
@@ -266,6 +281,7 @@ function mergeState(baseState, overrideState) {
|
||||
return {
|
||||
companyForm: { ...baseState.companyForm, ...(overrideState?.companyForm || {}) },
|
||||
adminForm: { ...baseState.adminForm, ...(overrideState?.adminForm || {}) },
|
||||
sessionForm: { ...baseState.sessionForm, ...(overrideState?.sessionForm || {}) },
|
||||
llmForm: mergedLlmForm,
|
||||
renderForm: { ...baseState.renderForm, ...(overrideState?.renderForm || {}) },
|
||||
logForm: { ...baseState.logForm, ...(overrideState?.logForm || {}) },
|
||||
@@ -276,11 +292,12 @@ function mergeState(baseState, overrideState) {
|
||||
function sanitizeForStorage(state) {
|
||||
return {
|
||||
companyForm: { ...state.companyForm },
|
||||
adminForm: {
|
||||
...state.adminForm,
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
adminForm: {
|
||||
...state.adminForm,
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
sessionForm: { ...state.sessionForm },
|
||||
llmForm: {
|
||||
...state.llmForm,
|
||||
mainApiKey: '',
|
||||
@@ -373,11 +390,15 @@ function computeSectionStatus(state) {
|
||||
normalizeValue(state.companyForm.displayName) &&
|
||||
normalizeValue(state.companyForm.copyright)
|
||||
),
|
||||
admin: Boolean(
|
||||
normalizeValue(state.adminForm.adminAccount) &&
|
||||
normalizeValue(state.adminForm.adminEmail) &&
|
||||
Number(state.adminForm.sessionTimeout) >= 5
|
||||
),
|
||||
admin: Boolean(
|
||||
normalizeValue(state.adminForm.adminAccount) &&
|
||||
normalizeValue(state.adminForm.adminEmail) &&
|
||||
Number(state.adminForm.sessionTimeout) >= 5
|
||||
),
|
||||
session: Boolean(
|
||||
Number(state.sessionForm.conversationRetentionDays) >= 1 &&
|
||||
Number(state.sessionForm.conversationRetentionDays) <= 10
|
||||
),
|
||||
llm: Boolean(
|
||||
isModelConfigReady(state.llmForm.mainProvider, state.llmForm.mainModel, state.llmForm.mainEndpoint) &&
|
||||
isModelConfigReady(state.llmForm.backupProvider, state.llmForm.backupModel, state.llmForm.backupEndpoint) &&
|
||||
@@ -414,9 +435,11 @@ export default {
|
||||
const { companyProfile, currentUser, updateCompanyProfilePreview } = useSystemState()
|
||||
|
||||
const buildResolvedDefaults = () => buildDefaultState(companyProfile.value, currentUser.value)
|
||||
const pageState = ref(mergeState(buildResolvedDefaults(), readStoredSettings()))
|
||||
const activeSection = ref('profile')
|
||||
const modelTestState = ref({
|
||||
const pageState = ref(mergeState(buildResolvedDefaults(), readStoredSettings()))
|
||||
const activeSection = ref('profile')
|
||||
const sessionRetentionPickerOpen = ref(false)
|
||||
const sessionRetentionPickerRef = ref(null)
|
||||
const modelTestState = ref({
|
||||
main: { status: 'idle', message: '' },
|
||||
backup: { status: 'idle', message: '' },
|
||||
vlm: { status: 'idle', message: '' },
|
||||
@@ -424,8 +447,9 @@ export default {
|
||||
})
|
||||
|
||||
const sections = SECTION_DEFINITIONS
|
||||
const logLevels = LOG_LEVELS
|
||||
const providerOptions = PROVIDER_OPTIONS
|
||||
const logLevels = LOG_LEVELS
|
||||
const providerOptions = PROVIDER_OPTIONS
|
||||
const sessionRetentionOptions = SESSION_RETENTION_OPTIONS
|
||||
|
||||
const sectionStatus = computed(() => computeSectionStatus(pageState.value))
|
||||
const completedSectionCount = computed(() => Object.values(sectionStatus.value).filter(Boolean).length)
|
||||
@@ -497,6 +521,7 @@ export default {
|
||||
return {
|
||||
companyForm: { ...pageState.value.companyForm },
|
||||
adminForm: { ...pageState.value.adminForm },
|
||||
sessionForm: { ...pageState.value.sessionForm },
|
||||
llmForm: buildLlmPayload(pageState.value.llmForm),
|
||||
renderForm: buildRenderPayload(pageState.value.renderForm),
|
||||
logForm: { ...pageState.value.logForm },
|
||||
@@ -504,7 +529,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
async function persistRemoteSettings(successMessage, options = {}) {
|
||||
async function persistRemoteSettings(successMessage, options = {}) {
|
||||
try {
|
||||
const snapshot = await saveSettings(buildSettingsPayload())
|
||||
applyLoadedSnapshot(snapshot, options)
|
||||
@@ -516,13 +541,40 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
function activateSection(sectionId) {
|
||||
activeSection.value = sectionId
|
||||
}
|
||||
function activateSection(sectionId) {
|
||||
sessionRetentionPickerOpen.value = false
|
||||
activeSection.value = sectionId
|
||||
}
|
||||
|
||||
function toggleBoolean(formKey, field) {
|
||||
pageState.value[formKey][field] = !pageState.value[formKey][field]
|
||||
}
|
||||
function toggleBoolean(formKey, field) {
|
||||
pageState.value[formKey][field] = !pageState.value[formKey][field]
|
||||
}
|
||||
|
||||
function toggleSessionRetentionPicker() {
|
||||
sessionRetentionPickerOpen.value = !sessionRetentionPickerOpen.value
|
||||
}
|
||||
|
||||
function closeSessionRetentionPicker() {
|
||||
sessionRetentionPickerOpen.value = false
|
||||
}
|
||||
|
||||
function selectSessionRetentionDays(value) {
|
||||
pageState.value.sessionForm.conversationRetentionDays = Number(value)
|
||||
closeSessionRetentionPicker()
|
||||
}
|
||||
|
||||
function handleDocumentPointerDown(event) {
|
||||
if (!sessionRetentionPickerOpen.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const target = event.target
|
||||
if (sessionRetentionPickerRef.value?.contains(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
closeSessionRetentionPicker()
|
||||
}
|
||||
|
||||
function applyProviderPreset(testKey) {
|
||||
const config = MODEL_TEST_CONFIGS[testKey]
|
||||
@@ -626,8 +678,8 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
async function saveAdminSection() {
|
||||
const adminForm = pageState.value.adminForm
|
||||
async function saveAdminSection() {
|
||||
const adminForm = pageState.value.adminForm
|
||||
|
||||
if (!normalizeValue(adminForm.adminAccount)) {
|
||||
toast('请输入管理员账号。')
|
||||
@@ -662,7 +714,24 @@ export default {
|
||||
preserveRenderSecret: true,
|
||||
preserveMailPassword: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSessionSection() {
|
||||
const sessionForm = pageState.value.sessionForm
|
||||
const retentionDays = Number(sessionForm.conversationRetentionDays)
|
||||
|
||||
if (retentionDays < 1 || retentionDays > 10) {
|
||||
toast('会话保留天数必须在 1 到 10 天之间。')
|
||||
return
|
||||
}
|
||||
|
||||
await persistRemoteSettings('会话设置已保存。', {
|
||||
preserveModelApiKeys: true,
|
||||
preserveAdminPasswords: true,
|
||||
preserveRenderSecret: true,
|
||||
preserveMailPassword: true
|
||||
})
|
||||
}
|
||||
|
||||
async function saveLlmSection() {
|
||||
const llmForm = pageState.value.llmForm
|
||||
@@ -757,15 +826,20 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection.value === 'admin') {
|
||||
await saveAdminSection()
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection.value === 'llm') {
|
||||
await saveLlmSection()
|
||||
return
|
||||
}
|
||||
if (activeSection.value === 'admin') {
|
||||
await saveAdminSection()
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection.value === 'session') {
|
||||
await saveSessionSection()
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection.value === 'llm') {
|
||||
await saveLlmSection()
|
||||
return
|
||||
}
|
||||
|
||||
if (activeSection.value === 'logs') {
|
||||
await saveLogsSection()
|
||||
@@ -780,14 +854,23 @@ export default {
|
||||
await saveMailSection()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSettingsSnapshot()
|
||||
})
|
||||
|
||||
return {
|
||||
activeSection,
|
||||
activeSectionConfig,
|
||||
activateSection,
|
||||
onMounted(() => {
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('pointerdown', handleDocumentPointerDown)
|
||||
}
|
||||
loadSettingsSnapshot()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (typeof document !== 'undefined') {
|
||||
document.removeEventListener('pointerdown', handleDocumentPointerDown)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
activeSection,
|
||||
activeSectionConfig,
|
||||
activateSection,
|
||||
applyProviderPreset,
|
||||
clearRenderSecretMask,
|
||||
clearModelSecretMask,
|
||||
@@ -795,14 +878,20 @@ export default {
|
||||
getModelTestState,
|
||||
isModelTesting,
|
||||
logLevels,
|
||||
modelTestState,
|
||||
pageState,
|
||||
providerOptions,
|
||||
saveActiveSection,
|
||||
sectionStatus,
|
||||
sections,
|
||||
testModelConnection,
|
||||
toggleBoolean
|
||||
}
|
||||
}
|
||||
}
|
||||
modelTestState,
|
||||
pageState,
|
||||
providerOptions,
|
||||
sessionRetentionOptions,
|
||||
sessionRetentionPickerOpen,
|
||||
sessionRetentionPickerRef,
|
||||
saveActiveSection,
|
||||
sectionStatus,
|
||||
sections,
|
||||
selectSessionRetentionDays,
|
||||
testModelConnection,
|
||||
toggleSessionRetentionPicker,
|
||||
closeSessionRetentionPicker,
|
||||
toggleBoolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { useSystemState } from '../../composables/useSystemState.js'
|
||||
import { recognizeOcrFiles } from '../../services/ocr.js'
|
||||
@@ -63,11 +63,29 @@ function createMessage(role, text, attachments = [], extras = {}) {
|
||||
citations: [],
|
||||
suggestedActions: [],
|
||||
draftPayload: null,
|
||||
reviewPayload: null,
|
||||
riskFlags: [],
|
||||
...extras
|
||||
}
|
||||
}
|
||||
|
||||
function formatMessageTime(value) {
|
||||
if (!value) {
|
||||
return nowTime()
|
||||
}
|
||||
|
||||
const parsed = new Date(value)
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return nowTime()
|
||||
}
|
||||
|
||||
return parsed.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
|
||||
function sanitizeRequest(request) {
|
||||
if (!request) return { ...DEFAULT_REQUEST }
|
||||
return {
|
||||
@@ -129,6 +147,22 @@ function buildMessageMeta(payload, fileNames = []) {
|
||||
return items
|
||||
}
|
||||
|
||||
function buildStoredMessageMeta(messageJson, attachmentNames = []) {
|
||||
const payload = messageJson?.orchestrator_payload
|
||||
if (payload) {
|
||||
return buildMessageMeta(payload, attachmentNames)
|
||||
}
|
||||
|
||||
const items = []
|
||||
if (messageJson?.status) {
|
||||
items.push(`状态: ${messageJson.status}`)
|
||||
}
|
||||
if (attachmentNames.length) {
|
||||
items.push(`附件: ${attachmentNames.length}`)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
function normalizeOcrDocuments(payload) {
|
||||
const documents = Array.isArray(payload?.documents) ? payload.documents : []
|
||||
return documents.slice(0, 5).map((item) => ({
|
||||
@@ -149,6 +183,43 @@ function buildOcrSummary(payload) {
|
||||
return parts.join(';')
|
||||
}
|
||||
|
||||
function inferPreviewKind(file) {
|
||||
const mediaType = String(file?.type || '').toLowerCase()
|
||||
const filename = String(file?.name || '').toLowerCase()
|
||||
if (mediaType.startsWith('image/') || /\.(png|jpg|jpeg|webp|bmp)$/i.test(filename)) {
|
||||
return 'image'
|
||||
}
|
||||
if (mediaType.includes('pdf') || /\.pdf$/i.test(filename)) {
|
||||
return 'pdf'
|
||||
}
|
||||
return 'file'
|
||||
}
|
||||
|
||||
function buildFilePreviews(files, previewRegistry) {
|
||||
return files.map((file) => {
|
||||
const kind = inferPreviewKind(file)
|
||||
if (kind !== 'image') {
|
||||
return {
|
||||
filename: file.name,
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(file)
|
||||
previewRegistry.push(url)
|
||||
return {
|
||||
filename: file.name,
|
||||
kind,
|
||||
url
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function resolveDocumentPreview(filePreviews, filename) {
|
||||
if (!Array.isArray(filePreviews)) return null
|
||||
return filePreviews.find((item) => item.filename === filename) ?? null
|
||||
}
|
||||
|
||||
function buildWelcomeInsight(entrySource, linkedRequest) {
|
||||
return {
|
||||
intent: 'welcome',
|
||||
@@ -163,6 +234,41 @@ function buildWelcomeInsight(entrySource, linkedRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveInitialConversationId(conversation) {
|
||||
return String(conversation?.conversation_id || conversation?.conversationId || '').trim()
|
||||
}
|
||||
|
||||
function resolveInitialDraftClaimId(conversation) {
|
||||
return String(conversation?.draft_claim_id || conversation?.draftClaimId || '').trim()
|
||||
}
|
||||
|
||||
function normalizeInitialConversationMessages(conversation) {
|
||||
const rawMessages = Array.isArray(conversation?.messages) ? conversation.messages : []
|
||||
|
||||
return rawMessages.map((item) => {
|
||||
const messageJson = item?.message_json || item?.messageJson || {}
|
||||
const attachmentNames = Array.isArray(messageJson?.attachment_names)
|
||||
? messageJson.attachment_names.filter(Boolean)
|
||||
: []
|
||||
const orchestratorPayload = messageJson?.orchestrator_payload || null
|
||||
const result = orchestratorPayload?.result || {}
|
||||
|
||||
return createMessage(item.role, item.content, attachmentNames, {
|
||||
id: `restored-${item.id || ++messageSeed}`,
|
||||
time: formatMessageTime(item.created_at || item.createdAt),
|
||||
meta: item.role === 'assistant' ? buildStoredMessageMeta(messageJson, attachmentNames) : [],
|
||||
citations: item.role === 'assistant' && Array.isArray(result?.citations) ? result.citations : [],
|
||||
suggestedActions:
|
||||
item.role === 'assistant' && Array.isArray(result?.suggested_actions)
|
||||
? result.suggested_actions
|
||||
: [],
|
||||
draftPayload: item.role === 'assistant' ? result?.draft_payload || messageJson?.draft_payload || null : null,
|
||||
reviewPayload: item.role === 'assistant' ? result?.review_payload || null : null,
|
||||
riskFlags: item.role === 'assistant' && Array.isArray(result?.risk_flags) ? result.risk_flags : []
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function buildErrorInsight(error, fileNames = []) {
|
||||
return {
|
||||
intent: 'agent',
|
||||
@@ -183,17 +289,19 @@ function buildErrorInsight(error, fileNames = []) {
|
||||
citations: [],
|
||||
suggestedActions: [],
|
||||
draftPayload: null,
|
||||
reviewPayload: null,
|
||||
riskFlags: [],
|
||||
toolCount: 0,
|
||||
failedToolCount: 0,
|
||||
selectedCapabilityCodes: [],
|
||||
filePreviews: [],
|
||||
statusLabel: '失败',
|
||||
statusTone: 'note'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildAgentInsight(payload, fileNames = []) {
|
||||
function buildAgentInsight(payload, fileNames = [], filePreviews = []) {
|
||||
const trace = payload?.trace_summary || {}
|
||||
const result = payload?.result || {}
|
||||
const statusLabel = resolveStatusLabel(payload?.status)
|
||||
@@ -219,12 +327,14 @@ function buildAgentInsight(payload, fileNames = []) {
|
||||
citations: Array.isArray(result?.citations) ? result.citations : [],
|
||||
suggestedActions: Array.isArray(result?.suggested_actions) ? result.suggested_actions : [],
|
||||
draftPayload: result?.draft_payload || null,
|
||||
reviewPayload: result?.review_payload || null,
|
||||
riskFlags: Array.isArray(result?.risk_flags) ? result.risk_flags : [],
|
||||
toolCount: Number(trace?.tool_count || 0),
|
||||
failedToolCount: Number(trace?.failed_tool_count || 0),
|
||||
selectedCapabilityCodes: Array.isArray(trace?.selected_capability_codes)
|
||||
? trace.selected_capability_codes
|
||||
: [],
|
||||
filePreviews,
|
||||
statusLabel,
|
||||
statusTone: resolveStatusTone(payload?.status)
|
||||
}
|
||||
@@ -242,6 +352,10 @@ export default {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
initialConversation: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
entrySource: {
|
||||
type: String,
|
||||
default: 'requests'
|
||||
@@ -260,8 +374,23 @@ export default {
|
||||
const composerDraft = ref('')
|
||||
const attachedFiles = ref([])
|
||||
const submitting = ref(false)
|
||||
const messages = ref([])
|
||||
const linkedRequest = computed(() => sanitizeRequest(props.requestContext))
|
||||
const restoredMessages = normalizeInitialConversationMessages(props.initialConversation)
|
||||
const messages = ref(
|
||||
restoredMessages.length
|
||||
? restoredMessages
|
||||
: [
|
||||
createMessage(
|
||||
'assistant',
|
||||
props.entrySource === 'detail'
|
||||
? `已进入统一对话工作台,当前关联单据 ${linkedRequest.value.id}。你的提问会直接进入真实智能体链路。`
|
||||
: '这里是统一对话入口。你发送的内容会直接进入真实 Orchestrator 和 User Agent。'
|
||||
)
|
||||
]
|
||||
)
|
||||
const conversationId = ref(resolveInitialConversationId(props.initialConversation))
|
||||
const draftClaimId = ref(resolveInitialDraftClaimId(props.initialConversation))
|
||||
const previewRegistry = []
|
||||
|
||||
const currentInsight = ref(buildWelcomeInsight(props.entrySource, linkedRequest.value))
|
||||
const sourceLabel = computed(() => SOURCE_LABELS[props.entrySource] ?? '来自 AI 工作台')
|
||||
@@ -333,15 +462,6 @@ export default {
|
||||
]
|
||||
})
|
||||
|
||||
messages.value = [
|
||||
createMessage(
|
||||
'assistant',
|
||||
props.entrySource === 'detail'
|
||||
? `已进入统一对话工作台,当前关联单据 ${linkedRequest.value.id}。你的提问会直接进入真实智能体链路。`
|
||||
: '这里是统一对话入口。你发送的内容会直接进入真实 Orchestrator 和 User Agent。'
|
||||
)
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
currentInsight.value = buildWelcomeInsight(props.entrySource, linkedRequest.value)
|
||||
if (props.initialPrompt?.trim() || props.initialFiles.length) {
|
||||
@@ -353,6 +473,12 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
for (const url of previewRegistry) {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
})
|
||||
|
||||
function scrollToBottom() {
|
||||
if (!messageListRef.value) return
|
||||
messageListRef.value.scrollTop = messageListRef.value.scrollHeight
|
||||
@@ -412,6 +538,7 @@ export default {
|
||||
const rawText = composerDraft.value.trim()
|
||||
const files = Array.from(attachedFiles.value)
|
||||
const fileNames = files.map((file) => file.name)
|
||||
const filePreviews = buildFilePreviews(files, previewRegistry)
|
||||
const userText =
|
||||
rawText || `我上传了 ${fileNames.length} 份票据,请帮我识别并给出报销建议。`
|
||||
|
||||
@@ -451,6 +578,7 @@ export default {
|
||||
const payload = await runOrchestrator({
|
||||
source: 'user_message',
|
||||
user_id: user.username || user.name || 'anonymous',
|
||||
conversation_id: conversationId.value || null,
|
||||
message: backendMessage,
|
||||
context_json: {
|
||||
role_codes: Array.isArray(user.roleCodes) ? user.roleCodes : [],
|
||||
@@ -461,11 +589,16 @@ export default {
|
||||
request_context: linkedRequest.value,
|
||||
attachment_names: fileNames,
|
||||
attachment_count: fileNames.length,
|
||||
draft_claim_id: draftClaimId.value || undefined,
|
||||
ocr_summary: ocrSummary,
|
||||
ocr_documents: ocrDocuments
|
||||
}
|
||||
})
|
||||
|
||||
conversationId.value = String(payload?.conversation_id || '').trim() || conversationId.value
|
||||
draftClaimId.value =
|
||||
String(payload?.result?.draft_payload?.claim_id || '').trim() || draftClaimId.value
|
||||
|
||||
replaceMessage(
|
||||
pendingMessage.id,
|
||||
createMessage('assistant', payload?.result?.answer || payload?.result?.message || '智能体已完成处理。', [], {
|
||||
@@ -475,10 +608,11 @@ export default {
|
||||
? payload.result.suggested_actions
|
||||
: [],
|
||||
draftPayload: payload?.result?.draft_payload || null,
|
||||
reviewPayload: payload?.result?.review_payload || null,
|
||||
riskFlags: Array.isArray(payload?.result?.risk_flags) ? payload.result.risk_flags : []
|
||||
})
|
||||
)
|
||||
currentInsight.value = buildAgentInsight(payload, fileNames)
|
||||
currentInsight.value = buildAgentInsight(payload, fileNames, filePreviews)
|
||||
} catch (error) {
|
||||
replaceMessage(
|
||||
pendingMessage.id,
|
||||
@@ -514,6 +648,7 @@ export default {
|
||||
composerPlaceholder,
|
||||
currentIntentLabel,
|
||||
shortcuts,
|
||||
resolveDocumentPreview,
|
||||
triggerFileUpload,
|
||||
handleFilesChange,
|
||||
runShortcut,
|
||||
|
||||
Reference in New Issue
Block a user