chore: 更新个人工作台和差旅报销相关功能
This commit is contained in:
@@ -234,6 +234,255 @@
|
|||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flow-status-chip {
|
||||||
|
min-height: 28px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-status-chip.running {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-status-chip.completed {
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-status-chip.failed {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-icon-btn {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid rgba(203, 213, 225, 0.86);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
color: #475569;
|
||||||
|
font-size: 16px;
|
||||||
|
box-shadow: 0 6px 14px rgba(148, 163, 184, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-icon-btn:hover:not(:disabled) {
|
||||||
|
border-color: rgba(37, 99, 235, 0.28);
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-icon-btn:disabled {
|
||||||
|
opacity: 0.46;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-item {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 34px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-item:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-rail {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-rail::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 34px;
|
||||||
|
bottom: -12px;
|
||||||
|
left: 50%;
|
||||||
|
width: 2px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-item:last-child .flow-step-rail::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-rail span {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 900;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-item.completed .flow-step-rail span {
|
||||||
|
background: #10b981;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-item.running .flow-step-rail span {
|
||||||
|
background: #2563eb;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0 0 5px rgba(37, 99, 235, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-item.failed .flow-step-rail span {
|
||||||
|
background: #ef4444;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-card {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 7px;
|
||||||
|
padding: 13px 14px;
|
||||||
|
border: 1px solid #e5edf5;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 8px 22px rgba(226, 232, 240, 0.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-item.running .flow-step-card {
|
||||||
|
border-color: rgba(37, 99, 235, 0.42);
|
||||||
|
background: linear-gradient(180deg, #f8fbff 0%, #eef6ff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-card header,
|
||||||
|
.flow-step-side {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-card header {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-card strong {
|
||||||
|
min-width: 0;
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-side {
|
||||||
|
flex: none;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-status {
|
||||||
|
min-height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 9px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-status.completed {
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-status.running {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-status.failed {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-side time {
|
||||||
|
min-width: 36px;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 850;
|
||||||
|
text-align: right;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-tool,
|
||||||
|
.flow-step-detail,
|
||||||
|
.flow-step-error {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-detail {
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-step-error {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-empty-state {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
align-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 30px;
|
||||||
|
color: #64748b;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-empty-state i {
|
||||||
|
font-size: 34px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-empty-state strong {
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-empty-state p {
|
||||||
|
max-width: 260px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flowPulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.assistant-layout {
|
.assistant-layout {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -1562,6 +1811,23 @@
|
|||||||
box-shadow: 0 6px 14px rgba(239, 68, 68, 0.16);
|
box-shadow: 0 6px 14px rgba(239, 68, 68, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.review-insight-switch-icon-btn.flow.available {
|
||||||
|
border-color: rgba(37, 99, 235, 0.28);
|
||||||
|
background: rgba(239, 246, 255, 0.96);
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-insight-switch-icon-btn.flow.active {
|
||||||
|
border-color: rgba(37, 99, 235, 0.42);
|
||||||
|
background: rgba(219, 234, 254, 0.98);
|
||||||
|
color: #1d4ed8;
|
||||||
|
box-shadow: 0 6px 14px rgba(37, 99, 235, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-insight-switch-icon-btn.flow.running i {
|
||||||
|
animation: flowPulse 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.review-insight-switch-icon-btn:hover:not(:disabled) {
|
.review-insight-switch-icon-btn:hover:not(:disabled) {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
@@ -2542,6 +2808,49 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.review-flow-panel {
|
||||||
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-flow-summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 34px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-flow-summary .flow-icon-btn {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-flow-list {
|
||||||
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-flow-panel .flow-step-card {
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-flow-panel .flow-empty-state {
|
||||||
|
min-height: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-empty-state.compact {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
.review-message-block {
|
.review-message-block {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
@@ -2558,12 +2867,14 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 20px;
|
border-radius: 18px;
|
||||||
border: 1px solid rgba(16, 185, 129, 0.14);
|
border: 1px solid rgba(37, 99, 235, 0.14);
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top right, rgba(34, 197, 94, 0.08), transparent 28%),
|
radial-gradient(circle at top right, rgba(37, 99, 235, 0.08), transparent 30%),
|
||||||
linear-gradient(180deg, #fbfffd 0%, #f6fbf9 100%);
|
linear-gradient(180deg, #ffffff 0%, #f7fafc 100%);
|
||||||
box-shadow: 0 8px 20px rgba(226, 232, 240, 0.28);
|
box-shadow:
|
||||||
|
0 14px 30px rgba(15, 23, 42, 0.06),
|
||||||
|
0 1px 0 rgba(255, 255, 255, 0.9) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-flow-card {
|
.review-flow-card {
|
||||||
@@ -2651,6 +2962,294 @@
|
|||||||
padding: 12px 4px 0;
|
padding: 12px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.review-followup-panel {
|
||||||
|
display: grid;
|
||||||
|
gap: 0;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel.pending {
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel.ready {
|
||||||
|
border-color: #d1fae5;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel summary {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-head:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-head-main {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-mark {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel.pending .review-followup-mark {
|
||||||
|
background: #fffbeb;
|
||||||
|
border-color: #fde68a;
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel.ready .review-followup-mark {
|
||||||
|
background: #ecfdf5;
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-title-copy {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-title-copy strong {
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-title-copy p {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-preview {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel[open] .review-followup-preview {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-preview span {
|
||||||
|
min-height: 22px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 160px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-side {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-count {
|
||||||
|
min-height: 26px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: none;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 850;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel.pending .review-followup-count {
|
||||||
|
border-color: #fde68a;
|
||||||
|
background: #fffbeb;
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel.ready .review-followup-count {
|
||||||
|
border-color: rgba(16, 185, 129, 0.22);
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #047857;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-chevron {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: transform 0.18s ease, color 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-panel[open] .review-followup-chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-body {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 12px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 0;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 30px minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item.warning .review-followup-icon {
|
||||||
|
background: #fffbeb;
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item.danger .review-followup-icon {
|
||||||
|
background: #fff1f2;
|
||||||
|
color: #e11d48;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item.ready .review-followup-icon {
|
||||||
|
background: #ecfdf5;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-copy {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-copy strong {
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 850;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-copy p {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-status {
|
||||||
|
min-height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 850;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item.warning .review-followup-status {
|
||||||
|
border-color: #fde68a;
|
||||||
|
background: transparent;
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item.danger .review-followup-status {
|
||||||
|
border-color: #fecdd3;
|
||||||
|
background: transparent;
|
||||||
|
color: #e11d48;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-item.ready .review-followup-status {
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
background: transparent;
|
||||||
|
color: #047857;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-helper {
|
||||||
|
margin: 0;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #eef2f7;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
.review-card-head {
|
.review-card-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -2671,10 +3270,10 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: linear-gradient(135deg, #22c55e, #10b981);
|
background: linear-gradient(135deg, #2563eb, #0f766e);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
box-shadow: 0 8px 16px rgba(16, 185, 129, 0.16);
|
box-shadow: 0 8px 16px rgba(37, 99, 235, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-card-head-copy {
|
.review-card-head-copy {
|
||||||
@@ -3674,6 +4273,57 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.welcome-quick-actions {
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px dashed rgba(203, 213, 225, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-quick-actions-title {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-quick-action-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-quick-action-btn {
|
||||||
|
min-height: 36px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border: 1px solid rgba(191, 219, 254, 0.92);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(239, 246, 255, 0.94) 100%);
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 750;
|
||||||
|
box-shadow: 0 6px 14px rgba(59, 130, 246, 0.08);
|
||||||
|
transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-quick-action-btn i {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-quick-action-btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border-color: rgba(59, 130, 246, 0.34);
|
||||||
|
box-shadow: 0 10px 18px rgba(59, 130, 246, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-quick-action-btn:disabled {
|
||||||
|
opacity: 0.48;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.welcome-grid {
|
.welcome-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -3864,6 +4514,7 @@
|
|||||||
.insight-panel-shell:not(.collapsed) {
|
.insight-panel-shell:not(.collapsed) {
|
||||||
max-height: min(34dvh, 360px);
|
max-height: min(34dvh, 360px);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
@media (max-width: 760px) {
|
||||||
@@ -3900,6 +4551,10 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flow-step-card header {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.assistant-layout {
|
.assistant-layout {
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
@@ -3962,6 +4617,19 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.review-followup-head {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-side {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-count {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.metric-grid {
|
.metric-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@@ -3981,6 +4649,15 @@
|
|||||||
justify-self: start;
|
justify-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.review-followup-item {
|
||||||
|
grid-template-columns: 34px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-followup-status {
|
||||||
|
grid-column: 2;
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
.review-footer-btn-row {
|
.review-footer-btn-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,8 +280,21 @@ async function handleExpenseConversationAction() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingAction.value = 'expense'
|
|
||||||
const nextPayload = buildAssistantPayload()
|
const nextPayload = buildAssistantPayload()
|
||||||
|
const shouldOpenImmediately = Boolean(nextPayload.prompt || nextPayload.files.length)
|
||||||
|
|
||||||
|
if (shouldOpenImmediately) {
|
||||||
|
emitAssistant({
|
||||||
|
...nextPayload,
|
||||||
|
conversation: null
|
||||||
|
})
|
||||||
|
void clearKnowledgeHistoryBeforeExpense().catch((error) => {
|
||||||
|
console.warn('Failed to clear knowledge history before expense:', error)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingAction.value = 'expense'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await clearKnowledgeHistoryBeforeExpense()
|
await clearKnowledgeHistoryBeforeExpense()
|
||||||
@@ -1131,4 +1144,3 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<PersonalWorkbench :show-header="false" @open-assistant="emit('openAssistant', $event)" />
|
<PersonalWorkbench
|
||||||
|
:show-header="false"
|
||||||
|
:assistant-modal-open="assistantModalOpen"
|
||||||
|
@open-assistant="emit('openAssistant', $event)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import PersonalWorkbench from '../components/business/PersonalWorkbench.vue'
|
import PersonalWorkbench from '../components/business/PersonalWorkbench.vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
assistantModalOpen: { type: Boolean, default: false }
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['openAssistant'])
|
const emit = defineEmits(['openAssistant'])
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -46,7 +46,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="assistant-layout" :class="{ 'can-show-insight': hasInsightPanelContent, 'has-insight': showInsightPanel }">
|
<div
|
||||||
|
class="assistant-layout"
|
||||||
|
:class="{
|
||||||
|
'can-show-insight': hasInsightPanelContent,
|
||||||
|
'has-insight': showInsightPanel
|
||||||
|
}"
|
||||||
|
>
|
||||||
<section class="dialog-panel">
|
<section class="dialog-panel">
|
||||||
<div v-if="shortcuts.length" class="dialog-toolbar">
|
<div v-if="shortcuts.length" class="dialog-toolbar">
|
||||||
<button
|
<button
|
||||||
@@ -72,7 +78,7 @@
|
|||||||
<span class="message-avatar">
|
<span class="message-avatar">
|
||||||
<img
|
<img
|
||||||
:src="message.role === 'assistant' ? aiAvatar : userAvatar"
|
:src="message.role === 'assistant' ? aiAvatar : userAvatar"
|
||||||
:alt="message.role === 'assistant' ? 'AI 助手头像' : '用户头像'"
|
:alt="message.role === 'assistant' ? '财务助手头像' : '用户头像'"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -92,9 +98,9 @@
|
|||||||
v-else-if="message.text && message.role === 'assistant'"
|
v-else-if="message.text && message.role === 'assistant'"
|
||||||
class="message-answer-content message-answer-markdown"
|
class="message-answer-content message-answer-markdown"
|
||||||
v-html="renderMarkdown(message.text)"
|
v-html="renderMarkdown(message.text)"
|
||||||
></motion>
|
></div>
|
||||||
|
|
||||||
<motion
|
<div
|
||||||
v-if="message.role === 'assistant' && message.welcomeQuickActions?.length"
|
v-if="message.role === 'assistant' && message.welcomeQuickActions?.length"
|
||||||
class="welcome-quick-actions"
|
class="welcome-quick-actions"
|
||||||
>
|
>
|
||||||
@@ -111,12 +117,12 @@
|
|||||||
<i :class="action.icon"></i>
|
<i :class="action.icon"></i>
|
||||||
<span>{{ action.label }}</span>
|
<span>{{ action.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
</motion>
|
</div>
|
||||||
</motion>
|
</div>
|
||||||
|
|
||||||
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.meta?.length" class="message-meta-row">
|
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.meta?.length" class="message-meta-row">
|
||||||
<span v-for="item in message.meta" :key="item" class="message-meta-chip">{{ item }}</span>
|
<span v-for="item in message.meta" :key="item" class="message-meta-chip">{{ item }}</span>
|
||||||
</motion>
|
</div>
|
||||||
|
|
||||||
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.riskFlags?.length" class="message-detail-block">
|
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.riskFlags?.length" class="message-detail-block">
|
||||||
<strong>风险标签</strong>
|
<strong>风险标签</strong>
|
||||||
@@ -256,35 +262,62 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="review-disclosure-card" :open="shouldOpenReviewDisclosure(message.reviewPayload)">
|
<details
|
||||||
<summary class="review-disclosure-summary">
|
class="review-followup-panel"
|
||||||
<div class="review-disclosure-copy">
|
:class="buildReviewStateTone(message.reviewPayload, message.draftPayload)"
|
||||||
<strong>{{ buildReviewDisclosureTitle(message.reviewPayload) }}</strong>
|
:open="shouldOpenReviewDisclosure(message.reviewPayload)"
|
||||||
<p>{{ buildReviewDisclosureHint(message.reviewPayload) }}</p>
|
>
|
||||||
|
<summary class="review-followup-head">
|
||||||
|
<div class="review-followup-head-main">
|
||||||
|
<span class="review-followup-mark">
|
||||||
|
<i :class="buildReviewStateTone(message.reviewPayload, message.draftPayload) === 'ready' ? 'mdi mdi-clipboard-check-outline' : 'mdi mdi-clipboard-text-clock-outline'"></i>
|
||||||
|
</span>
|
||||||
|
<div class="review-followup-title-copy">
|
||||||
|
<strong>{{ buildReviewTodoSectionTitle(message.reviewPayload) }}</strong>
|
||||||
|
<p>{{ buildReviewDisclosureHint(message.reviewPayload) }}</p>
|
||||||
|
<div class="review-followup-preview">
|
||||||
|
<span
|
||||||
|
v-for="item in buildReviewTodoItems(message.reviewPayload).slice(0, 2)"
|
||||||
|
:key="`${message.id}-preview-${item.key}`"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
|
<span v-if="buildReviewTodoItems(message.reviewPayload).length > 2">
|
||||||
|
+{{ buildReviewTodoItems(message.reviewPayload).length - 2 }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="review-followup-side">
|
||||||
|
<span class="review-followup-count">{{ buildReviewTodoSectionMeta(message.reviewPayload) }}</span>
|
||||||
|
<span class="review-followup-chevron">
|
||||||
|
<i class="mdi mdi-chevron-down"></i>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="review-disclosure-toggle">
|
|
||||||
<i class="mdi mdi-chevron-down"></i>
|
|
||||||
</span>
|
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
<div class="review-disclosure-body">
|
<div class="review-followup-body">
|
||||||
<div class="review-pending-list plain">
|
<div class="review-followup-list">
|
||||||
<!-- 待补充信息项 -->
|
|
||||||
<article
|
<article
|
||||||
v-for="item in buildReviewTodoItems(message.reviewPayload)"
|
v-for="item in buildReviewTodoItems(message.reviewPayload)"
|
||||||
:key="`${message.id}-${item.key}`"
|
:key="`${message.id}-${item.key}`"
|
||||||
class="review-pending-item"
|
class="review-followup-item"
|
||||||
|
:class="item.tone"
|
||||||
>
|
>
|
||||||
<span class="review-pending-icon">
|
<span class="review-followup-icon">
|
||||||
<i :class="item.icon"></i>
|
<i :class="item.icon"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="review-pending-copy">
|
<div class="review-followup-copy">
|
||||||
<strong>{{ item.title }}</strong>
|
<strong>{{ item.title }}</strong>
|
||||||
<p>{{ item.hint }}</p>
|
<p>{{ item.hint }}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="review-pending-status" :class="item.tone">{{ item.status }}</span>
|
<span class="review-followup-status">{{ item.status }}</span>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p v-if="buildReviewDecisionHint(message.reviewPayload)" class="review-followup-helper">
|
||||||
|
{{ buildReviewDecisionHint(message.reviewPayload) }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -538,7 +571,7 @@
|
|||||||
:placeholder="composerPlaceholder"
|
:placeholder="composerPlaceholder"
|
||||||
:disabled="submitting || reviewActionBusy || sessionSwitchBusy"
|
:disabled="submitting || reviewActionBusy || sessionSwitchBusy"
|
||||||
@input="handleComposerInput"
|
@input="handleComposerInput"
|
||||||
@keydown.enter.exact.stop
|
@keydown.enter.exact.prevent="handleComposerEnter"
|
||||||
@keydown.ctrl.enter.prevent="submitComposer"
|
@keydown.ctrl.enter.prevent="submitComposer"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -558,9 +591,13 @@
|
|||||||
:aria-hidden="(!showInsightPanel).toString()"
|
:aria-hidden="(!showInsightPanel).toString()"
|
||||||
>
|
>
|
||||||
<aside class="insight-panel">
|
<aside class="insight-panel">
|
||||||
<div v-if="!isKnowledgeSession" class="insight-head" :class="{ 'review-mode': activeReviewPayload }">
|
<div
|
||||||
|
v-if="!isKnowledgeSession"
|
||||||
|
class="insight-head"
|
||||||
|
:class="{ 'review-mode': activeReviewPayload || isReviewFlowDrawer }"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="!activeReviewPayload" class="insight-head-eyebrow">
|
<div v-if="!activeReviewPayload && !isReviewFlowDrawer" class="insight-head-eyebrow">
|
||||||
<span class="intent-pill" :class="currentInsight.intent">{{ currentIntentLabel }}</span>
|
<span class="intent-pill" :class="currentInsight.intent">{{ currentIntentLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="review-insight-title-row">
|
<div v-else class="review-insight-title-row">
|
||||||
@@ -568,12 +605,13 @@
|
|||||||
<h3>{{ reviewDrawerTitle }}</h3>
|
<h3>{{ reviewDrawerTitle }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 v-if="!activeReviewPayload">{{ currentInsight.title }}</h3>
|
<h3 v-if="!activeReviewPayload && !isReviewFlowDrawer">{{ currentInsight.title }}</h3>
|
||||||
<p v-if="!activeReviewPayload">{{ currentInsight.summary }}</p>
|
<p v-if="!activeReviewPayload && !isReviewFlowDrawer">{{ currentInsight.summary }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeReviewPayload" class="review-insight-tools">
|
<div v-if="activeReviewPayload || isReviewFlowDrawer" class="review-insight-tools">
|
||||||
<button
|
<button
|
||||||
|
v-if="activeReviewPayload"
|
||||||
type="button"
|
type="button"
|
||||||
class="review-insight-switch-icon-btn"
|
class="review-insight-switch-icon-btn"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -589,6 +627,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="activeReviewPayload"
|
||||||
type="button"
|
type="button"
|
||||||
class="review-insight-switch-icon-btn risk"
|
class="review-insight-switch-icon-btn risk"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -602,9 +641,25 @@
|
|||||||
>
|
>
|
||||||
<i :class="reviewRiskDrawerIcon"></i>
|
<i :class="reviewRiskDrawerIcon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="review-insight-switch-icon-btn flow"
|
||||||
|
:class="{
|
||||||
|
available: reviewFlowDrawerAvailable,
|
||||||
|
active: reviewFlowDrawerAvailable && isReviewFlowDrawer,
|
||||||
|
running: flowOverallStatusTone === 'running'
|
||||||
|
}"
|
||||||
|
:disabled="!reviewFlowDrawerAvailable || submitting || reviewActionBusy"
|
||||||
|
:title="reviewFlowDrawerLabel"
|
||||||
|
:aria-label="reviewFlowDrawerLabel"
|
||||||
|
@click="toggleReviewFlowDrawer"
|
||||||
|
>
|
||||||
|
<i :class="reviewFlowDrawerIcon"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="confidence-card" v-if="!activeReviewPayload">
|
<div class="confidence-card" v-if="!activeReviewPayload && !isReviewFlowDrawer">
|
||||||
<span>{{ currentInsight.metricLabel }}</span>
|
<span>{{ currentInsight.metricLabel }}</span>
|
||||||
<strong>{{ currentInsight.metricValue }}</strong>
|
<strong>{{ currentInsight.metricValue }}</strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -639,9 +694,60 @@
|
|||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="isReviewFlowDrawer">
|
||||||
|
<section class="review-flow-panel">
|
||||||
|
<div class="review-flow-summary">
|
||||||
|
<span class="flow-status-chip" :class="flowOverallStatusTone">{{ flowOverallStatusText }}</span>
|
||||||
|
<span>总耗时 {{ flowTotalDurationText }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flow-icon-btn"
|
||||||
|
:disabled="!flowRunId || flowRefreshBusy"
|
||||||
|
title="刷新流程"
|
||||||
|
aria-label="刷新流程"
|
||||||
|
@click="refreshFlowRunDetail"
|
||||||
|
>
|
||||||
|
<i :class="flowRefreshBusy ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-refresh'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="flowSteps.length" class="review-flow-list">
|
||||||
|
<article
|
||||||
|
v-for="(step, index) in flowSteps"
|
||||||
|
:key="step.key"
|
||||||
|
class="flow-step-item"
|
||||||
|
:class="step.status"
|
||||||
|
>
|
||||||
|
<div class="flow-step-rail">
|
||||||
|
<span>{{ index + 1 }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flow-step-card">
|
||||||
|
<header>
|
||||||
|
<strong>{{ step.title }}</strong>
|
||||||
|
<div class="flow-step-side">
|
||||||
|
<span class="flow-step-status" :class="step.status">{{ resolveFlowStepStatusLabel(step) }}</span>
|
||||||
|
<time>{{ formatFlowStepDuration(step) }}</time>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<p class="flow-step-tool">工具:{{ step.tool }}</p>
|
||||||
|
<p class="flow-step-detail">{{ resolveFlowStepDetail(step) }}</p>
|
||||||
|
<p v-if="step.error" class="flow-step-error">{{ step.error }}</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="flow-empty-state compact">
|
||||||
|
<i class="mdi mdi-timeline-question-outline"></i>
|
||||||
|
<strong>暂无识别流程</strong>
|
||||||
|
<p>发起识别后,这里会显示调用步骤和耗时。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-else-if="currentInsight.intent === 'agent' && currentInsight.agent">
|
<template v-else-if="currentInsight.intent === 'agent' && currentInsight.agent">
|
||||||
<template v-if="activeReviewPayload">
|
<template v-if="activeReviewPayload">
|
||||||
<template v-if="!isReviewDocumentDrawer && !isReviewRiskDrawer">
|
<template v-if="!isReviewDocumentDrawer && !isReviewRiskDrawer && !isReviewFlowDrawer">
|
||||||
<section class="review-side-card review-side-overview-card">
|
<section class="review-side-card review-side-overview-card">
|
||||||
<div class="review-side-intent-row">
|
<div class="review-side-intent-row">
|
||||||
<i class="mdi mdi-account-outline"></i>
|
<i class="mdi mdi-account-outline"></i>
|
||||||
@@ -778,6 +884,57 @@
|
|||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="isReviewFlowDrawer">
|
||||||
|
<section class="review-flow-panel">
|
||||||
|
<div class="review-flow-summary">
|
||||||
|
<span class="flow-status-chip" :class="flowOverallStatusTone">{{ flowOverallStatusText }}</span>
|
||||||
|
<span>总耗时 {{ flowTotalDurationText }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flow-icon-btn"
|
||||||
|
:disabled="!flowRunId || flowRefreshBusy"
|
||||||
|
title="刷新流程"
|
||||||
|
aria-label="刷新流程"
|
||||||
|
@click="refreshFlowRunDetail"
|
||||||
|
>
|
||||||
|
<i :class="flowRefreshBusy ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-refresh'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="flowSteps.length" class="review-flow-list">
|
||||||
|
<article
|
||||||
|
v-for="(step, index) in flowSteps"
|
||||||
|
:key="step.key"
|
||||||
|
class="flow-step-item"
|
||||||
|
:class="step.status"
|
||||||
|
>
|
||||||
|
<div class="flow-step-rail">
|
||||||
|
<span>{{ index + 1 }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flow-step-card">
|
||||||
|
<header>
|
||||||
|
<strong>{{ step.title }}</strong>
|
||||||
|
<div class="flow-step-side">
|
||||||
|
<span class="flow-step-status" :class="step.status">{{ resolveFlowStepStatusLabel(step) }}</span>
|
||||||
|
<time>{{ formatFlowStepDuration(step) }}</time>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<p class="flow-step-tool">工具:{{ step.tool }}</p>
|
||||||
|
<p class="flow-step-detail">{{ resolveFlowStepDetail(step) }}</p>
|
||||||
|
<p v-if="step.error" class="flow-step-error">{{ step.error }}</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="flow-empty-state compact">
|
||||||
|
<i class="mdi mdi-timeline-question-outline"></i>
|
||||||
|
<strong>暂无识别流程</strong>
|
||||||
|
<p>发起识别后,这里会显示调用步骤和耗时。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-else-if="isReviewDocumentDrawer">
|
<template v-else-if="isReviewDocumentDrawer">
|
||||||
<section class="review-side-card review-document-switch-card review-ticket-drawer">
|
<section class="review-side-card review-document-switch-card review-ticket-drawer">
|
||||||
<div class="review-side-head review-document-switch-head">
|
<div class="review-side-head review-document-switch-head">
|
||||||
@@ -996,6 +1153,7 @@
|
|||||||
</Transition>
|
</Transition>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ const SESSION_TYPE_KNOWLEDGE = 'knowledge'
|
|||||||
const REVIEW_DRAWER_MODE_REVIEW = 'review'
|
const REVIEW_DRAWER_MODE_REVIEW = 'review'
|
||||||
const REVIEW_DRAWER_MODE_DOCUMENTS = 'documents'
|
const REVIEW_DRAWER_MODE_DOCUMENTS = 'documents'
|
||||||
const REVIEW_DRAWER_MODE_RISK = 'risk'
|
const REVIEW_DRAWER_MODE_RISK = 'risk'
|
||||||
|
const REVIEW_DRAWER_MODE_FLOW = 'flow'
|
||||||
const FLOW_STEP_STATUS_PENDING = 'pending'
|
const FLOW_STEP_STATUS_PENDING = 'pending'
|
||||||
const FLOW_STEP_STATUS_RUNNING = 'running'
|
const FLOW_STEP_STATUS_RUNNING = 'running'
|
||||||
const FLOW_STEP_STATUS_COMPLETED = 'completed'
|
const FLOW_STEP_STATUS_COMPLETED = 'completed'
|
||||||
@@ -208,6 +209,12 @@ const FLOW_STEP_FALLBACKS = {
|
|||||||
runningText: '正在识别票据附件...',
|
runningText: '正在识别票据附件...',
|
||||||
completedText: '票据识别完成'
|
completedText: '票据识别完成'
|
||||||
},
|
},
|
||||||
|
agent: {
|
||||||
|
title: '智能体编排',
|
||||||
|
tool: 'UserAgent',
|
||||||
|
runningText: '正在调用财务智能体...',
|
||||||
|
completedText: '智能体处理完成'
|
||||||
|
},
|
||||||
result: {
|
result: {
|
||||||
title: '生成结果',
|
title: '生成结果',
|
||||||
tool: 'ResultGenerator',
|
tool: 'ResultGenerator',
|
||||||
@@ -230,7 +237,7 @@ const EXPENSE_WELCOME_QUICK_ACTIONS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '交通费报销',
|
label: '交通费报销',
|
||||||
prompt: '我要报销交通出行费用,请帮我识别常见票据类型和报销注意事项。',
|
prompt: '我要报销交通出行费用,请帮我识别场景并列出待补充信息。',
|
||||||
icon: 'mdi mdi-car-outline'
|
icon: 'mdi mdi-car-outline'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -250,41 +257,6 @@ const EXPENSE_WELCOME_QUICK_ACTIONS = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const ASSISTANT_DISPLAY_NAME = '财务助手'
|
|
||||||
|
|
||||||
const EXPENSE_WELCOME_QUICK_ACTIONS = [
|
|
||||||
{
|
|
||||||
label: '发起差旅报销',
|
|
||||||
prompt: '我要报销一笔出差费用,请帮我说明需要准备的材料,并引导我上传票据。',
|
|
||||||
icon: 'mdi mdi-bag-suitcase-outline'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '招待费报销',
|
|
||||||
prompt: '我要报销客户招待餐费,请告诉我需要补充的客户、参与人员和票据要求。',
|
|
||||||
icon: 'mdi mdi-food-fork-drink'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '交通费报销',
|
|
||||||
prompt: '我要报销交通出行费用,请帮我识别场景并列出待补充信息。',
|
|
||||||
icon: 'mdi mdi-car-outline'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '上传票据识别',
|
|
||||||
prompt: '我已准备好票据,请帮我识别票据内容并生成报销草稿。',
|
|
||||||
icon: 'mdi mdi-file-upload-outline'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '查询近期报销',
|
|
||||||
prompt: '帮我查询近10天的报销记录和金额汇总。',
|
|
||||||
icon: 'mdi mdi-chart-timeline-variant'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '解释报销风险',
|
|
||||||
prompt: '请结合公司制度,说明酒店超标、发票抬头不一致等常见报销风险与处理方式。',
|
|
||||||
icon: 'mdi mdi-shield-alert-outline'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const HOT_KNOWLEDGE_QUESTIONS = [
|
const HOT_KNOWLEDGE_QUESTIONS = [
|
||||||
'差旅住宿标准按什么规则执行?',
|
'差旅住宿标准按什么规则执行?',
|
||||||
'酒店超标后如何申请例外报销?',
|
'酒店超标后如何申请例外报销?',
|
||||||
@@ -373,8 +345,39 @@ function formatMessageTime(value) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFlowSteps() {
|
function createFlowSteps(options = {}) {
|
||||||
return []
|
const keys = []
|
||||||
|
if (options.includeIntent) {
|
||||||
|
keys.push('intent')
|
||||||
|
}
|
||||||
|
if (options.includeOcr) {
|
||||||
|
keys.push('ocr')
|
||||||
|
}
|
||||||
|
if (options.includeExtraction) {
|
||||||
|
keys.push('extraction')
|
||||||
|
}
|
||||||
|
if (options.includeAgent) {
|
||||||
|
keys.push('agent')
|
||||||
|
}
|
||||||
|
if (options.includeResult) {
|
||||||
|
keys.push('result')
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys.map((key, index) => {
|
||||||
|
const definition = FLOW_STEP_FALLBACKS[key] || {}
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
index: index + 1,
|
||||||
|
title: definition.title || '智能体工具调用',
|
||||||
|
tool: definition.tool || 'AgentTool',
|
||||||
|
status: FLOW_STEP_STATUS_PENDING,
|
||||||
|
detail: '',
|
||||||
|
durationMs: null,
|
||||||
|
startedAt: 0,
|
||||||
|
finishedAt: 0,
|
||||||
|
error: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSemanticEntityValue(entity) {
|
function formatSemanticEntityValue(entity) {
|
||||||
@@ -2888,7 +2891,6 @@ export default {
|
|||||||
url: ''
|
url: ''
|
||||||
})
|
})
|
||||||
const sessionSwitchBusy = ref(false)
|
const sessionSwitchBusy = ref(false)
|
||||||
const flowPanelOpen = ref(false)
|
|
||||||
const flowRunId = ref('')
|
const flowRunId = ref('')
|
||||||
const flowStartedAt = ref(0)
|
const flowStartedAt = ref(0)
|
||||||
const flowFinishedAt = ref(0)
|
const flowFinishedAt = ref(0)
|
||||||
@@ -2950,8 +2952,24 @@ export default {
|
|||||||
}
|
}
|
||||||
return total ? `待执行 0/${total}` : '暂无流程'
|
return total ? `待执行 0/${total}` : '暂无流程'
|
||||||
})
|
})
|
||||||
|
const flowTotalDurationText = computed(() => {
|
||||||
|
if (!flowStartedAt.value) {
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
const finishedAt = flowFinishedAt.value || (runningFlowStep.value ? flowTick.value : 0)
|
||||||
|
if (finishedAt > flowStartedAt.value) {
|
||||||
|
return formatFlowDuration(finishedAt - flowStartedAt.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const measuredDuration = flowSteps.value.reduce((total, step) => {
|
||||||
|
const duration = Number(step.durationMs)
|
||||||
|
return total + (Number.isFinite(duration) && duration > 0 ? duration : 0)
|
||||||
|
}, 0)
|
||||||
|
return measuredDuration ? formatFlowDuration(measuredDuration) : '--'
|
||||||
|
})
|
||||||
const hasInsightPanelContent = computed(
|
const hasInsightPanelContent = computed(
|
||||||
() => isKnowledgeSession.value || currentInsight.value.intent !== 'welcome'
|
() => isKnowledgeSession.value || currentInsight.value.intent !== 'welcome' || flowSteps.value.length > 0
|
||||||
)
|
)
|
||||||
const showInsightPanel = computed(() => hasInsightPanelContent.value && !insightPanelCollapsed.value)
|
const showInsightPanel = computed(() => hasInsightPanelContent.value && !insightPanelCollapsed.value)
|
||||||
const insightPanelToggleLabel = computed(() =>
|
const insightPanelToggleLabel = computed(() =>
|
||||||
@@ -3027,18 +3045,22 @@ export default {
|
|||||||
const reviewDocumentDrawerAvailable = computed(() => reviewDocumentCount.value > 0)
|
const reviewDocumentDrawerAvailable = computed(() => reviewDocumentCount.value > 0)
|
||||||
const reviewRiskDrawerAvailable = computed(() => !reviewRiskEmpty.value)
|
const reviewRiskDrawerAvailable = computed(() => !reviewRiskEmpty.value)
|
||||||
const reviewRiskActionAvailable = computed(() => reviewRiskItems.value.length > 0)
|
const reviewRiskActionAvailable = computed(() => reviewRiskItems.value.length > 0)
|
||||||
|
const reviewFlowDrawerAvailable = computed(() => flowSteps.value.length > 0)
|
||||||
const recognizedNarratives = computed(() => buildReviewRecognizedLines(activeReviewPayload.value))
|
const recognizedNarratives = computed(() => buildReviewRecognizedLines(activeReviewPayload.value))
|
||||||
const reviewRecognitionNotes = computed(() => buildReviewRecognitionNotes(activeReviewPayload.value))
|
const reviewRecognitionNotes = computed(() => buildReviewRecognitionNotes(activeReviewPayload.value))
|
||||||
const reviewDocumentSummaries = computed(() => buildReviewDocumentSummaries(activeReviewPayload.value))
|
const reviewDocumentSummaries = computed(() => buildReviewDocumentSummaries(activeReviewPayload.value))
|
||||||
const reviewDocumentCount = computed(() => reviewDocumentDrafts.value.length)
|
const reviewDocumentCount = computed(() => reviewDocumentDrafts.value.length)
|
||||||
const isReviewDocumentDrawer = computed(() => reviewDrawerMode.value === REVIEW_DRAWER_MODE_DOCUMENTS)
|
const isReviewDocumentDrawer = computed(() => reviewDrawerMode.value === REVIEW_DRAWER_MODE_DOCUMENTS)
|
||||||
const isReviewRiskDrawer = computed(() => reviewDrawerMode.value === REVIEW_DRAWER_MODE_RISK)
|
const isReviewRiskDrawer = computed(() => reviewDrawerMode.value === REVIEW_DRAWER_MODE_RISK)
|
||||||
|
const isReviewFlowDrawer = computed(() => reviewDrawerMode.value === REVIEW_DRAWER_MODE_FLOW)
|
||||||
const reviewDrawerTitle = computed(() => (
|
const reviewDrawerTitle = computed(() => (
|
||||||
isReviewDocumentDrawer.value
|
isReviewDocumentDrawer.value
|
||||||
? '票据识别结果'
|
? '票据识别结果'
|
||||||
: isReviewRiskDrawer.value
|
: isReviewRiskDrawer.value
|
||||||
? '风险提示'
|
? '风险提示'
|
||||||
: '报销识别核对'
|
: isReviewFlowDrawer.value
|
||||||
|
? '调用流程'
|
||||||
|
: '报销识别核对'
|
||||||
))
|
))
|
||||||
const reviewDocumentDrawerLabel = computed(() => (
|
const reviewDocumentDrawerLabel = computed(() => (
|
||||||
isReviewDocumentDrawer.value ? '显示核对' : '显示票据'
|
isReviewDocumentDrawer.value ? '显示核对' : '显示票据'
|
||||||
@@ -3056,6 +3078,14 @@ export default {
|
|||||||
? 'mdi mdi-shield-alert'
|
? 'mdi mdi-shield-alert'
|
||||||
: 'mdi mdi-shield-alert-outline'
|
: 'mdi mdi-shield-alert-outline'
|
||||||
))
|
))
|
||||||
|
const reviewFlowDrawerLabel = computed(() => (
|
||||||
|
isReviewFlowDrawer.value ? '显示核对' : '显示流程'
|
||||||
|
))
|
||||||
|
const reviewFlowDrawerIcon = computed(() => (
|
||||||
|
isReviewFlowDrawer.value
|
||||||
|
? 'mdi mdi-timeline-clock'
|
||||||
|
: 'mdi mdi-timeline-clock-outline'
|
||||||
|
))
|
||||||
const activeReviewDocument = computed(() => reviewDocumentDrafts.value[activeReviewDocumentIndex.value] ?? null)
|
const activeReviewDocument = computed(() => reviewDocumentDrafts.value[activeReviewDocumentIndex.value] ?? null)
|
||||||
const activeReviewDocumentPreview = computed(() =>
|
const activeReviewDocumentPreview = computed(() =>
|
||||||
activeReviewDocument.value
|
activeReviewDocument.value
|
||||||
@@ -3305,6 +3335,15 @@ export default {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => reviewFlowDrawerAvailable.value,
|
||||||
|
(available) => {
|
||||||
|
if (!available && reviewDrawerMode.value === REVIEW_DRAWER_MODE_FLOW) {
|
||||||
|
reviewDrawerMode.value = REVIEW_DRAWER_MODE_REVIEW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => composerDraft.value,
|
() => composerDraft.value,
|
||||||
() => {
|
() => {
|
||||||
@@ -3377,7 +3416,6 @@ export default {
|
|||||||
flowStartedAt.value = 0
|
flowStartedAt.value = 0
|
||||||
flowFinishedAt.value = 0
|
flowFinishedAt.value = 0
|
||||||
flowSteps.value = createFlowSteps()
|
flowSteps.value = createFlowSteps()
|
||||||
flowPanelOpen.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustComposerTextareaHeight() {
|
function adjustComposerTextareaHeight() {
|
||||||
@@ -3408,14 +3446,6 @@ export default {
|
|||||||
submitComposer()
|
submitComposer()
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFlowPanel() {
|
|
||||||
flowPanelOpen.value = !flowPanelOpen.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function openFlowPanel() {
|
|
||||||
flowPanelOpen.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearFlowSimulationTimers() {
|
function clearFlowSimulationTimers() {
|
||||||
while (flowSimulationTimers.length) {
|
while (flowSimulationTimers.length) {
|
||||||
const timerId = flowSimulationTimers.pop()
|
const timerId = flowSimulationTimers.pop()
|
||||||
@@ -3424,25 +3454,22 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleFlowPanelAutoCollapse(delayMs = 1200) {
|
function resetFlowRun(options = {}) {
|
||||||
const collapseTimer = window.setTimeout(() => {
|
|
||||||
if (runningFlowStep.value || flowRefreshBusy.value || submitting.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (flowSteps.value.length && !flowSteps.value.some((step) => step.status === FLOW_STEP_STATUS_FAILED)) {
|
|
||||||
flowPanelOpen.value = false
|
|
||||||
}
|
|
||||||
}, delayMs)
|
|
||||||
flowSimulationTimers.push(collapseTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetFlowRun() {
|
|
||||||
clearFlowSimulationTimers()
|
clearFlowSimulationTimers()
|
||||||
flowPanelOpen.value = true
|
|
||||||
flowRunId.value = ''
|
flowRunId.value = ''
|
||||||
flowStartedAt.value = Date.now()
|
flowStartedAt.value = Date.now()
|
||||||
flowFinishedAt.value = 0
|
flowFinishedAt.value = 0
|
||||||
flowSteps.value = createFlowSteps()
|
reviewDrawerMode.value = REVIEW_DRAWER_MODE_FLOW
|
||||||
|
insightPanelCollapsed.value = false
|
||||||
|
const hasText = Boolean(String(options.rawText || '').trim())
|
||||||
|
const attachmentCount = Number(options.attachmentCount || 0)
|
||||||
|
flowSteps.value = createFlowSteps({
|
||||||
|
includeIntent: hasText,
|
||||||
|
includeOcr: attachmentCount > 0,
|
||||||
|
includeExtraction: hasText || attachmentCount > 0,
|
||||||
|
includeAgent: true,
|
||||||
|
includeResult: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFlowDefinition(key) {
|
function findFlowDefinition(key) {
|
||||||
@@ -3476,10 +3503,22 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeFlowStepIndexes(steps) {
|
||||||
|
return steps.map((step, index) => ({ ...step, index: index + 1 }))
|
||||||
|
}
|
||||||
|
|
||||||
function upsertFlowStep(key, patch) {
|
function upsertFlowStep(key, patch) {
|
||||||
const existingStep = flowSteps.value.find((step) => step.key === key)
|
const existingStep = flowSteps.value.find((step) => step.key === key)
|
||||||
if (!existingStep) {
|
if (!existingStep) {
|
||||||
flowSteps.value = [...flowSteps.value, createFlowStep(key, patch)]
|
const nextStep = createFlowStep(key, patch)
|
||||||
|
const resultIndex = flowSteps.value.findIndex((step) => step.key === 'result')
|
||||||
|
if (resultIndex !== -1 && key !== 'result') {
|
||||||
|
const nextSteps = [...flowSteps.value]
|
||||||
|
nextSteps.splice(resultIndex, 0, nextStep)
|
||||||
|
flowSteps.value = normalizeFlowStepIndexes(nextSteps)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
flowSteps.value = normalizeFlowStepIndexes([...flowSteps.value, nextStep])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const normalizedPatch = normalizeFlowStepPatch(key, patch)
|
const normalizedPatch = normalizeFlowStepPatch(key, patch)
|
||||||
@@ -3660,6 +3699,13 @@ export default {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flowSteps.value.some((step) => step.key === 'agent')) {
|
||||||
|
completePendingFlowStep(
|
||||||
|
'agent',
|
||||||
|
toolCalls.length ? `已完成 ${toolCalls.length} 个工具调用` : FLOW_STEP_FALLBACKS.agent.completedText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
toolCalls.forEach((toolCall, index) => {
|
toolCalls.forEach((toolCall, index) => {
|
||||||
const meta = resolveToolCallFlowMeta(toolCall, index)
|
const meta = resolveToolCallFlowMeta(toolCall, index)
|
||||||
const failed = String(toolCall?.status || '').toLowerCase() === 'failed'
|
const failed = String(toolCall?.status || '').toLowerCase() === 'failed'
|
||||||
@@ -3687,10 +3733,17 @@ export default {
|
|||||||
if (!answer && !payload?.result) {
|
if (!answer && !payload?.result) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
flowSteps.value
|
||||||
|
.filter((step) => step.key !== 'result' && ![FLOW_STEP_STATUS_COMPLETED, FLOW_STEP_STATUS_FAILED].includes(step.status))
|
||||||
|
.forEach((step) => {
|
||||||
|
completeFlowStep(step.key, resolveFlowStepDetail({ ...step, status: FLOW_STEP_STATUS_COMPLETED }))
|
||||||
|
})
|
||||||
startFlowStep('result', '正在返回处理结果...')
|
startFlowStep('result', '正在返回处理结果...')
|
||||||
completeFlowStep('result', '结果已返回到对话区', resolveResultStepDurationMs(run))
|
completeFlowStep('result', '结果已返回到对话区', resolveResultStepDurationMs(run))
|
||||||
flowFinishedAt.value = Date.now()
|
flowFinishedAt.value = Date.now()
|
||||||
scheduleFlowPanelAutoCollapse()
|
if (reviewDrawerMode.value === REVIEW_DRAWER_MODE_FLOW) {
|
||||||
|
reviewDrawerMode.value = REVIEW_DRAWER_MODE_REVIEW
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshFlowRunDetail() {
|
async function refreshFlowRunDetail() {
|
||||||
@@ -3717,6 +3770,38 @@ export default {
|
|||||||
return formatFlowDuration(step?.durationMs)
|
return formatFlowDuration(step?.durationMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveFlowStepStatusLabel(step) {
|
||||||
|
const status = String(step?.status || '').trim()
|
||||||
|
if (status === FLOW_STEP_STATUS_COMPLETED) {
|
||||||
|
return '完成'
|
||||||
|
}
|
||||||
|
if (status === FLOW_STEP_STATUS_RUNNING) {
|
||||||
|
return '执行中'
|
||||||
|
}
|
||||||
|
if (status === FLOW_STEP_STATUS_FAILED) {
|
||||||
|
return '异常'
|
||||||
|
}
|
||||||
|
return '待执行'
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFlowStepDetail(step) {
|
||||||
|
const detail = String(step?.detail || '').trim()
|
||||||
|
if (detail) {
|
||||||
|
return detail
|
||||||
|
}
|
||||||
|
const definition = findFlowDefinition(step?.key)
|
||||||
|
if (step?.status === FLOW_STEP_STATUS_COMPLETED) {
|
||||||
|
return definition?.completedText || '步骤已完成'
|
||||||
|
}
|
||||||
|
if (step?.status === FLOW_STEP_STATUS_RUNNING) {
|
||||||
|
return definition?.runningText || '正在执行当前步骤...'
|
||||||
|
}
|
||||||
|
if (step?.status === FLOW_STEP_STATUS_FAILED) {
|
||||||
|
return step?.error || '步骤执行异常'
|
||||||
|
}
|
||||||
|
return definition?.runningText ? `等待${definition.title || '当前步骤'}...` : '等待智能体调度...'
|
||||||
|
}
|
||||||
|
|
||||||
function buildComposerBusinessTimeLabel() {
|
function buildComposerBusinessTimeLabel() {
|
||||||
if (composerDateMode.value === 'single') {
|
if (composerDateMode.value === 'single') {
|
||||||
return `业务发生时间:${composerSingleDate.value}`
|
return `业务发生时间:${composerSingleDate.value}`
|
||||||
@@ -4064,6 +4149,16 @@ export default {
|
|||||||
: REVIEW_DRAWER_MODE_RISK
|
: REVIEW_DRAWER_MODE_RISK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleReviewFlowDrawer() {
|
||||||
|
if (!reviewFlowDrawerAvailable.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reviewDrawerMode.value =
|
||||||
|
reviewDrawerMode.value === REVIEW_DRAWER_MODE_FLOW
|
||||||
|
? REVIEW_DRAWER_MODE_REVIEW
|
||||||
|
: REVIEW_DRAWER_MODE_FLOW
|
||||||
|
}
|
||||||
|
|
||||||
function setInlineReviewFieldError(key, message) {
|
function setInlineReviewFieldError(key, message) {
|
||||||
reviewInlineErrors.value = {
|
reviewInlineErrors.value = {
|
||||||
...reviewInlineErrors.value,
|
...reviewInlineErrors.value,
|
||||||
@@ -4438,7 +4533,7 @@ export default {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFlowRun()
|
resetFlowRun({ rawText, attachmentCount: files.length })
|
||||||
if (rawText) {
|
if (rawText) {
|
||||||
startFlowStep('intent', '正在识别业务意图...')
|
startFlowStep('intent', '正在识别业务意图...')
|
||||||
startSemanticFlowPreview(rawText, { attachmentCount: files.length })
|
startSemanticFlowPreview(rawText, { attachmentCount: files.length })
|
||||||
@@ -4524,6 +4619,17 @@ export default {
|
|||||||
extraContext.review_action = 'create_new_claim_from_documents'
|
extraContext.review_action = 'create_new_claim_from_documents'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runningExtractionStep = flowSteps.value.find(
|
||||||
|
(step) => step.key === 'extraction' && step.status === FLOW_STEP_STATUS_RUNNING
|
||||||
|
)
|
||||||
|
if (runningExtractionStep) {
|
||||||
|
completeFlowStep(
|
||||||
|
'extraction',
|
||||||
|
runningExtractionStep.detail || FLOW_STEP_FALLBACKS.extraction.completedText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
startFlowStep('agent', FLOW_STEP_FALLBACKS.agent.runningText)
|
||||||
|
|
||||||
const backendMessage = buildBackendMessage(rawText, effectiveFileNames, effectiveOcrSummary)
|
const backendMessage = buildBackendMessage(rawText, effectiveFileNames, effectiveOcrSummary)
|
||||||
const payload = await runOrchestrator(
|
const payload = await runOrchestrator(
|
||||||
{
|
{
|
||||||
@@ -4887,12 +4993,13 @@ export default {
|
|||||||
closeComposerDatePicker,
|
closeComposerDatePicker,
|
||||||
setComposerDateMode,
|
setComposerDateMode,
|
||||||
removeComposerBusinessTimeTag,
|
removeComposerBusinessTimeTag,
|
||||||
flowPanelOpen,
|
|
||||||
flowSteps,
|
flowSteps,
|
||||||
flowRunId,
|
flowRunId,
|
||||||
flowRefreshBusy,
|
flowRefreshBusy,
|
||||||
|
completedFlowStepCount,
|
||||||
flowOverallStatusTone,
|
flowOverallStatusTone,
|
||||||
flowOverallStatusText,
|
flowOverallStatusText,
|
||||||
|
flowTotalDurationText,
|
||||||
attachedFiles,
|
attachedFiles,
|
||||||
composerFilesExpanded,
|
composerFilesExpanded,
|
||||||
visibleAttachedFiles,
|
visibleAttachedFiles,
|
||||||
@@ -4918,13 +5025,17 @@ export default {
|
|||||||
reviewDrawerMode,
|
reviewDrawerMode,
|
||||||
isReviewDocumentDrawer,
|
isReviewDocumentDrawer,
|
||||||
isReviewRiskDrawer,
|
isReviewRiskDrawer,
|
||||||
|
isReviewFlowDrawer,
|
||||||
reviewDrawerTitle,
|
reviewDrawerTitle,
|
||||||
reviewDocumentDrawerAvailable,
|
reviewDocumentDrawerAvailable,
|
||||||
reviewRiskDrawerAvailable,
|
reviewRiskDrawerAvailable,
|
||||||
|
reviewFlowDrawerAvailable,
|
||||||
reviewDocumentDrawerLabel,
|
reviewDocumentDrawerLabel,
|
||||||
reviewDocumentDrawerIcon,
|
reviewDocumentDrawerIcon,
|
||||||
reviewRiskDrawerLabel,
|
reviewRiskDrawerLabel,
|
||||||
reviewRiskDrawerIcon,
|
reviewRiskDrawerIcon,
|
||||||
|
reviewFlowDrawerLabel,
|
||||||
|
reviewFlowDrawerIcon,
|
||||||
activeReviewDocument,
|
activeReviewDocument,
|
||||||
activeReviewDocumentIndex,
|
activeReviewDocumentIndex,
|
||||||
activeReviewDocumentPreview,
|
activeReviewDocumentPreview,
|
||||||
@@ -5005,13 +5116,14 @@ export default {
|
|||||||
askHotKnowledgeQuestion,
|
askHotKnowledgeQuestion,
|
||||||
resolveKnowledgeRankLabel,
|
resolveKnowledgeRankLabel,
|
||||||
resolveKnowledgeRankTone,
|
resolveKnowledgeRankTone,
|
||||||
toggleFlowPanel,
|
|
||||||
openFlowPanel,
|
|
||||||
refreshFlowRunDetail,
|
refreshFlowRunDetail,
|
||||||
formatFlowStepDuration,
|
formatFlowStepDuration,
|
||||||
|
resolveFlowStepStatusLabel,
|
||||||
|
resolveFlowStepDetail,
|
||||||
toggleInsightPanel,
|
toggleInsightPanel,
|
||||||
toggleReviewDocumentDrawer,
|
toggleReviewDocumentDrawer,
|
||||||
toggleReviewRiskDrawer,
|
toggleReviewRiskDrawer,
|
||||||
|
toggleReviewFlowDrawer,
|
||||||
toggleAttachedFilesExpanded,
|
toggleAttachedFilesExpanded,
|
||||||
removeAttachedFile,
|
removeAttachedFile,
|
||||||
clearAttachedFiles,
|
clearAttachedFiles,
|
||||||
|
|||||||
Reference in New Issue
Block a user