feat(web): update Vue components and composables

- PersonalWorkbench.vue: update personal workbench component
- useAppShell.js: update app shell composable
- useChat.js: update chat composable with new features
- AppShellRouteView.vue: update route view
- ChatView.vue: update chat view with enhanced UI
- TravelReimbursementCreateView.vue: update travel reimbursement form
- ChatView.js: update chat view script logic
- TravelReimbursementCreateView.js: update travel form script logic
This commit is contained in:
caoxiaozhu
2026-05-12 01:27:49 +00:00
parent e3548dfaba
commit 93b1a5e746
8 changed files with 976 additions and 550 deletions

View File

@@ -14,13 +14,25 @@ export function useAppShell() {
const travelCreateMode = ref(false)
const smartEntryOpen = ref(false)
const smartEntryContext = ref({ prompt: '', source: 'requests', request: null })
const smartEntryContext = ref({ prompt: '', source: 'requests', request: null, files: [] })
const smartEntrySessionId = ref(0)
const { activeView, currentView, setView } = useNavigation()
const { requests, search, filters, ranges, activeRange, filteredRequests, approveRequest, rejectRequest } =
useRequests()
const { messages, draft, uploadedFiles, messageList, activeCase, prompts, sendMessage, handleUpload, openChat, openNewChat } =
const {
messages,
draft,
uploadedFiles,
messageList,
activeCase,
prompts,
sending,
sendMessage,
handleUpload,
openChat,
openNewChat
} =
useChat(activeView)
const { toast } = useToast()
@@ -81,7 +93,7 @@ export function useAppShell() {
function openTravelCreate() {
smartEntryOpen.value = true
travelCreateMode.value = false
smartEntryContext.value = { prompt: '', source: 'topbar', request: null }
smartEntryContext.value = { prompt: '', source: 'topbar', request: null, files: [] }
smartEntrySessionId.value += 1
}
@@ -92,7 +104,8 @@ export function useAppShell() {
smartEntryContext.value = {
prompt: payload.prompt ?? '',
source: payload.source ?? 'workbench',
request: payload.request ?? selectedTravelRequest.value
request: payload.request ?? selectedTravelRequest.value,
files: Array.isArray(payload.files) ? payload.files : []
}
smartEntrySessionId.value += 1
}
@@ -145,6 +158,7 @@ export function useAppShell() {
search,
selectedTravelRequest,
sendMessage,
sending,
setView,
smartEntryContext,
smartEntryOpen,

View File

@@ -1,5 +1,8 @@
import { nextTick, ref } from 'vue'
import { useSystemState } from './useSystemState.js'
import { runOrchestrator } from '../services/orchestrator.js'
const initialMessages = [
{
id: 1,
@@ -21,11 +24,13 @@ const initialMessages = [
export const prompts = ['生成审批意见', '列出补件清单', '解释风险原因', '生成运营简报']
export function useChat(activeView) {
const { currentUser } = useSystemState()
const messages = ref([...initialMessages])
const draft = ref('')
const uploadedFiles = ref([])
const messageList = ref(null)
const activeCase = ref(null)
const sending = ref(false)
function agentReply(text) {
const c = activeCase.value
@@ -56,15 +61,113 @@ export function useChat(activeView) {
nextTick(() => messageList.value?.scrollTo({ top: messageList.value.scrollHeight, behavior: 'smooth' }))
}
function sendMessage() {
function replaceMessage(id, nextMessage) {
const index = messages.value.findIndex((item) => item.id === id)
if (index === -1) {
messages.value.push(nextMessage)
return
}
messages.value.splice(index, 1, nextMessage)
}
function buildOrchestratorMeta(payload) {
const items = []
if (payload?.selected_agent) {
items.push(`Agent: ${payload.selected_agent}`)
}
if (payload?.permission_level) {
items.push(`权限: ${payload.permission_level}`)
}
if (payload?.trace_summary?.tool_count) {
items.push(`工具: ${payload.trace_summary.tool_count}`)
}
if (payload?.trace_summary?.degraded) {
items.push('已降级')
}
if (payload?.requires_confirmation) {
items.push('待确认')
}
if (payload?.run_id) {
items.push(`Run: ${payload.run_id}`)
}
return items
}
function buildAssistantMessage(payload, fallbackText) {
const result = payload?.result || {}
return {
text: result.answer || result.message || fallbackText,
meta: buildOrchestratorMeta(payload),
citations: Array.isArray(result.citations) ? result.citations : [],
suggestedActions: Array.isArray(result.suggested_actions) ? result.suggested_actions : [],
draftPayload: result.draft_payload || null,
riskFlags: Array.isArray(result.risk_flags) ? result.risk_flags : []
}
}
async function sendMessage() {
const text = draft.value.trim()
if (!text) return false
messages.value.push({ id: Date.now(), role: 'user', text })
if (!text || sending.value) return false
const userMessageId = Date.now()
const pendingMessageId = userMessageId + 1
messages.value.push({ id: userMessageId, role: 'user', text })
draft.value = ''
setTimeout(() => {
messages.value.push({ id: Date.now() + 1, role: 'agent', text: agentReply(text) })
sending.value = true
messages.value.push({
id: pendingMessageId,
role: 'agent',
text: 'Orchestrator 正在处理中...',
meta: ['运行中']
})
scrollToBottom()
const user = currentUser.value || {}
try {
const payload = await runOrchestrator({
source: 'user_message',
user_id: user.username || user.name || 'anonymous',
message: text,
context_json: {
role_codes: Array.isArray(user.roleCodes) ? user.roleCodes : [],
is_admin: Boolean(user.isAdmin),
name: user.name || '',
role: user.role || '',
active_case_id: activeCase.value?.id || ''
}
})
const assistantMessage = buildAssistantMessage(payload, 'Orchestrator 已完成处理。')
replaceMessage(pendingMessageId, {
id: pendingMessageId,
role: 'agent',
...assistantMessage
})
} catch (error) {
replaceMessage(pendingMessageId, {
id: pendingMessageId,
role: 'agent',
text: `后端暂时不可用,已切换为本地占位回复:${agentReply(text)}`,
meta: [error?.message || '后端调用失败'],
citations: [],
suggestedActions: [],
draftPayload: null,
riskFlags: []
})
} finally {
sending.value = false
scrollToBottom()
}, 260)
}
return true
}
@@ -96,7 +199,7 @@ export function useChat(activeView) {
}
return {
messages, draft, uploadedFiles, messageList, activeCase, prompts,
messages, draft, uploadedFiles, messageList, activeCase, prompts, sending,
sendMessage, handleUpload, openChat, openNewChat, scrollToBottom
}
}