feat(assistant): unify confirmed memory capture

This commit is contained in:
2026-07-20 02:30:06 +08:00
parent 46a6156d8c
commit 0a5fc9a463
32 changed files with 949 additions and 242 deletions
@@ -5,7 +5,7 @@
<view class="subpage-heading">
<text class="employee-kicker">工作助手</text>
<text class="page-title">我的资料与记忆</text>
<text class="page-subtitle">首版仅展示待确认事项和项目跟进;完整个人资料库将在后续开放。</text>
<text class="page-subtitle">查看仅自己保存的信息、公司处理进度和项目跟进。</text>
</view>
</view>
@@ -38,7 +38,35 @@
</view>
<view v-else class="empty-card employee-card">
<text class="empty-title">暂无待确认事项</text>
<text class="empty-copy">在工作助手里说“记一下”,识别到完整信息后会先让你确认。</text>
<text class="empty-copy">在工作助手里说“记一下”,系统整理后会先让你选择保存范围。</text>
</view>
<view class="section-head followup-head">
<view>
<text class="section-title">我的记录</text>
<text class="section-note">仅自己保存的内容不会提交公司</text>
</view>
</view>
<view v-if="captures.length" class="service-list">
<view v-for="item in captures" :key="item.id" class="service-card employee-card">
<view class="service-head">
<text class="service-category">{{ captureTypeLabel(item.suggestedType) }}</text>
<text class="service-status" :class="{ done: item.saveScope === 'PRIVATE' }">
{{ captureScopeLabel(item) }}
</text>
</view>
<text class="service-subject">{{ captureSubject(item) || item.category }}</text>
<text class="service-summary">{{ item.summary }}</text>
<view class="service-meta">
<text>记录 {{ displayDate(item.occurredAt) }}</text>
<text v-if="item.followUpAt">跟进 {{ displayDate(item.followUpAt) }}</text>
<text v-if="item.saveScope === 'COMPANY'">{{ deliveryLabel(item.deliveryStatus) }}</text>
</view>
</view>
</view>
<view v-else class="empty-card employee-card">
<text class="empty-title">暂无已保存记录</text>
<text class="empty-copy">确认保存后的个人想法、服务线索和工作事项会显示在这里。</text>
</view>
<view class="section-head followup-head">
@@ -74,9 +102,9 @@
<script setup lang="ts">
import { ref } from 'vue';
import { onHide, onShow, onUnload } from '@dcloudio/uni-app';
import type { MemoryCandidate, ServiceMemory } from '@/types/api';
import type { AssistantCapture, MemoryCandidate, ServiceMemory } from '@/types/api';
import MemoryCandidateCard from '@/components/chat/MemoryCandidateCard.vue';
import { getMemoryCandidates, getServiceMemories } from '@/services/personal-assistant';
import { getAssistantCaptures, getMemoryCandidates, getServiceMemories } from '@/services/personal-assistant';
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { ensureEmployeePosition } from '@/services/position';
import { resetPageScroll } from '@/services/navigation';
@@ -87,6 +115,7 @@ const loggedIn = ref(false);
const loading = ref(false);
const message = ref('');
const candidates = ref<MemoryCandidate[]>([]);
const captures = ref<AssistantCapture[]>([]);
const serviceMemories = ref<ServiceMemory[]>([]);
const requests = createPageRequestScope();
@@ -122,41 +151,52 @@ const serviceSubject = (item: ServiceMemory) => {
].filter(Boolean).join('');
};
const captureSubject = (item: AssistantCapture) => [item.buildingName, item.unitName, item.roomNo]
.filter(Boolean).join('');
const captureTypeLabel = (type = '') => ({
SERVICE_LEAD: '服务线索',
RESIDENT_PROFILE: '住户画像',
CASE: '优秀案例',
PERSONAL_NOTE: '个人想法',
FOLLOW_UP: '待跟进事项',
PROJECT_NOTE: '工作记录'
}[type] || '工作记录');
const captureScopeLabel = (item: AssistantCapture) => item.saveScope === 'PRIVATE' ? '仅自己' : '公司处理';
const deliveryLabel = (status = '') => ({
PENDING: '待流转',
DELIVERED: '已流转',
FAILED: '流转待重试'
}[status] || '处理中');
const removeCandidate = (candidateId: number, toast: string) => {
candidates.value = candidates.value.filter((item) => item.id !== candidateId);
uni.showToast({ title: toast, icon: 'none' });
void loadServiceMemories();
};
const loadServiceMemories = async () => {
const request = requests.begin('service-memories');
try {
const result = await getServiceMemories();
if (requests.isCurrent(request)) serviceMemories.value = result;
} catch (error) {
if (requests.isCurrent(request)) {
message.value = error instanceof Error ? error.message : '项目跟进读取失败';
}
}
void load();
};
const load = async () => {
loggedIn.value = isLoggedIn();
candidates.value = [];
captures.value = [];
serviceMemories.value = [];
message.value = '';
if (!loggedIn.value) return;
if (!(await ensureEmployeePosition(path))) return;
const request = requests.begin('memory-page');
loading.value = true;
const [candidateResult, serviceResult] = await Promise.allSettled([
const [candidateResult, captureResult, serviceResult] = await Promise.allSettled([
getMemoryCandidates('DRAFT'),
getAssistantCaptures(),
getServiceMemories()
]);
if (!requests.isCurrent(request)) return;
if (candidateResult.status === 'fulfilled') candidates.value = candidateResult.value;
if (captureResult.status === 'fulfilled') captures.value = captureResult.value;
if (serviceResult.status === 'fulfilled') serviceMemories.value = serviceResult.value;
const errors = [candidateResult, serviceResult]
const errors = [candidateResult, captureResult, serviceResult]
.filter((item): item is PromiseRejectedResult => item.status === 'rejected')
.map((item) => item.reason instanceof Error ? item.reason.message : '读取失败');
message.value = errors.join(';');
+4 -1
View File
@@ -61,7 +61,10 @@
</button>
</view>
<view v-if="msg.voiceStatus === 'unavailable' || msg.transcriptOpen" class="bubble master transcript-bubble">
<view
v-if="(msg.voiceStatus === 'unavailable' || msg.transcriptOpen) && (msg.result.answer || msg.result.snippets?.length || !msg.result.memoryCandidate)"
class="bubble master transcript-bubble"
>
<text
v-if="msg.result.rewrittenQuery && msg.result.rewrittenQuery !== msg.result.queryText"
class="context-used"