feat(web): AI 工作台文件预览/附件关联任务与草稿分支
- 新增 WorkbenchAiFilePreviewDialog 附件预览对话框及 useWorkbenchAiFilePreview,附件支持点击预览 - 新增 attachmentAssociationJobs/linkedReimbursementDraftJobs 前端服务与对应 composable,接入后台任务轮询与状态展示 - 新增 travelReimbursementDraftBranchModel 草稿分支模型,报销关联门控支持跳过/选择草稿 - PersonalWorkbenchAiMode 及各 composable(expense/document/steward/application-preview/attachment-association)重构适配,WorkbenchAiComposer/FileStrip 样式与交互完善 - DocumentsCenter/ReceiptFolder/TravelReimbursementCreate 等视图及 scripts 重构,风险/差旅规划/审批等工具适配 - 新增/更新前端测试:application-result-card、reimbursement-list-preview-fetch、guided-flow、composer-components 等
This commit is contained in:
@@ -286,7 +286,7 @@ const reimbursementTrendHasSignal = computed(() =>
|
||||
)
|
||||
const reimbursementTrendRows = computed(() => sourceReimbursementTrendRows.value)
|
||||
const reimbursementTrendSignalLabel = computed(() =>
|
||||
reimbursementTrendHasSignal.value ? '来自你的真实单据' : '暂无单据时展示空走势'
|
||||
reimbursementTrendHasSignal.value ? '来自您的真实单据' : '暂无单据时展示空走势'
|
||||
)
|
||||
const reimbursementTrendLabels = computed(() => reimbursementTrendRows.value.map((item) => item.label))
|
||||
const reimbursementTrendAmounts = computed(() => reimbursementTrendRows.value.map((item) => item.amount))
|
||||
|
||||
@@ -405,6 +405,8 @@
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<WorkbenchAiFilePreviewDialog :runtime="workbenchAiRuntime" />
|
||||
|
||||
<Transition name="workbench-ai-confirm-fade">
|
||||
<div v-if="deleteDialogOpen" class="workbench-ai-confirm-mask" role="presentation" @click.self="cancelDeleteConversation">
|
||||
<div
|
||||
|
||||
@@ -5,6 +5,7 @@ import { proxyRefs } from 'vue'
|
||||
import orbIcon from '../../assets/workbench-ai-mode-orb-icon.gif'
|
||||
import WorkbenchAiComposer from './workbench-ai/WorkbenchAiComposer.vue'
|
||||
import WorkbenchAiFileStrip from './workbench-ai/WorkbenchAiFileStrip.vue'
|
||||
import WorkbenchAiFilePreviewDialog from './workbench-ai/WorkbenchAiFilePreviewDialog.vue'
|
||||
import { usePersonalWorkbenchAiMode } from '../../composables/workbenchAiMode/usePersonalWorkbenchAiMode.js'
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
v-model="runtime.assistantDraft"
|
||||
maxlength="1000"
|
||||
rows="3"
|
||||
:placeholder="runtime.isAiModeInputLocked ? '费用测算中,请稍等...' : placeholder"
|
||||
:placeholder="runtime.isAiModeInputLocked ? runtime.aiModeInputLockMessage : placeholder"
|
||||
:disabled="runtime.isAiModeInputLocked"
|
||||
@keydown.enter.exact.prevent="runtime.submitAiModePrompt"
|
||||
></textarea>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<Transition name="workbench-ai-preview-fade">
|
||||
<div
|
||||
v-if="preview"
|
||||
class="workbench-ai-file-preview-mask"
|
||||
role="presentation"
|
||||
@click.self="runtime.closeAiModeFilePreview"
|
||||
>
|
||||
<section class="workbench-ai-file-preview-dialog" role="dialog" aria-modal="true" @click.stop>
|
||||
<header class="workbench-ai-file-preview-head">
|
||||
<div>
|
||||
<span class="workbench-ai-file-preview-badge">附件预览</span>
|
||||
<h3>{{ preview.name }}</h3>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="workbench-ai-file-preview-close"
|
||||
aria-label="关闭附件预览"
|
||||
@click="runtime.closeAiModeFilePreview"
|
||||
>
|
||||
<i class="mdi mdi-close"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="workbench-ai-file-preview-body">
|
||||
<div class="workbench-ai-file-preview-source">
|
||||
<img
|
||||
v-if="preview.sourceKind === 'image'"
|
||||
:src="preview.sourceUrl"
|
||||
:alt="preview.name"
|
||||
class="workbench-ai-file-preview-image"
|
||||
/>
|
||||
<iframe
|
||||
v-else-if="preview.sourceKind === 'pdf'"
|
||||
:src="preview.sourceUrl"
|
||||
class="workbench-ai-file-preview-frame"
|
||||
title="附件 PDF 预览"
|
||||
></iframe>
|
||||
<div v-else class="workbench-ai-file-preview-state">
|
||||
<i class="mdi mdi-file-eye-outline"></i>
|
||||
<span>当前附件暂不支持直接预览。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="workbench-ai-file-preview-insight">
|
||||
<div class="workbench-ai-file-preview-insight-head">
|
||||
<span>识别信息</span>
|
||||
<strong>{{ preview.documentTypeLabel }}</strong>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="workbench-ai-file-preview-status"
|
||||
:class="`is-${preview.recognitionStatus}`"
|
||||
:title="preview.recognitionStatusTitle"
|
||||
>
|
||||
<i v-if="preview.recognitionStatus === 'recognizing'" class="mdi mdi-loading"></i>
|
||||
<i v-else-if="preview.recognitionStatus === 'recognized'" class="mdi mdi-text-recognition"></i>
|
||||
<i v-else-if="preview.recognitionStatus === 'failed'" class="mdi mdi-alert-circle-outline"></i>
|
||||
<i v-else class="mdi mdi-file-search-outline"></i>
|
||||
<span>{{ preview.recognitionStatusLabel }}</span>
|
||||
</div>
|
||||
|
||||
<section class="workbench-ai-file-preview-section">
|
||||
<span>文件信息</span>
|
||||
<dl>
|
||||
<template v-for="row in preview.fileInfoRows" :key="row.label">
|
||||
<dt>{{ row.label }}</dt>
|
||||
<dd>{{ row.value }}</dd>
|
||||
</template>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section v-if="preview.ocrFields.length" class="workbench-ai-file-preview-section">
|
||||
<span>字段结果</span>
|
||||
<dl>
|
||||
<template v-for="field in preview.ocrFields" :key="`${field.label}-${field.value}`">
|
||||
<dt>{{ field.label }}</dt>
|
||||
<dd>{{ field.value }}</dd>
|
||||
</template>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section v-if="preview.ocrSummary" class="workbench-ai-file-preview-section">
|
||||
<span>识别摘要</span>
|
||||
<p>{{ preview.ocrSummary }}</p>
|
||||
</section>
|
||||
|
||||
<section v-if="preview.rawText" class="workbench-ai-file-preview-section">
|
||||
<span>OCR 原文</span>
|
||||
<p>{{ preview.rawText }}</p>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
runtime: { type: Object, required: true }
|
||||
})
|
||||
|
||||
const preview = computed(() => props.runtime.activeAiModeFilePreview || null)
|
||||
</script>
|
||||
|
||||
<style scoped src="../../../assets/styles/components/workbench-ai-file-preview-dialog.css"></style>
|
||||
@@ -5,7 +5,17 @@
|
||||
:class="{ inline }"
|
||||
aria-label="已选择附件"
|
||||
>
|
||||
<article v-for="file in runtime.selectedFileCards" :key="file.key" class="workbench-ai-file-card">
|
||||
<article
|
||||
v-for="file in runtime.selectedFileCards"
|
||||
:key="file.key"
|
||||
class="workbench-ai-file-card"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:aria-label="`预览附件 ${file.name}`"
|
||||
@click="runtime.openAiModeFilePreview(file.key)"
|
||||
@keydown.enter.prevent="runtime.openAiModeFilePreview(file.key)"
|
||||
@keydown.space.prevent="runtime.openAiModeFilePreview(file.key)"
|
||||
>
|
||||
<span class="workbench-ai-file-card__icon" :class="`type-${file.tone}`" aria-hidden="true">
|
||||
<i :class="file.icon"></i>
|
||||
</span>
|
||||
@@ -29,7 +39,7 @@
|
||||
class="workbench-ai-file-card__remove"
|
||||
:disabled="runtime.isAiModeInputLocked"
|
||||
:aria-label="`移除附件 ${file.name}`"
|
||||
@click="runtime.removeAiModeFile(file.key)"
|
||||
@click.stop="runtime.removeAiModeFile(file.key)"
|
||||
>
|
||||
<i class="mdi mdi-close"></i>
|
||||
</button>
|
||||
|
||||
@@ -741,7 +741,7 @@ function buildFieldStepDescription() {
|
||||
if (activeSimulationResult.value?.recognized_fields?.length) {
|
||||
return `已确认 ${activeSimulationResult.value.recognized_fields.length} 个字段。`
|
||||
}
|
||||
if (draft.value.trim()) return '将使用你输入的文字抽取测试字段。'
|
||||
if (draft.value.trim()) return '将使用您输入的文字抽取测试字段。'
|
||||
return '识别完成或补充字段后进入确认。'
|
||||
}
|
||||
|
||||
@@ -756,7 +756,7 @@ function buildWelcomeMessage() {
|
||||
if (requiresAttachment.value) {
|
||||
return '这条规则要求测试附件。请先上传临时票据并填写测试意图,点击发送后我会统一识别附件和文字,再交给规则执行器判断。'
|
||||
}
|
||||
return '这条规则不需要上传附件。你可以直接输入测试事实,我只会执行风险识别,不创建单据、不写入主工作台会话。'
|
||||
return '这条规则不需要上传附件。您可以直接输入测试事实,我只会执行风险识别,不创建单据、不写入主工作台会话。'
|
||||
}
|
||||
|
||||
function clearFileInput() {
|
||||
|
||||
@@ -505,7 +505,7 @@
|
||||
<i class="mdi mdi-text-recognition"></i>
|
||||
</span>
|
||||
<strong>暂无结构化字段</strong>
|
||||
<p>当前只返回了摘要信息,你仍然可以直接修改上面的票据摘要。</p>
|
||||
<p>当前只返回了摘要信息,您仍然可以直接修改上面的票据摘要。</p>
|
||||
</div>
|
||||
|
||||
<div v-if="ui.activeReviewDocument.warnings?.length" class="review-document-warning-list">
|
||||
|
||||
@@ -549,7 +549,7 @@
|
||||
{{ line }}
|
||||
</p>
|
||||
<p v-if="ui.canUseInlineSaveDraft(message)" class="review-inline-save-copy">
|
||||
请核查上面的关键信息。您也可以暂时不处理上述的这些内容,我可以帮你先保存为
|
||||
请核查上面的关键信息。您也可以暂时不处理上述的这些内容,我可以帮您先保存为
|
||||
<button
|
||||
type="button"
|
||||
class="review-inline-draft-link"
|
||||
|
||||
Reference in New Issue
Block a user